So, your application is crashing and it is crashing in the bad way. After spending hours of debugging and trying different things you figured out that this is this Xslt stylesheet that causes all the problems. How come? XslCompiledTransform is a compiler. It’s a bit different from C# or VB.NET compilers because it runs at runtime but the result of the compilation is basically the same – IL (Intermediate Language) code. One of disadvantages of running compilation at runtime is that compilation issues may have impact on the running application. In the “offline” scenario compiler would just return an error (the one I hate the most is “Internal Compiler Error” – still it will not affect my app at runtime – at worst there is no app). So, compilation may be one source of problems. The other source of problems can be the IL code the stylesheet was compiled to. Here there is not much difference between code generated from Xslt, C# or VB. In any case it is easy to write a program that compiles correctly but misbehaves at runtime (bugs anyone?). Let’s take a look at two most common unexpected exceptions you may hit when working with XslCompiledTransform and ways to resolve them.

OutOfMemoryException (a.k.a. memory leak)

This is one is pretty easy. It can happen when you combine embedded scripts or loading stylesheets in debug mode with the not recommended way of using XsltCompiledTransform. The recommended way of using XslCompiledTransform is to load an Xslt stylesheet once and re-use the instance the stylesheet was loaded to for further transformations. Loading the same stylesheet repeatedly should be avoided. The main concern here is performance. XslCompiledTransform.Load() does not mean only “load the stylesheet” but “load and compile the stylesheet”. This is the “compile” part that makes .Load() expensive. There is more to the story though. In some cases when compiling a stylesheet the compiler will create assemblies on the fly. When does this happen? Easy to guess - either when you are using scripts in your stylesheet (to compile scripts the compiler uses CodeDOM which creates assemblies containing compiled code for the scripts) or when loading a stylesheet in debug mode (necessary to make stylesheet debugging work). Why is this important? The dynamically created assemblies are loaded into the AppDomain. Once they are loaded they cannot be unloaded (CLR does not allow unloading a single assembly from an AppDomain – the only way to unload an assembly from an AppDomain is to unload the whole AppDomain). So, if you load a stylesheet in debug mode or a stylesheet containing a script repeatedly you will see that your app is consuming more and more memory. The reason for this are the dynamically created assemblies that cannot be unloaded (Houston, we’ve got a leak!). Eventually, so many assemblies have been created and loaded that it is not possible to load yet another one and you get the OutOfMemoryException. If you just load a stylesheet with a script once (you should avoid loading stylesheets in debug mode in production environment entirely) and re-use the object there will be only one assembly created for your script and you should not see the OutOfMemoryExceptions caused by the Xslt. Most likely you will also see a perf boost which is a result of not compiling the stylesheet many times. Another way to resolve the problem is to compile the stylesheet “offline” (i.e. not at runtime) with the xsltc (xslt compiler, more details in MSDN http://msdn.microsoft.com/en-us/library/bb399405.aspx). This tool will create “regular” assemblies (.dll files) for the Xslt stylesheet and the embedded scripts. To be able to use them in your app you just need to add references. The memory leak problem is resolved – there is no compilation going on at runtime so no temporary assemblies are created.

StackOverflowException

This exception is bad. It brings the whole process down and there is nothing in your app you can really do about it (starting from .NET Framework 2.0 StackOverflowException is not “catchable”). When using XslCompiledTransform you can get this exception either when the Xslt stylesheet is being compiled or when the transformation is being performed.

StackOverflowExceptions thrown when compiling the stylesheet are rare. There can be just a couple root causes of this problem:

  • You have a pretty big xsl:choose instruction. “Big” means here a thousand or more xsl:when instructions inside this single xsl:choose instruction on a 64-bit machine when there is ~256KB of stack remaining (see notes below for more details). The fix is simple – refactor the stylesheet.
    • If you use xsl:choose just to simulate a dictionary replace it with a variable containing an xml fragment with key-value pairs and use XPath expressions to access it. So, instead doing this:

      <xsl:template name="countries">
        <xsl:param name="country-code" />
        <xsl:choose>
          <xsl:when test="$country-code = 'AF'">Afghanistan</xsl:when>
          <xsl:when test="$country-code = 'AX'">Aland Islands</xsl:when>
          <xsl:when test="$country-code = 'AL'">Albania</xsl:when>
          <!—... -->
        </xsl:choose>
      </xsl:template>

      try this:

      <xsl:variable name="countries">
        <item code="AF">Afghanistan</item>
        <item code="AX">Aland Islands</item>
        <item code="AL">Albania</item>
        <!—... -->
      </xsl:variable>

      <!—... -->
      <!-- get the value –>
      <xsl:value-of select="msxsl:node-set($countries)/item[@code='AX']"/>
    • If you use xsl:choose to do different kinds of processing depending on values of elements/nodes/attributes – move the contents of the xsl:when instructions to separate templates and use xsl:apply-templates to invoke them (or have the default template invoke them). So, instead doing this:

      <xsl:template match="/">
        <xsl:choose>
          <xsl:when test="/root/name">
            <!—... -->
          </xsl:when>
          <xsl:when test="/root/address[@country='US']">
            <!—... -->
          </xsl:when>
          <xsl:when test="/root/address[@country='IE']">
          </xsl:when>
          <!—... -->
        </xsl:choose>
      </xsl:template

      try this:

      <xsl:template match="/root">
        <xsl:apply-templates select="name" />
        <xsl:apply-templates select="address" />
        <!—... -->
      </xsl:template>

      <
      xsl:template match="name">
        <!—... -->
      </xsl:template>

      <xsl:template match="address[@country='US']">
        <!—... -->
      </xsl:template>

      <xsl:template match="address[@country='IE']">
        <!—... -->
      </xsl:template>
  • You have an xsl:template instruction whose match attribute contains a lot | (pipe) separated matches or you have a lot of templates in a given mode. Again “a lot” here depends on the free space on the stack (see notes below for more details) but it usually means at least a thousand on a 64-bit machine with the smallest possible stack size. To fix this try one of the following:
    • Try changing match value to * and use xsl:if with the original match value inside the template to filter nodes you want to process
    • Try moving the template with a lot of | pipe separated matches to a different mode
    • If the above does not help move some (half?) of the templates to a different mode (if you have a template with a lot of | separated matches move the body of the template to a named template and create two or more templates in different modes. Each of these templates should contain some of the matches from the original template and should call the named template you had created. Obviously you need to fix up the places you invoked the original template as well). I agree this just a workaround which is not pretty but having thousands of matches is not common and the compiler was not optimized to handle this kind of situations.
    • See http://social.msdn.microsoft.com/forums/en-US/xmlandnetfx/thread/6edac9a7-0cf0-45a6-b196-c4308b54b937/ for more details and ideas

There are a few additional interesting notes:

  • Whether you see the stack overflow or not depends on two things:
    • How much of the stack is left when you start the compilation. This in turn heavily depends on how big the stack of the current managed thread is. The default size of the stack of a managed thread is 1MB. However managed threads created by IIS have by default stacks of 256KB.
    • How big the stack frames are - stack frames on AMD64 are in general much bigger than stack frames on x86 so nested method calls on AMD64 “eat” the stack much faster than on x86.

The conclusion is that web apps running on 64-bit machines are more prone to this problem than non-web apps or apps running on x86 (In fact I don’t remember this problem being reported for non-web apps).

Other Workarounds

Disclaimer: I believe that most Xslt stylesheets hitting this problem are poorly written. Usually aside from issues with compilation their performance is also bad. I strongly believe that the *right* way of addressing these kinds of issues is to refactor/fix the stylesheet.

In case it is not possible for you to refactor the stylesheet:

  • To avoid this problem happening at runtime where the size of the stack is limited you can use the xsltc (Xslt compiler) to precompile your stylesheet. As a result the compilation is not happening at runtime so you won’t get this exception. A nice side effect of doing this is the performance boost your application will get.
  • You can “fix” the issue by increasing the size of the stack for the thread the stylesheet is being compiled on. But beware. There was a reason why the stack size was limited so increasing it may cause other problems.

Stack overflow thrown when transforming an Xml file.

This one is pretty common and there is one main root cause – your stylesheet is using very deep or infinite recursion. Recursion is the way to emulate “regular” loops (like for, while etc.) in Xslt. Now, if you have a bug in the stylesheet and you never stop the recursion or the recursion is deep you can get the StackOveflowException. It’s not different from other .NET languages like C# or VB where infinite or very deep recursion will result in the same exception. If you have a bug – fix the bug and you should be good to go. If the reason is the deep recursion then it’s a bit more difficult. Obviously you need to change the stylesheet to fix it. There is a couple of things that you can try doing:

  • Avoid the recursion at all. Sometimes people are using recursion because it’s easier or nicer even though there is a viable non-recursive approach. Go for it.
  • Use “divide and conquer” approach (http://en.wikipedia.org/wiki/Divide_and_conquer_algorithm). This will reduce the size of the stack needed to execute the template. For instance if you have a loop that writes numbers from 1 to 1000000 and you use the simple recursion for this then you will need to have space for 1000000 stack frames while with divide-and-conquer you need space for only 14 frames. The following examples show how to rewrite a simple recursion to “divide-and-conquer” recursion: Here is an example of simple recursion:

    <xsl:template name="Count">
      <xsl:param name="current-idx" select="0"/>
      <xsl:param name="stop" />

      <xsl:value-of select="concat($current-idx, '|')"/>

      <xsl:if test="$current-idx &lt; $stop">
        <xsl:call-template name="Count">
          <xsl:with-param name="current-idx" select="$current-idx + 1" />
          <xsl:with-param name="stop" select="$stop" />
        </xsl:call-template>
      </xsl:if>
    </xsl:template>

    and here is the same template rewritten using divide-and-conquer approach:

    <xsl:template name="Count">
      <xsl:param name="current-idx" select="0" />
      <xsl:param name="stop" />

      <xsl:choose>
        <xsl:when test="$current-idx = $stop">
        <xsl:value-of select="concat($current-idx, '|')"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:variable name="split" select="$current-idx + floor(($stop - $current-idx) div 2)" />
          <xsl:call-template name="Count">
            <xsl:with-param name="current-idx" select="$current-idx" />
            <xsl:with-param name="stop" select="$split" />
          </xsl:call-template>
          <xsl:call-template name="Count">
            <xsl:with-param name="current-idx" select="$split + 1" />
            <xsl:with-param name="stop" select="$stop" />
          </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>
  • With the simple recursion I could not count even to 8000 (AMD64 machine, 1MB stack size) before getting StackOverflowException while with the divide-and-conquer version I easily got to 100000000. This does make a difference.

Obviously to work around this problem you can try increasing the stack size for the thread the transformation is running on but again – are you really sure you want to do this?

Pawel Kadluczka