// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Threading; using System.Web.Http.SelfHost.Properties; namespace System.Web.Http.SelfHost.ServiceModel.Channels { // AsyncResult starts acquired; Complete releases. [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Ported from WCF")] internal abstract class AsyncResult : IAsyncResult { private static AsyncCallback _asyncCompletionWrapperCallback; private readonly AsyncCallback _completionCallback; private bool _completedSynchronously; private bool _endCalled; private Exception _exception; private bool _isCompleted; private AsyncCompletion _nextAsyncCompletion; private readonly object _state; private Action _beforePrepareAsyncCompletionAction; private Func _checkSyncValidationFunc; private ManualResetEvent _manualResetEvent; private readonly object _manualResetEventLock = new object(); protected AsyncResult(AsyncCallback callback, object state) { _completionCallback = callback; _state = state; } /// /// Can be utilized by subclasses to write core completion code for both the sync and async paths /// in one location, signalling chainable synchronous completion with the boolean result, /// and leveraging PrepareAsyncCompletion for conversion to an AsyncCallback. /// /// NOTE: requires that "this" is passed in as the state object to the asynchronous sub-call being used with a completion routine. protected delegate bool AsyncCompletion(IAsyncResult result); public object AsyncState { get { return _state; } } public WaitHandle AsyncWaitHandle { get { lock (_manualResetEventLock) { if (_manualResetEvent == null) { _manualResetEvent = new ManualResetEvent(_isCompleted); } } return _manualResetEvent; } } public bool CompletedSynchronously { get { return _completedSynchronously; } } public bool HasCallback { get { return _completionCallback != null; } } public bool IsCompleted { get { return _isCompleted; } } // used in conjunction with PrepareAsyncCompletion to allow for finally blocks protected Action OnCompleting { get; set; } // subclasses like TraceAsyncResult can use this to wrap the callback functionality in a scope protected Action VirtualCallback { get; set; } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is propagated or FailFast")] protected void Complete(bool didCompleteSynchronously) { if (_isCompleted) { throw Error.InvalidOperation(SRResources.AsyncResultCompletedTwice, GetType()); } _completedSynchronously = didCompleteSynchronously; if (OnCompleting != null) { // Allow exception replacement, like a catch/throw pattern. try { OnCompleting(this, _exception); } catch (Exception e) { _exception = e; } } if (didCompleteSynchronously) { // If we completedSynchronously, then there's no chance that the manualResetEvent was created so // we don't need to worry about a race Debug.Assert(_manualResetEvent == null, "No ManualResetEvent should be created for a synchronous AsyncResult."); _isCompleted = true; } else { lock (_manualResetEventLock) { _isCompleted = true; if (_manualResetEvent != null) { _manualResetEvent.Set(); } } } if (_completionCallback != null) { try { if (VirtualCallback != null) { VirtualCallback(_completionCallback, this); } else { _completionCallback(this); } } #pragma warning disable 1634 #pragma warning suppress 56500 // transferring exception to another thread catch (Exception e) { // String is not resourced because it occurs only in debug build, only with a fatal assert, // and because it appears as an unused resource in a release build Contract.Assert(false, Error.Format("{0}{1}{2}", "Async Callback threw an exception.", Environment.NewLine, e.ToString())); } #pragma warning restore 1634 } } protected void Complete(bool didCompleteSynchronously, Exception error) { _exception = error; Complete(didCompleteSynchronously); } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is propagated")] private static void AsyncCompletionWrapperCallback(IAsyncResult result) { if (result == null) { throw Error.InvalidOperation(SRResources.InvalidNullAsyncResult); } if (result.CompletedSynchronously) { return; } AsyncResult thisPtr = (AsyncResult)result.AsyncState; if (!thisPtr.OnContinueAsyncCompletion(result)) { return; } AsyncCompletion callback = thisPtr.GetNextCompletion(); if (callback == null) { ThrowInvalidAsyncResult(result); } bool completeSelf = false; Exception completionException = null; try { completeSelf = callback(result); } catch (Exception e) { completeSelf = true; completionException = e; } if (completeSelf) { thisPtr.Complete(false, completionException); } } // Note: this should be only derived by the TransactedAsyncResult protected virtual bool OnContinueAsyncCompletion(IAsyncResult result) { return true; } // Note: this should be used only by the TransactedAsyncResult protected void SetBeforePrepareAsyncCompletionAction(Action completionAction) { _beforePrepareAsyncCompletionAction = completionAction; } // Note: this should be used only by the TransactedAsyncResult protected void SetCheckSyncValidationFunc(Func validationFunc) { _checkSyncValidationFunc = validationFunc; } protected AsyncCallback PrepareAsyncCompletion(AsyncCompletion callback) { if (_beforePrepareAsyncCompletionAction != null) { _beforePrepareAsyncCompletionAction(); } _nextAsyncCompletion = callback; if (AsyncResult._asyncCompletionWrapperCallback == null) { AsyncResult._asyncCompletionWrapperCallback = new AsyncCallback(AsyncCompletionWrapperCallback); } return AsyncResult._asyncCompletionWrapperCallback; } protected bool CheckSyncContinue(IAsyncResult result) { AsyncCompletion dummy; return TryContinueHelper(result, out dummy); } protected bool SyncContinue(IAsyncResult result) { AsyncCompletion callback; if (TryContinueHelper(result, out callback)) { return callback(result); } else { return false; } } private bool TryContinueHelper(IAsyncResult result, out AsyncCompletion callback) { if (result == null) { throw Error.InvalidOperation(SRResources.InvalidNullAsyncResult); } callback = null; if (_checkSyncValidationFunc != null) { if (!_checkSyncValidationFunc(result)) { return false; } } else if (!result.CompletedSynchronously) { return false; } callback = GetNextCompletion(); if (callback == null) { ThrowInvalidAsyncResult("Only call Check/SyncContinue once per async operation (once per PrepareAsyncCompletion)."); } return true; } private AsyncCompletion GetNextCompletion() { AsyncCompletion result = _nextAsyncCompletion; _nextAsyncCompletion = null; return result; } protected static void ThrowInvalidAsyncResult(IAsyncResult result) { if (result == null) { throw Error.ArgumentNull("result"); } throw Error.InvalidOperation(SRResources.InvalidAsyncResultImplementation, result.GetType()); } protected static void ThrowInvalidAsyncResult(string debugText) { string message = SRResources.InvalidAsyncResultImplementationGeneric; if (debugText != null) { #if DEBUG message += " " + debugText; #endif } throw Error.InvalidOperation(message); } [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Existing API")] protected static TAsyncResult End(IAsyncResult result) where TAsyncResult : AsyncResult { if (result == null) { throw Error.ArgumentNull("result"); } TAsyncResult asyncResult = result as TAsyncResult; if (asyncResult == null) { throw Error.Argument("result", SRResources.InvalidAsyncResult); } if (asyncResult._endCalled) { throw Error.InvalidOperation(SRResources.AsyncResultAlreadyEnded); } asyncResult._endCalled = true; if (!asyncResult._isCompleted) { asyncResult.AsyncWaitHandle.WaitOne(); } if (asyncResult._manualResetEvent != null) { asyncResult._manualResetEvent.Close(); } if (asyncResult._exception != null) { throw asyncResult._exception; } return asyncResult; } } }