Replace whitespaces to 8-spaces tabs
[mono.git] / mcs / class / referencesource / System.Web / LegacyAspNetSynchronizationContext.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="LegacyAspNetSynchronizationContext.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 using System;
8 using System.ComponentModel;
9 using System.Runtime.ExceptionServices;
10 using System.Security.Permissions;
11 using System.Threading;
12 using System.Web;
13 using System.Web.Util;
14
15 namespace System.Web {
16
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.
19
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;
30
31         internal LegacyAspNetSynchronizationContext(HttpApplication app) {
32             _application = app;
33             _appVerifierCallback = AppVerifier.GetSyncContextCheckDelegate(app);
34             _lastCompletionCallbackLock = new object();
35         }
36
37         private void CheckForRequestStateIfRequired() {
38             if (_appVerifierCallback != null) {
39                 _appVerifierCallback(false);
40             }
41         }
42
43         private void CallCallback(SendOrPostCallback callback, Object state) {
44             CheckForRequestStateIfRequired();
45
46             // don't take app lock for [....] caller to avoid deadlocks in case they poll for result
47             if (_syncCaller) {
48                 CallCallbackPossiblyUnderLock(callback, state);
49             }
50             else {
51                 lock (_application) {
52                     CallCallbackPossiblyUnderLock(callback, state);
53                 }
54             }
55         }
56
57         private void CallCallbackPossiblyUnderLock(SendOrPostCallback callback, Object state) {
58             ThreadContext threadContext = null;
59             try {
60                 threadContext = _application.OnThreadEnter();
61                 try {
62                     callback(state);
63                 }
64                 catch (Exception e) {
65                     _error = ExceptionDispatchInfo.Capture(e);
66                 }
67             }
68             finally {
69                 if (threadContext != null) {
70                     threadContext.DisassociateFromCurrentThread();
71                 }
72             }
73         }
74
75         // this property no-ops using the legacy [....] context
76         internal override bool AllowAsyncDuringSyncStages {
77             get;
78             set;
79         }
80
81         internal override int PendingOperationsCount {
82             get { return _pendingCount; }
83         }
84
85         internal override ExceptionDispatchInfo ExceptionDispatchInfo {
86             get { return _error; }
87         }
88
89         internal override void ClearError() {
90             _error = null;
91         }
92
93         // Dev11 Bug 70908: Race condition involving SynchronizationContext allows ASP.NET requests to be abandoned in the pipeline
94         //  
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) {
104                         pending = true;
105                         _lastCompletionCallback = callback;
106                     }
107                 }
108             }
109             return pending;            
110         }
111
112         public override void Send(SendOrPostCallback callback, Object state) {
113 #if DBG
114             Debug.Trace("Async", "Send");
115             Debug.Trace("AsyncStack", "Send from:\r\n" + GetDebugStackTrace());
116 #endif
117             CallCallback(callback, state);
118         }
119
120         public override void Post(SendOrPostCallback callback, Object state) {
121 #if DBG
122             Debug.Trace("Async", "Post");
123             Debug.Trace("AsyncStack", "Post from:\r\n" + GetDebugStackTrace());
124 #endif
125             CallCallback(callback, state);
126         }
127
128 #if DBG
129         [EnvironmentPermission(SecurityAction.Assert, Unrestricted=true)]
130         private void CreateCopyDumpStack() {
131             Debug.Trace("Async", "CreateCopy");
132             Debug.Trace("AsyncStack", "CreateCopy from:\r\n" + GetDebugStackTrace());
133         }
134 #endif
135
136         public override SynchronizationContext CreateCopy() {
137 #if DBG
138             CreateCopyDumpStack();
139 #endif
140             LegacyAspNetSynchronizationContext context = new LegacyAspNetSynchronizationContext(_application);
141             context._disabled = _disabled;
142             context._syncCaller = _syncCaller;
143             context.AllowAsyncDuringSyncStages = AllowAsyncDuringSyncStages;
144             return context;
145         }
146
147         public override void OperationStarted() {
148             if (_invalidOperationEncountered || (_disabled && _pendingCount == 0)) {
149                 _invalidOperationEncountered = true;
150                 throw new InvalidOperationException(SR.GetString(SR.Async_operation_disabled));
151             }
152
153             Interlocked.Increment(ref _pendingCount);
154 #if DBG
155             Debug.Trace("Async", "OperationStarted(count=" + _pendingCount + ")");
156             Debug.Trace("AsyncStack", "OperationStarted(count=" + _pendingCount + ") from:\r\n" + GetDebugStackTrace());
157 #endif
158         }
159
160         public override void OperationCompleted() {
161             if (_invalidOperationEncountered || (_disabled && _pendingCount == 0)) {
162                 // throw from operation started could cause extra operation completed
163                 return;
164             }
165
166             int pendingCount = Interlocked.Decrement(ref _pendingCount);
167
168 #if DBG
169             Debug.Trace("Async", "OperationCompleted(pendingCount=" + pendingCount + ")");
170             Debug.Trace("AsyncStack", "OperationCompleted(pendingCount=" + pendingCount + ") from:\r\n" + GetDebugStackTrace());
171 #endif
172
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;
179                 }
180
181                 if (callback != null) {
182                     Debug.Trace("Async", "Queueing LastCompletionWorkItemCallback");
183                     ThreadPool.QueueUserWorkItem(callback);
184                 }
185             }
186         }
187
188         internal override bool Enabled {
189             get { return !_disabled; }
190         }
191
192         internal override void Enable() {
193             _disabled = false;
194         }
195
196         internal override void Disable() {
197             _disabled = true;
198         }
199
200         internal override void SetSyncCaller() {
201             _syncCaller = true;
202         }
203
204         internal override void ResetSyncCaller() {
205             _syncCaller = false;
206         }
207
208         internal override void AssociateWithCurrentThread() {
209             Monitor.Enter(_application);
210         }
211
212         internal override void DisassociateFromCurrentThread() {
213             Monitor.Exit(_application);
214         }
215
216 #if DBG
217         [EnvironmentPermission(SecurityAction.Assert, Unrestricted = true)]
218         private static string GetDebugStackTrace() {
219             return Environment.StackTrace;
220         }
221 #endif
222     }
223 }