1 //------------------------------------------------------------------------------
2 // <copyright file="LegacyAspNetSynchronizationContext.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
8 using System.ComponentModel;
9 using System.Runtime.ExceptionServices;
10 using System.Security.Permissions;
11 using System.Threading;
13 using System.Web.Util;
15 namespace System.Web {
17 // Represents a SynchronizationContext that has legacy behavior (<= FX 4.0) when it comes to asynchronous operations.
18 // Characterized by locking on the HttpApplication to synchronize work, dispatching Posts as Sends.
20 internal sealed class LegacyAspNetSynchronizationContext : AspNetSynchronizationContextBase {
21 private HttpApplication _application;
22 private Action<bool> _appVerifierCallback;
23 private bool _disabled;
24 private bool _syncCaller;
25 private bool _invalidOperationEncountered;
26 private int _pendingCount;
27 private ExceptionDispatchInfo _error;
28 private WaitCallback _lastCompletionCallback;
29 private object _lastCompletionCallbackLock;
31 internal LegacyAspNetSynchronizationContext(HttpApplication app) {
33 _appVerifierCallback = AppVerifier.GetSyncContextCheckDelegate(app);
34 _lastCompletionCallbackLock = new object();
37 private void CheckForRequestStateIfRequired() {
38 if (_appVerifierCallback != null) {
39 _appVerifierCallback(false);
43 private void CallCallback(SendOrPostCallback callback, Object state) {
44 CheckForRequestStateIfRequired();
46 // don't take app lock for [....] caller to avoid deadlocks in case they poll for result
48 CallCallbackPossiblyUnderLock(callback, state);
52 CallCallbackPossiblyUnderLock(callback, state);
57 private void CallCallbackPossiblyUnderLock(SendOrPostCallback callback, Object state) {
58 ThreadContext threadContext = null;
60 threadContext = _application.OnThreadEnter();
65 _error = ExceptionDispatchInfo.Capture(e);
69 if (threadContext != null) {
70 threadContext.DisassociateFromCurrentThread();
75 // this property no-ops using the legacy [....] context
76 internal override bool AllowAsyncDuringSyncStages {
81 internal override int PendingOperationsCount {
82 get { return _pendingCount; }
85 internal override ExceptionDispatchInfo ExceptionDispatchInfo {
86 get { return _error; }
89 internal override void ClearError() {
93 // Dev11 Bug 70908: Race condition involving SynchronizationContext allows ASP.NET requests to be abandoned in the pipeline
95 // When the last completion occurs, the _pendingCount is decremented and then the _lastCompletionCallbackLock is acquired to get
96 // the _lastCompletionCallback. If the _lastCompletionCallback is non-null, then the last completion will invoke the callback;
97 // otherwise, the caller of PendingCompletion will handle the completion.
98 internal override bool PendingCompletion(WaitCallback callback) {
99 Debug.Assert(_lastCompletionCallback == null); // only one at a time
100 bool pending = false;
101 if (_pendingCount != 0) {
102 lock (_lastCompletionCallbackLock) {
103 if (_pendingCount != 0) {
105 _lastCompletionCallback = callback;
112 public override void Send(SendOrPostCallback callback, Object state) {
114 Debug.Trace("Async", "Send");
115 Debug.Trace("AsyncStack", "Send from:\r\n" + GetDebugStackTrace());
117 CallCallback(callback, state);
120 public override void Post(SendOrPostCallback callback, Object state) {
122 Debug.Trace("Async", "Post");
123 Debug.Trace("AsyncStack", "Post from:\r\n" + GetDebugStackTrace());
125 CallCallback(callback, state);
129 [EnvironmentPermission(SecurityAction.Assert, Unrestricted=true)]
130 private void CreateCopyDumpStack() {
131 Debug.Trace("Async", "CreateCopy");
132 Debug.Trace("AsyncStack", "CreateCopy from:\r\n" + GetDebugStackTrace());
136 public override SynchronizationContext CreateCopy() {
138 CreateCopyDumpStack();
140 LegacyAspNetSynchronizationContext context = new LegacyAspNetSynchronizationContext(_application);
141 context._disabled = _disabled;
142 context._syncCaller = _syncCaller;
143 context.AllowAsyncDuringSyncStages = AllowAsyncDuringSyncStages;
147 public override void OperationStarted() {
148 if (_invalidOperationEncountered || (_disabled && _pendingCount == 0)) {
149 _invalidOperationEncountered = true;
150 throw new InvalidOperationException(SR.GetString(SR.Async_operation_disabled));
153 Interlocked.Increment(ref _pendingCount);
155 Debug.Trace("Async", "OperationStarted(count=" + _pendingCount + ")");
156 Debug.Trace("AsyncStack", "OperationStarted(count=" + _pendingCount + ") from:\r\n" + GetDebugStackTrace());
160 public override void OperationCompleted() {
161 if (_invalidOperationEncountered || (_disabled && _pendingCount == 0)) {
162 // throw from operation started could cause extra operation completed
166 int pendingCount = Interlocked.Decrement(ref _pendingCount);
169 Debug.Trace("Async", "OperationCompleted(pendingCount=" + pendingCount + ")");
170 Debug.Trace("AsyncStack", "OperationCompleted(pendingCount=" + pendingCount + ") from:\r\n" + GetDebugStackTrace());
173 // notify (once) about the last completion to resume the async work
174 if (pendingCount == 0) {
175 WaitCallback callback = null;
176 lock (_lastCompletionCallbackLock) {
177 callback = _lastCompletionCallback;
178 _lastCompletionCallback = null;
181 if (callback != null) {
182 Debug.Trace("Async", "Queueing LastCompletionWorkItemCallback");
183 ThreadPool.QueueUserWorkItem(callback);
188 internal override bool Enabled {
189 get { return !_disabled; }
192 internal override void Enable() {
196 internal override void Disable() {
200 internal override void SetSyncCaller() {
204 internal override void ResetSyncCaller() {
208 internal override void AssociateWithCurrentThread() {
209 Monitor.Enter(_application);
212 internal override void DisassociateFromCurrentThread() {
213 Monitor.Exit(_application);
217 [EnvironmentPermission(SecurityAction.Assert, Unrestricted = true)]
218 private static string GetDebugStackTrace() {
219 return Environment.StackTrace;