File: src\libraries\Common\src\System\Net\LazyAsyncResult.cs
Web Access
Project: src\src\libraries\System.Net.Requests\src\System.Net.Requests.csproj (System.Net.Requests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
 
namespace System.Net
{
    // LazyAsyncResult - Base class for all IAsyncResult classes that want to take advantage of
    // lazily-allocated event handles.
    internal class LazyAsyncResult : IAsyncResult
    {
        private const int HighBit = unchecked((int)0x80000000);
        private const int ForceAsyncCount = 50;
 
        // This is to avoid user mistakes when they queue another async op from a callback the completes sync.
        [ThreadStatic]
        private static ThreadContext? t_threadContext;
 
        private static ThreadContext CurrentThreadContext
        {
            get
            {
                ThreadContext? threadContext = t_threadContext;
                if (threadContext == null)
                {
                    threadContext = new ThreadContext();
                    t_threadContext = threadContext;
                }
 
                return threadContext;
            }
        }
 
        private sealed class ThreadContext
        {
            internal int _nestedIOCount;
        }
 
#if DEBUG
        private bool _protectState;                 // Used by ContextAwareResult to prevent some calls.
#endif
 
        private readonly object? _asyncObject;              // Caller's async object.
        private readonly object? _asyncState;               // Caller's state object.
        private AsyncCallback? _asyncCallback;     // Caller's callback method.
        private object? _result;                   // Final IO result to be returned byt the End*() method.
        private int _errorCode;                    // Win32 error code for Win32 IO async calls (that want to throw).
        private int _intCompleted;                 // Sign bit indicates synchronous completion if set.
                                                   // Remaining bits count the number of InvokeCallbak() calls.
 
        private bool _endCalled;                   // True if the user called the End*() method.
        private bool _userEvent;                   // True if the event has been (or is about to be) handed to the user
 
        private object? _event;                    // Lazy allocated event to be returned in the IAsyncResult for the client to wait on.
 
        internal LazyAsyncResult(object? myObject, object? myState, AsyncCallback? myCallBack)
        {
            _asyncObject = myObject;
            _asyncState = myState;
            _asyncCallback = myCallBack;
            _result = DBNull.Value;
            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this);
        }
 
        // Interface method to return the original async object.
        internal object? AsyncObject
        {
            get
            {
                return _asyncObject;
            }
        }
 
        // Interface method to return the caller's state object.
        public object? AsyncState
        {
            get
            {
                return _asyncState;
            }
        }
 
        protected AsyncCallback? AsyncCallback
        {
            get
            {
                return _asyncCallback;
            }
 
            set
            {
                _asyncCallback = value;
            }
        }
 
        // Interface property to return a WaitHandle that can be waited on for I/O completion.
        //
        // This property implements lazy event creation.
        //
        // If this is used, the event cannot be disposed because it is under the control of the
        // application.  Internal should use InternalWaitForCompletion instead - never AsyncWaitHandle.
        public WaitHandle AsyncWaitHandle
        {
            get
            {
#if DEBUG
                // Can't be called when state is protected.
                if (_protectState)
                {
                    throw new InvalidOperationException("get_AsyncWaitHandle called in protected state");
                }
#endif
 
                // Indicates that the user has seen the event; it can't be disposed.
                _userEvent = true;
 
                // The user has access to this object.  Lock-in CompletedSynchronously.
                if (_intCompleted == 0)
                {
                    Interlocked.CompareExchange(ref _intCompleted, HighBit, 0);
                }
 
                // Because InternalWaitForCompletion() tries to dispose this event, it's
                // possible for _event to become null immediately after being set, but only if
                // IsCompleted has become true.  Therefore it's possible for this property
                // to give different (set) events to different callers when IsCompleted is true.
                ManualResetEvent? asyncEvent = (ManualResetEvent?)_event;
                while (asyncEvent == null)
                {
                    LazilyCreateEvent(out asyncEvent);
                }
 
                return asyncEvent;
            }
        }
 
        // Returns true if this call created the event.
        // May return with a null handle.  That means it thought it got one, but it was disposed in the mean time.
        private bool LazilyCreateEvent(out ManualResetEvent waitHandle)
        {
            waitHandle = new ManualResetEvent(false);
            try
            {
                if (Interlocked.CompareExchange(ref _event, waitHandle, null) == null)
                {
                    if (InternalPeekCompleted)
                    {
                        waitHandle.Set();
                    }
                    return true;
                }
                else
                {
                    waitHandle.Dispose();
                    waitHandle = (ManualResetEvent)_event;
 
                    // There's a chance here that _event became null.  But the only way is if another thread completed
                    // in InternalWaitForCompletion and disposed it.  If we're in InternalWaitForCompletion, we now know
                    // IsCompleted is set, so we can avoid the wait when waitHandle comes back null.  AsyncWaitHandle
                    // will try again in this case.
                    return false;
                }
            }
            catch
            {
                // This should be very rare, but doing this will reduce the chance of deadlock.
                _event = null;
                waitHandle?.Dispose();
 
                throw;
            }
        }
 
#pragma warning disable CA1822
        // This allows ContextAwareResult to not let anyone trigger the CompletedSynchronously tripwire while the context is being captured.
        [Conditional("DEBUG")]
        protected void DebugProtectState(bool protect)
        {
#if DEBUG
            _protectState = protect;
#endif
        }
#pragma warning restore CA1822
 
        // Interface property, returning synchronous completion status.
        public bool CompletedSynchronously
        {
            get
            {
#if DEBUG
                // Can't be called when state is protected.
                if (_protectState)
                {
                    throw new InvalidOperationException("get_CompletedSynchronously called in protected state");
                }
#endif
 
                // If this returns greater than zero, it means it was incremented by InvokeCallback before anyone ever saw it.
                int result = _intCompleted;
                if (result == 0)
                {
                    result = Interlocked.CompareExchange(ref _intCompleted, HighBit, 0);
                }
 
                return result > 0;
            }
        }
 
        // Interface property, returning completion status.
        public bool IsCompleted
        {
            get
            {
#if DEBUG
                // Can't be called when state is protected.
                if (_protectState)
                {
                    throw new InvalidOperationException("get_IsCompleted called in protected state");
                }
#endif
 
                // Verify low bits to see if it's been incremented.  If it hasn't, set the high bit
                // to show that it's been looked at.
                int result = _intCompleted;
                if (result == 0)
                {
                    result = Interlocked.CompareExchange(ref _intCompleted, HighBit, 0);
                }
 
                return (result & ~HighBit) != 0;
            }
        }
 
        // Use to see if something's completed without fixing CompletedSynchronously.
        internal bool InternalPeekCompleted
        {
            get
            {
                return (_intCompleted & ~HighBit) != 0;
            }
        }
 
        // Internal property for setting the IO result.
        internal object? Result
        {
            get
            {
                return _result == DBNull.Value ? null : _result;
            }
            set
            {
                // Ideally this should never be called, since setting
                // the result object really makes sense when the IO completes.
                //
                // But if the result was set here (as a preemptive error or for some other reason),
                // then the "result" parameter passed to InvokeCallback() will be ignored.
 
                // It's an error to call after the result has been completed or with DBNull.
                Debug.Assert(value != DBNull.Value, "Result can't be set to DBNull - it's a special internal value.");
 
                Debug.Assert(!InternalPeekCompleted, "Called on completed result.");
                _result = value;
            }
        }
 
        internal bool EndCalled
        {
            get
            {
                return _endCalled;
            }
            set
            {
                _endCalled = value;
            }
        }
 
        // Internal property for setting the Win32 IO async error code.
        internal int ErrorCode
        {
            get
            {
                return _errorCode;
            }
            set
            {
                _errorCode = value;
            }
        }
 
        // A method for completing the IO with a result and invoking the user's callback.
        // Used by derived classes to pass context into an overridden Complete().  Useful
        // for determining the 'winning' thread in case several may simultaneously call
        // the equivalent of InvokeCallback().
        protected void ProtectedInvokeCallback(object? result, IntPtr userToken)
        {
            // Critical to disallow DBNull here - it could result in a stuck spinlock in WaitForCompletion.
            if (result == DBNull.Value)
            {
                throw new ArgumentNullException(nameof(result));
            }
 
#if DEBUG
            // Always safe to ask for the state now.
            _protectState = false;
#endif
 
            if ((_intCompleted & ~HighBit) == 0 && (Interlocked.Increment(ref _intCompleted) & ~HighBit) == 1)
            {
                // DBNull.Value is used to guarantee that the first caller wins,
                // even if the result was set to null.
                if (_result == DBNull.Value)
                {
                    _result = result;
                }
 
                ManualResetEvent? asyncEvent = (ManualResetEvent?)_event;
                if (asyncEvent != null)
                {
                    try
                    {
                        asyncEvent.Set();
                    }
                    catch (ObjectDisposedException)
                    {
                        // Simply ignore this exception - There is apparently a rare race condition
                        // where the event is disposed before the completion method is called.
                    }
                }
 
                Complete(userToken);
            }
        }
 
        // Completes the IO with a result and invoking the user's callback.
        internal void InvokeCallback(object? result)
        {
            ProtectedInvokeCallback(result, IntPtr.Zero);
        }
 
        // Completes the IO without a result and invoking the user's callback.
        internal void InvokeCallback()
        {
            ProtectedInvokeCallback(null, IntPtr.Zero);
        }
 
        // NOTE: THIS METHOD MUST NOT BE CALLED DIRECTLY.
        //
        // This method does the callback's job and is guaranteed to be called exactly once.
        // A derived overriding method must call the base class somewhere or the completion is lost.
        protected virtual void Complete(IntPtr userToken)
        {
            bool offloaded = false;
            ThreadContext threadContext = CurrentThreadContext;
            try
            {
                ++threadContext._nestedIOCount;
                if (_asyncCallback != null)
                {
                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "Invoking callback");
 
                    if (threadContext._nestedIOCount >= ForceAsyncCount)
                    {
                        if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "*** OFFLOADED the user callback ****");
 
                        Task.Factory.StartNew(
                            s => WorkerThreadComplete(s!),
                            this,
                            CancellationToken.None,
                            TaskCreationOptions.DenyChildAttach,
                            TaskScheduler.Default);
 
                        offloaded = true;
                    }
                    else
                    {
                        _asyncCallback(this);
                    }
                }
                else
                {
                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "No callback to invoke");
                }
            }
            finally
            {
                --threadContext._nestedIOCount;
 
                // Never call this method unless interlocked _intCompleted check has succeeded (like in this case).
                if (!offloaded)
                {
                    Cleanup();
                }
            }
        }
 
        // Only called by the above method.
        private static void WorkerThreadComplete(object state)
        {
            Debug.Assert(state is LazyAsyncResult);
            LazyAsyncResult thisPtr = (LazyAsyncResult)state;
 
            try
            {
                thisPtr._asyncCallback!(thisPtr);
            }
            finally
            {
                thisPtr.Cleanup();
            }
        }
 
        // Custom instance cleanup method.
        //
        // Derived types override this method to release unmanaged resources associated with an IO request.
        protected virtual void Cleanup()
        {
        }
 
        internal object? InternalWaitForCompletion()
        {
            return WaitForCompletion(true);
        }
 
        private object? WaitForCompletion(bool snap)
        {
            ManualResetEvent? waitHandle = null;
            bool createdByMe = false;
            bool complete = snap ? IsCompleted : InternalPeekCompleted;
 
            if (!complete)
            {
                // Not done yet, so wait:
                waitHandle = (ManualResetEvent?)_event;
                if (waitHandle == null)
                {
                    createdByMe = LazilyCreateEvent(out waitHandle);
                }
            }
 
            if (waitHandle != null)
            {
                try
                {
                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Waiting for completion event {waitHandle}");
                    waitHandle.WaitOne(Timeout.Infinite);
                }
                catch (ObjectDisposedException)
                {
                    // This can occur if this method is called from two different threads.
                    // This possibility is the trade-off for not locking.
                }
                finally
                {
                    // We also want to dispose the event although we can't unless we did wait on it here.
                    if (createdByMe && !_userEvent)
                    {
                        // Does _userEvent need to be volatile (or _event set via Interlocked) in order
                        // to avoid giving a user a disposed event?
                        ManualResetEvent? oldEvent = (ManualResetEvent?)_event;
                        _event = null;
                        if (!_userEvent)
                        {
                            oldEvent?.Dispose();
                        }
                    }
                }
            }
 
            // A race condition exists because InvokeCallback sets _intCompleted before _result (so that _result
            // can benefit from the synchronization of _intCompleted).  That means you can get here before _result got
            // set (although rarely - once every eight hours of stress).  Handle that case with a spin-lock.
 
            SpinWait sw = default;
            while (_result == DBNull.Value)
            {
                sw.SpinOnce();
            }
 
            return _result;
        }
 
        // A general interface that is called to release unmanaged resources associated with the class.
        // It completes the result but doesn't do any of the notifications.
        internal void InternalCleanup()
        {
            if ((_intCompleted & ~HighBit) == 0 && (Interlocked.Increment(ref _intCompleted) & ~HighBit) == 1)
            {
                // Set no result so that just in case there are waiters, they don't get stuck in the spin lock.
                _result = null;
                Cleanup();
            }
        }
    }
}