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