/// <summary>Rents a <see cref="Int32TaskSocketAsyncEventArgs"/> for immediate use.</summary> /// <param name="isReceive">true if this instance will be used for a receive; false if for sends.</param> private Int32TaskSocketAsyncEventArgs RentSocketAsyncEventArgs(bool isReceive) { // Get any cached SocketAsyncEventArg we may have. Int32TaskSocketAsyncEventArgs saea = isReceive ? Interlocked.Exchange(ref _cachedReceiveEventArgs, s_rentedSentinel) : Interlocked.Exchange(ref _cachedSendEventArgs, s_rentedSentinel); if (saea == s_rentedSentinel) { // An instance was once created (or is currently being created elsewhere), but some other // concurrent operation is using it. Since we can store at most one, and since an individual // APM operation is less expensive than creating a new SAEA and using it only once, we simply // return null, for a caller to fall back to using an APM implementation. return(null); } if (saea == null) { // No instance has been created yet, so create one. saea = new Int32TaskSocketAsyncEventArgs(); var handler = isReceive ? // branch to avoid capturing isReceive on every call new EventHandler <SocketAsyncEventArgs>((_, e) => CompleteSendReceive((Int32TaskSocketAsyncEventArgs)e, isReceive: true)) : new EventHandler <SocketAsyncEventArgs>((_, e) => CompleteSendReceive((Int32TaskSocketAsyncEventArgs)e, isReceive: false)); saea.Completed += handler; } // We got an instance. Configure and return it. saea.UserToken = this; return(saea); }
/// <summary>Returns a <see cref="Int32TaskSocketAsyncEventArgs"/> instance for reuse.</summary> /// <param name="saea">The instance to return.</param> /// <param name="isReceive">true if this instance is used for receives; false if used for sends.</param> private void ReturnSocketAsyncEventArgs(Int32TaskSocketAsyncEventArgs saea, bool isReceive) { Debug.Assert(saea != s_rentedSentinel); // Reset state on the SAEA before returning it. But do not reset buffer state. That'll be done // if necessary by the consumer, but we want to keep the buffers due to likely subsequent reuse // and the costs associated with changing them. saea.UserToken = null; saea._accessed = false; saea._builder = default(AsyncTaskMethodBuilder <int>); saea._wrapExceptionsInIOExceptions = false; // Write this instance back as a cached instance. It should only ever be overwriting the sentinel, // never null or another instance. if (isReceive) { Debug.Assert(_cachedReceiveEventArgs == s_rentedSentinel); Volatile.Write(ref _cachedReceiveEventArgs, saea); } else { Debug.Assert(_cachedSendEventArgs == s_rentedSentinel); Volatile.Write(ref _cachedSendEventArgs, saea); } }
/// <summary>Rents a <see cref="Int32TaskSocketAsyncEventArgs"/> for immediate use.</summary> /// <param name="isReceive">true if this instance will be used for a receive; false if for sends.</param> private Int32TaskSocketAsyncEventArgs RentSocketAsyncEventArgs(bool isReceive) { // Get any cached SocketAsyncEventArg we may have. CachedTaskEventArgs cea = LazyInitializer.EnsureInitialized(ref _cachedTaskEventArgs); Int32TaskSocketAsyncEventArgs saea = isReceive ? Interlocked.Exchange(ref cea.Receive, s_rentedInt32Sentinel) : Interlocked.Exchange(ref cea.Send, s_rentedInt32Sentinel); if (saea == s_rentedInt32Sentinel) { // An instance was once created (or is currently being created elsewhere), but some other // concurrent operation is using it. Since we can store at most one, and since an individual // APM operation is less expensive than creating a new SAEA and using it only once, we simply // return null, for a caller to fall back to using an APM implementation. return(null); } if (saea == null) { // No instance has been created yet, so create one. saea = new Int32TaskSocketAsyncEventArgs(); saea.Completed += isReceive ? ReceiveCompletedHandler : SendCompletedHandler; } return(saea); }
/// <summary>Completes the SocketAsyncEventArg's Task with the result of the send or receive, and returns it to the specified pool.</summary> private static void CompleteSendReceive(Int32TaskSocketAsyncEventArgs saea, bool isReceive) { // Pull the relevant state off of the SAEA Socket s = (Socket)saea.UserToken; SocketError error = saea.SocketError; int bytesTransferred = saea.BytesTransferred; bool wrapExceptionsInIOExceptions = saea._wrapExceptionsInIOExceptions; // Synchronize with the initiating thread. If the synchronous caller already got what // it needs from the SAEA, then we can return it to the pool now. Otherwise, it'll be // responsible for returning it once it's gotten what it needs from it. bool responsibleForReturningToPool; AsyncTaskMethodBuilder <int> builder = saea.GetCompletionResponsibility(out responsibleForReturningToPool); if (responsibleForReturningToPool) { s.ReturnSocketAsyncEventArgs(saea, isReceive); } // Complete the builder/task with the results. if (error == SocketError.Success) { builder.SetResult(bytesTransferred); } else { builder.SetException(GetException(error, wrapExceptionsInIOExceptions)); } }
/// <summary>Gets a task to represent the operation.</summary> /// <param name="pending">true if the operation completes asynchronously; false if it completed synchronously.</param> /// <param name="saea">The event args instance used with the operation.</param> /// <param name="fromNetworkStream"> /// true if the request is coming from NetworkStream, which has special semantics for /// exceptions and cached tasks; otherwise, false. /// </param> /// <param name="isReceive">true if this is a receive; false if this is a send.</param> private Task <int> GetTaskForSendReceive( bool pending, Int32TaskSocketAsyncEventArgs saea, bool fromNetworkStream, bool isReceive) { Task <int> t; if (pending) { // The operation is completing asynchronously (it may have already completed). // Get the task for the operation, with appropriate synchronization to coordinate // with the async callback that'll be completing the task. bool responsibleForReturningToPool; t = saea.GetCompletionResponsibility(out responsibleForReturningToPool).Task; if (responsibleForReturningToPool) { // We're responsible for returning it only if the callback has already been invoked // and gotten what it needs from the SAEA; otherwise, the callback will return it. ReturnSocketAsyncEventArgs(saea, isReceive); } } else { // The operation completed synchronously. Get a task for it. if (saea.SocketError == SocketError.Success) { // Get the number of bytes successfully received/sent. int bytesTransferred = saea.BytesTransferred; // For zero bytes transferred, we can return our cached 0 task. // We can also do so if the request came from network stream and is a send, // as for that we can return any value because it returns a non-generic Task. if (bytesTransferred == 0 || (fromNetworkStream & !isReceive)) { t = s_zeroTask; } else { // Get any cached, successfully-completed cached task that may exist on this SAEA. Task <int> lastTask = saea._successfullyCompletedTask; Debug.Assert(lastTask == null || lastTask.Status == TaskStatus.RanToCompletion); // If there is a task and if it has the desired result, simply reuse it. // Otherwise, create a new one for this result value, and in addition to returning it, // also store it into the SAEA for potential future reuse. t = lastTask != null && lastTask.Result == bytesTransferred ? lastTask : (saea._successfullyCompletedTask = Task.FromResult(bytesTransferred)); } } else { t = Task.FromException <int>(GetException(saea.SocketError, wrapExceptionsInIOExceptions: fromNetworkStream)); } // There won't be a callback, and we're done with the SAEA, so return it to the pool. ReturnSocketAsyncEventArgs(saea, isReceive); } return(t); }
internal ValueTask <int> SendAsync(ReadOnlyMemory <byte> buffer, SocketFlags socketFlags, bool fromNetworkStream, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(new ValueTask <int>(Task.FromCanceled <int>(cancellationToken))); } // TODO https://github.com/dotnet/corefx/issues/24430: // Fully plumb cancellation down into socket operations. Int32TaskSocketAsyncEventArgs saea = RentSocketAsyncEventArgs(isReceive: false); if (saea != null) { // We got a cached instance. Configure the buffer and initate the operation. ConfigureBuffer(saea, MemoryMarshal.AsMemory <byte>(buffer), socketFlags, wrapExceptionsInIOExceptions: fromNetworkStream); return(GetValueTaskForSendReceive(SendAsync(saea), saea, fromNetworkStream, isReceive: false)); } else { // We couldn't get a cached instance, due to a concurrent send operation on the socket. // Fall back to wrapping APM. return(new ValueTask <int>(SendAsyncApm(buffer, socketFlags))); } }
/// <summary>Gets a value task to represent the operation.</summary> /// <param name="pending">true if the operation completes asynchronously; false if it completed synchronously.</param> /// <param name="saea">The event args instance used with the operation.</param> /// <param name="fromNetworkStream"> /// true if the request is coming from NetworkStream, which has special semantics for /// exceptions and cached tasks; otherwise, false. /// </param> /// <param name="isReceive">true if this is a receive; false if this is a send.</param> private ValueTask <int> GetValueTaskForSendReceive( bool pending, Int32TaskSocketAsyncEventArgs saea, bool fromNetworkStream, bool isReceive) { ValueTask <int> t; if (pending) { // The operation is completing asynchronously (it may have already completed). // Get the task for the operation, with appropriate synchronization to coordinate // with the async callback that'll be completing the task. bool responsibleForReturningToPool; t = new ValueTask <int>(saea.GetCompletionResponsibility(out responsibleForReturningToPool).Task); if (responsibleForReturningToPool) { // We're responsible for returning it only if the callback has already been invoked // and gotten what it needs from the SAEA; otherwise, the callback will return it. ReturnSocketAsyncEventArgs(saea, isReceive); } } else { // The operation completed synchronously. Return a ValueTask for it. t = saea.SocketError == SocketError.Success ? new ValueTask <int>(saea.BytesTransferred) : new ValueTask <int>(Task.FromException <int>(GetException(saea.SocketError, wrapExceptionsInIOExceptions: fromNetworkStream))); // There won't be a callback, and we're done with the SAEA, so return it to the pool. ReturnSocketAsyncEventArgs(saea, isReceive); } return(t); }
private static void ConfigureBufferList( Int32TaskSocketAsyncEventArgs saea, IList <ArraySegment <byte> > buffers, SocketFlags socketFlags) { // Configure the buffer list. We don't clear the buffers when returning the SAEA to the pool, // so as to minimize overhead if the same buffers are used for subsequent operations (which is likely). // But SAEA doesn't support having both a buffer and a buffer list configured, so clear out a buffer // if there is one before we set the desired buffer list. if (!saea.MemoryBuffer.Equals(default))
internal Task <int> SendAsync(ArraySegment <byte> buffer, SocketFlags socketFlags, bool wrapExceptionsInIOExceptions) { // Validate the arguments. ValidateBuffer(buffer); // Get the SocketAsyncEventArgs instance to use for the operation. Int32TaskSocketAsyncEventArgs saea = RentSocketAsyncEventArgs(isReceive: false); if (saea == null) { // We couldn't get a cached instance, which means there's already a receive operation // happening on this socket. Fall back to wrapping APM. var tcs = new TaskCompletionSource <int>(this); BeginSend(buffer.Array, buffer.Offset, buffer.Count, socketFlags, iar => { var innerTcs = (TaskCompletionSource <int>)iar.AsyncState; try { innerTcs.TrySetResult(((Socket)innerTcs.Task.AsyncState).EndSend(iar)); } catch (Exception e) { innerTcs.TrySetException(e); } }, tcs); return(tcs.Task); } // Configure the buffer. We don't clear the buffers when returning the SAEA to the pool, // so as to minimize overhead if the same buffer is used for subsequent operations (which is likely). // But SAEA doesn't support having both a buffer and a buffer list configured, so clear out a buffer list // if there is one before we set the desired buffer. if (saea.BufferList != null) { saea.BufferList = null; } saea.SetBuffer(buffer.Array, buffer.Offset, buffer.Count); saea.SocketFlags = socketFlags; saea.WrapExceptionsInIOExceptions = wrapExceptionsInIOExceptions; // Initiate the send Task <int> t; if (!SendAsync(saea)) { // The operation completed synchronously. Get a task for it and return the SAEA for future use. t = saea.SocketError == SocketError.Success ? GetSuccessTask(saea) : Task.FromException <int>(GetException(saea.SocketError, wrapExceptionsInIOExceptions)); ReturnSocketAsyncEventArgs(saea, isReceive: false); } else { // The operation completed asynchronously. Get the task for the operation, // with appropriate synchronization to coordinate with the async callback // that'll be completing the task. t = saea.GetTaskSafe(); } return(t); }
/// <summary>Dispose of any cached <see cref="Int32TaskSocketAsyncEventArgs"/> instances.</summary> private void DisposeCachedTaskSocketAsyncEventArgs() { Int32TaskSocketAsyncEventArgs e = Interlocked.Exchange(ref _cachedReceiveEventArgs, s_rentedSentinel); if (e != s_rentedSentinel) { e?.Dispose(); } e = Interlocked.Exchange(ref _cachedSendEventArgs, s_rentedSentinel); if (e != s_rentedSentinel) { e?.Dispose(); } }
private static void ConfigureBuffer( Int32TaskSocketAsyncEventArgs saea, ArraySegment <byte> buffer, SocketFlags socketFlags, bool wrapExceptionsInIOExceptions) { // Configure the buffer. We don't clear the buffers when returning the SAEA to the pool, // so as to minimize overhead if the same buffer is used for subsequent operations (which is likely). // But SAEA doesn't support having both a buffer and a buffer list configured, so clear out a buffer list // if there is one before we set the desired buffer. if (saea.BufferList != null) { saea.BufferList = null; } saea.SetBuffer(buffer.Array, buffer.Offset, buffer.Count); saea.SocketFlags = socketFlags; saea._wrapExceptionsInIOExceptions = wrapExceptionsInIOExceptions; }
/// <summary>Gets a <see cref="Task{Int32}"/> that represents the BytesTransferred from a successful send/receive.</summary> private static Task <int> GetSuccessTask(Int32TaskSocketAsyncEventArgs saea) { // Get the number of bytes successfully received/sent. int bytesTransferred = saea.BytesTransferred; // And get any cached, successfully-completed cached task that may exist on this SAEA. Task <int> lastTask = saea.SuccessfullyCompletedTask; Debug.Assert(lastTask == null || lastTask.Status == TaskStatus.RanToCompletion); // If there is a task and if it has the desired result, simply reuse it. // Otherwise, create a new one for this result value, and in addition to returning it, // also store it into the SAEA for potential future reuse. return(lastTask != null && lastTask.Result == bytesTransferred ? lastTask : (saea.SuccessfullyCompletedTask = Task.FromResult(bytesTransferred))); }
internal Task <int> SendAsync(IList <ArraySegment <byte> > buffers, SocketFlags socketFlags) { // Validate the arguments. ValidateBuffersList(buffers); Int32TaskSocketAsyncEventArgs saea = RentSocketAsyncEventArgs(isReceive: false); if (saea != null) { // We got a cached instance. Configure the buffer list and initate the operation. ConfigureBufferList(saea, buffers, socketFlags); return(GetTaskForSendReceive(SendAsync(saea), saea, wrapExceptionsInIOExceptions: false, isReceive: false)); } else { // We couldn't get a cached instance, due to a concurrent send operation on the socket. // Fall back to wrapping APM. return(SendAsyncApm(buffers, socketFlags)); } }
internal Task <int> ReceiveAsync(ArraySegment <byte> buffer, SocketFlags socketFlags, bool wrapExceptionsInIOExceptions) { // Validate the arguments. ValidateBuffer(buffer); Int32TaskSocketAsyncEventArgs saea = RentSocketAsyncEventArgs(isReceive: true); if (saea != null) { // We got a cached instance. Configure the buffer and initate the operation. ConfigureBuffer(saea, buffer, socketFlags, wrapExceptionsInIOExceptions); return(GetTaskForSendReceive(ReceiveAsync(saea), saea, wrapExceptionsInIOExceptions, isReceive: true)); } else { // We couldn't get a cached instance, due to a concurrent receive operation on the socket. // Fall back to wrapping APM. return(ReceiveAsyncApm(buffer, socketFlags)); } }
/// <summary>Completes the SocketAsyncEventArg's Task with the result of the send or receive, and returns it to the specified pool.</summary> private static void CompleteSendReceive(Int32TaskSocketAsyncEventArgs saea, bool isReceive) { // Synchronize with the initiating thread accessing the task from the builder. saea.GetTaskSafe(); // Pull the relevant state off of the SAEA and only then return it to the pool. Socket s = (Socket)saea.UserToken; AsyncTaskMethodBuilder <int> builder = saea.Builder; SocketError error = saea.SocketError; int bytesTransferred = saea.BytesTransferred; bool wrapExceptionsInIOExceptions = saea.WrapExceptionsInIOExceptions; s.ReturnSocketAsyncEventArgs(saea, isReceive); // Complete the builder/task with the results. if (error == SocketError.Success) { builder.SetResult(bytesTransferred); } else { builder.SetException(GetException(error, wrapExceptionsInIOExceptions)); } }