Merge pull request #3962 from mkorkalo/fix_MonoBtlsContext_memory_leak
[mono.git] / mcs / class / referencesource / System.Web / AspNetSynchronizationContext.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="AspNetSynchronizationContext.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>                                                                
5 //------------------------------------------------------------------------------
6
7 namespace System.Web {
8     using System;
9     using System.Collections.Generic;
10     using System.Diagnostics.CodeAnalysis;
11     using System.Runtime.ExceptionServices;
12     using System.Threading;
13     using System.Threading.Tasks;
14     using System.Web.Util;
15
16     internal sealed class AspNetSynchronizationContext : AspNetSynchronizationContextBase {
17
18         // we move all of the state to a separate field since our CreateCopy() method needs shallow copy semantics
19         private readonly State _state;
20
21         internal AspNetSynchronizationContext(ISyncContext syncContext)
22             : this(new State(new SynchronizationHelper(syncContext))) {
23         }
24
25         private AspNetSynchronizationContext(State state) {
26             _state = state;
27         }
28
29         internal override bool AllowAsyncDuringSyncStages {
30             get {
31                 return _state.AllowAsyncDuringSyncStages;
32             }
33             set {
34                 _state.AllowAsyncDuringSyncStages = value;
35             }
36         }
37
38         // We can't ever truly disable the AspNetSynchronizationContext, as the user and runtime can kick off asynchronous
39         // operations whether we wanted them to or not. But this property can be used as a flag by Page and other types
40         // to signal that asynchronous operations are not currently valid, so at least ASP.NET can avoid kicking them
41         // off and can bubble an appropriate exception back to the developer.
42         internal override bool Enabled {
43             get { return _state.Enabled; }
44         }
45
46         internal override ExceptionDispatchInfo ExceptionDispatchInfo {
47             get { return _state.Helper.Error; }
48         }
49
50         internal override int PendingOperationsCount {
51             get { return _state.Helper.PendingCount; }
52         }
53
54         internal override void AllowVoidAsyncOperations() {
55             _state.AllowVoidAsyncOperations = true;
56         }
57
58         [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", Justification = "Used only during debug.")]
59         internal override void AssociateWithCurrentThread() {
60             IDisposable disassociationAction = _state.Helper.EnterSynchronousControl();
61
62 #if DBG
63             IDisposable capturedDisassociationAction = disassociationAction;
64             Thread capturedThread = Thread.CurrentThread;
65             disassociationAction = new DisposableAction(() => {
66                 Debug.Assert(capturedThread == Thread.CurrentThread, String.Format("AssociateWithCurrentThread was called on thread ID '{0}', but DisassociateFromCurrentThread was called on thread ID '{1}'.", capturedThread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId));
67                 capturedDisassociationAction.Dispose();
68             });
69 #endif
70
71             // Don't need to synchronize access to SyncControlDisassociationActions since only one thread can call
72             // EnterSynchronousControl() at a time.
73             _state.SyncControlDisassociationActions.Push(disassociationAction);
74         }
75
76         internal override void ClearError() {
77             _state.Helper.Error = null;
78         }
79
80         // Called by the BCL when it needs a SynchronizationContext that is identical to the existing context
81         // but does not have referential equality.
82         public override SynchronizationContext CreateCopy() {
83             return new AspNetSynchronizationContext(_state);
84         }
85
86         internal override void Disable() {
87             _state.Enabled = false;
88         }
89
90         internal override void DisassociateFromCurrentThread() {
91             // Don't need to synchronize access to SyncControlDisassociationActions since we assume that our callers are 
92             // well-behaved and won't call DisassociateFromCurrentThread() on a thread other than the one which called
93             // AssociateWithCurrentThread(), which itself serializes access.
94             Debug.Assert(_state.SyncControlDisassociationActions.Count > 0, "DisassociateFromCurrentThread() was called on a thread which hadn't previously called AssociateWithCurrentThread().");
95             IDisposable disassociationAction = _state.SyncControlDisassociationActions.Pop();
96             disassociationAction.Dispose();
97         }
98
99         internal override void Enable() {
100             _state.Enabled = true;
101         }
102
103         public override void OperationCompleted() {
104             Interlocked.Decrement(ref _state.VoidAsyncOutstandingOperationCount); // this line goes first since ChangeOperationCount might invoke a callback which depends on this value
105             _state.Helper.ChangeOperationCount(-1);
106         }
107
108         public override void OperationStarted() {
109             // If the caller tries to kick off an asynchronous operation while we are not
110             // processing an async module, handler, or Page, we should prohibit the operation.
111             if (!AllowAsyncDuringSyncStages && !_state.AllowVoidAsyncOperations) {
112                 InvalidOperationException ex = new InvalidOperationException(SR.GetString(SR.Async_operation_cannot_be_started));
113                 throw ex;
114             }
115
116             _state.Helper.ChangeOperationCount(+1);
117             Interlocked.Increment(ref _state.VoidAsyncOutstandingOperationCount);
118         }
119
120         // Dev11 Bug 70908: Race condition involving SynchronizationContext allows ASP.NET requests to be abandoned in the pipeline
121         //  
122         // When the last completion occurs, the _pendingCount is decremented and then the _lastCompletionCallbackLock is acquired to get
123         // the _lastCompletionCallback.  If the _lastCompletionCallback is non-null, then the last completion will invoke the callback;
124         // otherwise, the caller of PendingCompletion will handle the completion.
125         internal override bool PendingCompletion(WaitCallback callback) {
126             return _state.Helper.TrySetCompletionContinuation(() => callback(null));
127         }
128
129         public override void Post(SendOrPostCallback callback, Object state) {
130             _state.Helper.QueueAsynchronous(() => callback(state));
131         }
132
133         // The method is used to post async func.
134         internal void PostAsync(Func<object, Task> callback, Object state) {
135             _state.Helper.QueueAsynchronousAsync(callback, state);
136         }
137
138         internal override void ProhibitVoidAsyncOperations() {
139             _state.AllowVoidAsyncOperations = false;
140
141             // If the caller tries to prohibit async operations while there are still some
142             // outstanding, we should treat this as an error condition. We can't throw from
143             // this method since (a) the caller generally isn't prepared for it and (b) we
144             // need to wait for the outstanding operations to finish anyway, so we instead
145             // need to mark the helper as faulted.
146             // 
147             // There is technically a race condition here: the caller isn't guaranteed to
148             // observe the error if the operation counter hits zero at just the right time.
149             // But it's actually not terrible if that happens, since the error is really
150             // just meant to be used for diagnostic purposes.
151             if (!AllowAsyncDuringSyncStages && Volatile.Read(ref _state.VoidAsyncOutstandingOperationCount) > 0) {
152                 InvalidOperationException ex = new InvalidOperationException(SR.GetString(SR.Async_operation_cannot_be_pending));
153                 _state.Helper.Error = ExceptionDispatchInfo.Capture(ex);
154             }
155         }
156
157         internal override void ResetSyncCaller() {
158             // no-op
159             // this type doesn't special-case asynchronous work kicked off from a synchronous handler
160         }
161
162         internal override void SetSyncCaller() {
163             // no-op
164             // this type doesn't special-case asynchronous work kicked off from a synchronous handler
165         }
166
167         public override void Send(SendOrPostCallback callback, Object state) {
168             _state.Helper.QueueSynchronous(() => callback(state));
169         }
170
171         private sealed class State {
172             internal bool AllowAsyncDuringSyncStages = AppSettings.AllowAsyncDuringSyncStages;
173             internal volatile bool AllowVoidAsyncOperations = false;
174             internal bool Enabled = true;
175             internal readonly SynchronizationHelper Helper; // handles scheduling of the asynchronous tasks
176             internal Stack<IDisposable> SyncControlDisassociationActions = new Stack<IDisposable>(capacity: 1);
177             internal int VoidAsyncOutstandingOperationCount = 0;
178
179             internal State(SynchronizationHelper helper) {
180                 Helper = helper;
181             }
182         }
183
184     }
185 }