IIS 6 Crashing With Some Asynchronous Handlers

When an ASP.NET application uses Asynchronous Handlers with IIS 6 the worker process (w3wp.exe) may crash or exit. This occurs if the Asynchronous Handler completes the request synchronously without setting IAsync.CompletedSynchronously accordingly.

When an HttpApplication processes a client request it uses Execution Steps (IExecutionStep). An array of execution steps is constructed to represent the Http Pipeline. Each Execution Step defines an Execute() method that gets called to process its part of the pipeline. For example, calls to BeginRequest, AuthenticateRequest, AuthorizeRequest, BeginProcessRequest, EndProcessRequest, ect are built and wrapped as steps and then executed by the HttpApplication. Once all steps have completed execution the response is sent back to IIS.

The Handler in Figure 1 sets its IAsyncResult to complete inside the BeginProcessRequest. Figure 2 is the code for the IAsyncResult object and a typical implementation will execute the AsyncCallback method when the operation is complete. This callback is to the CallHandlerExecutionStep class’s method OnAsyncHandlerCompletion. This method first checks the IAsyncResult’s CompletedSynchronously property and if true just exits which allows the current thread to complete and sends the response to the client. Otherwise it calls the Async Handler’s EndProcessRequest method and releases its reference to the handler.

 

namespace MyAsyncHandler { public class BadAsyncHandler : IHttpAsyncHandler { public IAsyncResult BeginProcessRequest( HttpContext context, AsyncCallback cb, Object extraData) { // Create an async object to return to caller Async async = new Async(cb, extraData); //Typically here we spin up another thread queue a worker item to do the work //but in this case we are able to complete the request without doing so. async.SetCompleted(); return async; }

Figure 1

 

class Async : IAsyncResult { private AsyncCallback _cb = null; private Object _asyncState; private Boolean _isCompleted; internal Async(AsyncCallback cb, Object extraData) { _cb = cb; _asyncState = extraData; _isCompleted = false; } internal void SetCompleted() { _isCompleted = true; if(_cb != null) { _cb(this); } }

  public bool CompletedSynchronously { get { return false; } } public bool IsCompleted { get { return _isCompleted; } }}

Figure 2

If the current thread is a Thread Pool Thread (Thread.CurrentThread.IsThreadPoolThread) the HttpApplication will continue processing the remaining steps and send the response to IIS and then back to the client via a call to ISAPIWorkerRequest.EndOfRequest().

Void System.Web.HttpApplication/CallHandlerExecutionStep.OnAsyncHandlerCompletion(Class System.IAsyncResult) Void MyAsyncHandler.Async.SetCompleted() Class System.IAsyncResult MyAsyncHandler.BadAsyncHandler.BeginProcessRequest(Class System.Web.HttpContext,Class System.AsyncCallback,Object) Void System.Web.HttpApplication/CallHandlerExecutionStep.System.Web.HttpApplication+IExecutionStep.Execute() Class System.Exception System.Web.HttpApplication.ExecuteStep(Class IExecutionStep,ByRef Boolean) Void System.Web.HttpApplication.ResumeSteps(Class System.Exception) Class System.IAsyncResult System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(Class System.Web.HttpContext,Class System.AsyncCallback,Object) Void System.Web.HttpRuntime.ProcessRequestInternal(Class System.Web.HttpWorkerRequest) Void System.Web.HttpRuntime.ProcessRequestNow(Class System.Web.HttpWorkerRequest) Void System.Web.HttpRuntime.ProcessRequest(Class System.Web.HttpWorkerRequest) I4 System.Web.Hosting.ISAPIRuntime.ProcessRequest(I,I4)

Figure 3

When the stack unwinds back down to where the steps are being executed things start to go bad. Because the CompletedSynchronously of the AsynResult object was not set the HttpApplication halts its execution of the steps as the steps should be completed when the OnAsyncHandlerCompletion callback is invoked. At this point the HttpApplication breaks out of the step execution and attempts to return the thread to its proper culture and security context however the HttpContext has already been released due to the request being previously completed which causes a null reference exception to be thrown. The exception is caught and we attempt to finish the request with an error and we attempt to either build an error page or use a custom error page if one exists. The ISAPIWorkerRequest.EndOfRequest() method is now called which ends up trying to send the Http Error 500 using an ISAPI context/Request that has already sent and causes IIS (w3wp) to AV/crash/exit.