internal LegacyAspNetSynchronizationContext(HttpApplication app)
 {
     _application                = app;
     _appVerifierCallback        = AppVerifier.GetSyncContextCheckDelegate(app);
     _lastCompletionCallbackLock = new object();
 }
Exemplo n.º 2
0
        internal static int ProcessRequestNotificationHelper(
            IntPtr rootedObjectsPointer,
            IntPtr nativeRequestContext,
            IntPtr moduleData,
            int flags)
        {
            IIS7WorkerRequest         wr      = null;
            HttpContext               context = null;
            RequestNotificationStatus status  = RequestNotificationStatus.Continue;
            RootedObjects             root;
            bool workerRequestWasJustCreated = false;

            if (rootedObjectsPointer == IntPtr.Zero)
            {
                InitializeRequestContext(nativeRequestContext, flags, out wr, out context);
                workerRequestWasJustCreated = true;
                if (context == null)
                {
                    return((int)RequestNotificationStatus.FinishRequest);
                }

                root               = RootedObjects.Create();
                root.HttpContext   = context;
                root.WorkerRequest = wr;
                root.WriteTransferEventIfNecessary();
                context.RootedObjects = root;

                IIS.MgdSetManagedHttpContext(nativeRequestContext, root.Pointer);
            }
            else
            {
                root    = RootedObjects.FromPointer(rootedObjectsPointer);
                context = root.HttpContext;
                wr      = root.WorkerRequest as IIS7WorkerRequest;
            }

            Debug.Assert(root != null, "We should have a RootedObjects instance by this point.");
            Debug.Assert(wr != null, "We should have an IIS7WorkerRequest instance by this point.");

            using (root.WithinTraceBlock()) {
                if (workerRequestWasJustCreated)
                {
                    AspNetEventSource.Instance.RequestStarted(wr);
                }

                int  currentModuleIndex;
                bool isPostNotification;
                int  currentNotification;
                IIS.MgdGetCurrentNotificationInfo(nativeRequestContext, out currentModuleIndex, out isPostNotification, out currentNotification);

                // If the HttpContext is null at this point, then we've already transitioned this request to a WebSockets request.
                // The WebSockets module should already be running, and asynchronous module-level events (like SendResponse) are
                // ineligible to be hooked by managed code.
                if (context == null || context.HasWebSocketRequestTransitionStarted)
                {
                    return((int)RequestNotificationStatus.Continue);
                }

                // It is possible for a notification to complete asynchronously while we're in
                // a call to IndicateCompletion, in which case a new IIS thread might enter before
                // the call to IndicateCompletion returns.  If this happens, block the thread until
                // IndicateCompletion returns.  But never block a SendResponse notification, because
                // that can cause the request to hang (DevDiv Bugs 187441).
                if (context.InIndicateCompletion &&
                    context.ThreadInsideIndicateCompletion != Thread.CurrentThread &&
                    RequestNotification.SendResponse != (RequestNotification)currentNotification)
                {
                    while (context.InIndicateCompletion)
                    {
                        Thread.Sleep(10);
                    }
                }

                // RQ_SEND_RESPONSE fires out of band and completes synchronously only.
                // The pipeline must be reentrant to support this, so the notification
                // context for the previous notification must be saved and restored.
                NotificationContext savedNotificationContext = context.NotificationContext;
                bool cancellable = context.IsInCancellablePeriod;
                bool locked      = false;
                try {
                    if (cancellable)
                    {
                        context.EndCancellablePeriod();
                    }
                    bool isReEntry = (savedNotificationContext != null);
                    if (isReEntry)
                    {
                        context.ApplicationInstance.AcquireNotifcationContextLock(ref locked);
                    }
                    context.NotificationContext = new NotificationContext(flags /*CurrentNotificationFlags*/,
                                                                          isReEntry);

                    Action <RequestNotificationStatus> verifierCheck = null;
                    if (AppVerifier.IsAppVerifierEnabled)
                    {
                        verifierCheck = AppVerifier.GetRequestNotificationStatusCheckDelegate(context, (RequestNotification)currentNotification, isPostNotification);
                    }

                    status = HttpRuntime.ProcessRequestNotification(wr, context);

                    if (verifierCheck != null)
                    {
                        AppVerifier.InvokeVerifierCheck(verifierCheck, status);
                    }
                }
                finally {
                    if (status != RequestNotificationStatus.Pending)
                    {
                        // if we completed the notification, pop the notification context stack
                        // if this is an asynchronous unwind, then the completion will clear the context
                        context.NotificationContext = savedNotificationContext;

                        // DevDiv 112755 restore cancellable state if its changed
                        if (cancellable && !context.IsInCancellablePeriod)
                        {
                            context.BeginCancellablePeriod();
                        }
                        else if (!cancellable && context.IsInCancellablePeriod)
                        {
                            context.EndCancellablePeriod();
                        }
                    }
                    if (locked)
                    {
                        context.ApplicationInstance.ReleaseNotifcationContextLock();
                    }
                }

                if (status != RequestNotificationStatus.Pending)
                {
                    // The current notification may have changed due to the HttpApplication progressing the IIS state machine, so retrieve the info again.
                    IIS.MgdGetCurrentNotificationInfo(nativeRequestContext, out currentModuleIndex, out isPostNotification, out currentNotification);

                    // WOS 1785741: (Perf) In profiles, 8% of HelloWorld is transitioning from native to managed.
                    // The fix is to keep managed code on the stack so that the AppDomain context remains on the
                    // thread, and we can re-enter managed code without setting up the AppDomain context.
                    // If this optimization is possible, MgdIndicateCompletion will execute one or more notifications
                    // and return PENDING as the status.
                    ThreadContext threadContext = context.IndicateCompletionContext;
                    // DevDiv 482614:
                    // Don't use local copy to detect if we can call MgdIndicateCompletion because another thread
                    // unwinding from MgdIndicateCompletion may be changing context.IndicateCompletionContext at the same time.
                    if (!context.InIndicateCompletion && context.IndicateCompletionContext != null)
                    {
                        if (status == RequestNotificationStatus.Continue)
                        {
                            try {
                                context.InIndicateCompletion = true;
                                Interlocked.Increment(ref _inIndicateCompletionCount);
                                context.ThreadInsideIndicateCompletion = Thread.CurrentThread;
                                IIS.MgdIndicateCompletion(nativeRequestContext, ref status);
                            }
                            finally {
                                context.ThreadInsideIndicateCompletion = null;
                                Interlocked.Decrement(ref _inIndicateCompletionCount);

                                // Leave will have been called already if the last notification is returning pending
                                // DTS267762: Make sure InIndicateCompletion is released, not based on the thread context state
                                // Otherwise the next request notification may deadlock
                                if (!threadContext.HasBeenDisassociatedFromThread || context.InIndicateCompletion)
                                {
                                    lock (threadContext) {
                                        if (!threadContext.HasBeenDisassociatedFromThread)
                                        {
                                            threadContext.DisassociateFromCurrentThread();
                                        }

                                        context.IndicateCompletionContext = null;
                                        context.InIndicateCompletion      = false;
                                    }
                                }
                            }
                        }
                        else
                        {
                            if (!threadContext.HasBeenDisassociatedFromThread || context.InIndicateCompletion)
                            {
                                lock (threadContext) {
                                    if (!threadContext.HasBeenDisassociatedFromThread)
                                    {
                                        threadContext.DisassociateFromCurrentThread();
                                    }

                                    context.IndicateCompletionContext = null;
                                    context.InIndicateCompletion      = false;
                                }
                            }
                        }
                    }
                }

                if (context.HasWebSocketRequestTransitionStarted && status == RequestNotificationStatus.Pending)
                {
                    // At this point, the WebSocket module event (PostEndRequest) has executed and set up the appropriate contexts for us.
                    // However, there is a race condition that we need to avoid. It is possible that one thread has kicked off some async
                    // work, e.g. via an IHttpAsyncHandler, and that thread is unwinding and has reached this line of execution.
                    // Meanwhile, the IHttpAsyncHandler completed quickly (but asynchronously) and invoked MgdPostCompletion, which
                    // resulted in a new thread calling ProcessRequestNotification. If this second thread starts the WebSocket transition,
                    // then there's the risk that *both* threads might attempt to call WebSocketPipeline.ProcessRequest, which could AV
                    // the process.
                    //
                    // We protect against this by allowing only the thread which started the transition to complete the transition, so in
                    // the above scenario the original thread (which invoked the IHttpAsyncHandler) no-ops at this point and just returns
                    // Pending to its caller.

                    if (context.DidCurrentThreadStartWebSocketTransition)
                    {
                        // We'll mark the HttpContext as complete, call the continuation to kick off the socket send / receive loop, and return
                        // Pending to IIS so that it doesn't advance the state machine until the WebSocket loop completes.
                        root.ReleaseHttpContext();
                        root.WebSocketPipeline.ProcessRequest();
                    }
                }

                return((int)status);
            }
        }