Intermittent hangs in an ASP.NET application when serving asmx files via HTTP GET
I was recently debugging an intermittent, brief hang (5-15 seconds) in an ASP.NET application that would occur once every few days. The application was fully precompiled (non-updatable) and running .NET 4.0.
When the hang occurred, most threads would be sitting in a stack similar to this: (method signatures slightly modified for visibility reasons)
System.Web.Compilation.BuildManager.GetBuildResultFromCacheInternal
System.Web.Compilation.BuildManager.GetVPathBuildResultFromCacheInternal
System.Web.Compilation.BuildManager.GetVPathBuildResultInternal
System.Web.Compilation.BuildManager.GetVPathBuildResultWithNoAssert
System.Web.Compilation.BuildManager.GetVirtualPathObjectFactory
System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath
System.Web.UI.PageHandlerFactory.GetHandlerHelper
<snip>
With one thread in a stack like this:
System.Threading.WaitHandle.InternalWaitOne
System.CodeDom.Compiler.Executor.ExecWaitWithCaptureUnimpersonated
System.CodeDom.Compiler.Executor.ExecWaitWithCapture
Microsoft.CSharp.CSharpCodeGenerator.Compile
Microsoft.CSharp.CSharpCodeGenerator.FromFileBatch
Microsoft.CSharp.CSharpCodeGenerator.CompileAssemblyFromFileBatch
System.Web.Compilation.AssemblyBuilder.Compile
System.Web.Compilation.BuildProvidersCompiler.PerformBuild
System.Web.Compilation.BuildManager.CompileWebFile
System.Web.Compilation.BuildManager.GetVPathBuildResultInternal
System.Web.Compilation.BuildManager.GetVPathBuildResultWithNoAssert
System.Web.Compilation.BuildManager.GetVPathBuildResult
System.Web.UI.PageParser.GetCompiledPageInstance
System.Web.UI.PageParser.GetCompiledPageInstance
System.Web.Services.Protocols.DocumentationServerProtocol.GetCompiledPageInstance
System.Web.Services.Protocols.DocumentationServerProtocol.Initialize
System.Web.Services.Protocols.ServerProtocolFactory.Create
System.Web.Services.Protocols.WebServiceHandlerFactory.CoreGetHandler
System.Web.Services.Protocols.WebServiceHandlerFactory.GetHandler
System.Web.Script.Services.ScriptHandlerFactory.GetHandler
<snip>
(note, I didn't have parameters to these stack traces, had I, the diagnostic would have been much simpler in hindsight)
Every time this happened it'd be an ASMX file triggering the compile, never any other type. I was pretty perplexed by this because:
- The application is fully precompiled, so ASP.NET should never attempt to compile anything.
- There's not really anything to compile in our ASMX files (they're just pointers to a class in a DLL), so even if ASP.NET did try to compile it, it shouldn't take any time.
After a few days of continuing to be perplexed, I had a breakthrough.
In ASP.NET, if you just hit an ASMX file via a browser, you get a pretty web page that lists all the methods on the service and allows you to invoke methods through the browser. This page is actually an ASPX file (named DefaultWsdlHelpGenerator.aspx) that lives in the Config directory of whatever framework you're running (in my case it was C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config, ymmv). Bingo, that's what's getting compiled!
Once I figured this out, the solution was fairly simple. ASP.NET allows you to specify the WSDL help generator file via system.web/webServices/wsdlHelpGenerator in your web.config. I simply copied DefaultWsdlHelpGenerator.aspx into my application's root directory and added the wsdlHelpGenerator element to my web.config. Now, when my application needs to generate a WSDL file or page, ASP.NET uses the precompiled help generator instead of the default one and doesn't trigger a compile. Problem solved!
The moral of the story here: if you have a precompiled site with asmx services and care about short hangs, do what I outlined above. If you don't precompile, this is the least of your worries.