From 16b6df08de6b6f7763f4496dfc12322caa756e34 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 12 May 2020 21:44:55 +0200 Subject: [PATCH 1/8] Unix: add mode to inline Socket continuations On Unix, socket continuations are dispatched to the ThreadPool from an event thread. This avoids continuations blocking the event handling. This adds an option to disable that dispatch. Continuations for socket operations will be executed on the event thread directly. This removes the overhead of context switching to the ThreadPool. Currently this is implemented as an application level switch for benchmarking, and experimentation. It may be made controllable at the Socket level. To avoid the event threads being a bottleneck, ProcessorCount event threads are created. --- .../Net/Sockets/SocketAsyncContext.Unix.cs | 32 +++++++++++++-- .../Net/Sockets/SocketAsyncEngine.Unix.cs | 18 ++++++++- .../InlineContinuations.Unix.cs | 40 +++++++++++++++++++ .../tests/FunctionalTests/SendReceive.cs | 1 + .../System.Net.Sockets.Tests.csproj | 1 + 5 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineContinuations.Unix.cs diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs index a0479bfc336298..146521204c7e7d 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs @@ -843,7 +843,7 @@ public bool StartAsyncOperation(SocketAsyncContext context, TOperation operation } } - public AsyncOperation? ProcessSyncEventOrGetAsyncEvent(SocketAsyncContext context, bool skipAsyncEvents = false) + public AsyncOperation? ProcessSyncEventOrGetAsyncEvent(SocketAsyncContext context, bool skipAsyncEvents = false, bool inlineAsyncEvents = true) { AsyncOperation op; using (Lock()) @@ -864,6 +864,7 @@ public bool StartAsyncOperation(SocketAsyncContext context, TOperation operation Debug.Assert(_isNextOperationSynchronous == (op.Event != null)); if (skipAsyncEvents && !_isNextOperationSynchronous) { + Debug.Assert(!inlineAsyncEvents); // Return the operation to indicate that the async operation was not processed, without making // any state changes because async operations are being skipped return op; @@ -901,6 +902,11 @@ public bool StartAsyncOperation(SocketAsyncContext context, TOperation operation { // Async operation. The caller will figure out how to process the IO. Debug.Assert(!skipAsyncEvents); + if (inlineAsyncEvents) + { + op.Process(); + return null; + } return op; } } @@ -2023,15 +2029,35 @@ public Interop.Sys.SocketEvents HandleSyncEventsSpeculatively(Interop.Sys.Socket return events; } - public unsafe void HandleEvents(Interop.Sys.SocketEvents events) + // Called on the epoll thread. + public Interop.Sys.SocketEvents HandleSyncAndInlineEvents(Interop.Sys.SocketEvents events) { if ((events & Interop.Sys.SocketEvents.Error) != 0) { - // Set the Read and Write flags as well; the processing for these events + // Set the Read and Write flags; the processing for these events // will pick up the error. + events ^= Interop.Sys.SocketEvents.Error; events |= Interop.Sys.SocketEvents.Read | Interop.Sys.SocketEvents.Write; } + if ((events & Interop.Sys.SocketEvents.Read) != 0 && + _receiveQueue.ProcessSyncEventOrGetAsyncEvent(this, inlineAsyncEvents: true) == null) + { + events ^= Interop.Sys.SocketEvents.Read; + } + + if ((events & Interop.Sys.SocketEvents.Write) != 0 && + _sendQueue.ProcessSyncEventOrGetAsyncEvent(this, inlineAsyncEvents: true) == null) + { + events ^= Interop.Sys.SocketEvents.Write; + } + + return events; + } + + // Called on ThreadPool thread. + public unsafe void HandleEvents(Interop.Sys.SocketEvents events) + { AsyncOperation? receiveOperation = (events & Interop.Sys.SocketEvents.Read) != 0 ? _receiveQueue.ProcessSyncEventOrGetAsyncEvent(this) : null; AsyncOperation? sendOperation = diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs index 053cc4fcb6f77c..088a787f2c30e5 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs @@ -12,6 +12,14 @@ namespace System.Net.Sockets { internal sealed unsafe class SocketAsyncEngine : IThreadPoolWorkItem { + // Socket continuations are dispatched to the ThreadPool from the event thread. + // This avoids continuations blocking the event handling. + // Setting DOTNET_SYSTEM_NET_SOCKETS_INLINE_CONTINUATIONS to '1' + // runs continuations directly on the event thread. + // This removes the overhead of context switching to the ThreadPool, + // at the risk of potentially blocking the event thread. + internal static readonly bool InlineSocketContinuations = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_CONTINUATIONS") == "1"; + // // Encapsulates a particular SocketAsyncContext object's access to a SocketAsyncEngine. // @@ -75,6 +83,12 @@ private static int GetEngineCount() return (int)count; } + // // When inlining continuations, we default to ProcessorCount to make sure event threads cannot be a bottleneck. + if (InlineSocketContinuations) + { + return Environment.ProcessorCount; + } + Architecture architecture = RuntimeInformation.ProcessArchitecture; int coresPerEngine = architecture == Architecture.Arm64 || architecture == Architecture.Arm ? 8 @@ -297,6 +311,7 @@ private void EventLoop() Interop.Sys.SocketEvent* buffer = _buffer; ConcurrentDictionary handleToContextMap = _handleToContextMap; ConcurrentQueue eventQueue = _eventQueue; + bool inlineSocketContinuations = InlineSocketContinuations; IntPtr shutdownHandle = ShutdownHandle; SocketAsyncContext? context = null; while (!shutdown) @@ -320,7 +335,8 @@ private void EventLoop() { Debug.Assert(handle.ToInt64() < MaxHandles.ToInt64(), $"Unexpected values: handle={handle}, MaxHandles={MaxHandles}"); - Interop.Sys.SocketEvents events = context.HandleSyncEventsSpeculatively(socketEvent.Events); + Interop.Sys.SocketEvents events = inlineSocketContinuations ? context.HandleSyncAndInlineEvents(socketEvent.Events) : + context.HandleSyncEventsSpeculatively(socketEvent.Events); if (events != Interop.Sys.SocketEvents.None) { var ev = new SocketIOEvent(context, events); diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineContinuations.Unix.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineContinuations.Unix.cs new file mode 100644 index 00000000000000..6d1e891e000740 --- /dev/null +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineContinuations.Unix.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Diagnostics; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; +using Microsoft.DotNet.RemoteExecutor; + +namespace System.Net.Sockets.Tests +{ + public class InlineContinuations + { + [OuterLoop] + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix)] // Inline Socket mode is specific to Unix Socket implementation. + public void InlineSocketContinuations() + { + RemoteInvokeOptions options = new RemoteInvokeOptions(); + options.StartInfo.EnvironmentVariables.Add("DOTNET_SYSTEM_NET_SOCKETS_INLINE_CONTINUATIONS", "1"); + options.TimeOut = (int)TimeSpan.FromMinutes(20).TotalMilliseconds; + + RemoteExecutor.Invoke(async () => + { + // Connect/Accept tests + await new AcceptEap(null).Accept_ConcurrentAcceptsBeforeConnects_Success(5); + await new AcceptEap(null).Accept_ConcurrentAcceptsAfterConnects_Success(5); + + // Send/Receive tests + await new SendReceiveEap(null).SendRecv_Stream_TCP(IPAddress.Loopback, useMultipleBuffers: false); + await new SendReceiveEap(null).SendRecv_Stream_TCP_MultipleConcurrentReceives(IPAddress.Loopback, useMultipleBuffers: false); + await new SendReceiveEap(null).SendRecv_Stream_TCP_MultipleConcurrentSends(IPAddress.Loopback, useMultipleBuffers: false); + await new SendReceiveEap(null).TcpReceiveSendGetsCanceledByDispose(receiveOrSend: true); + await new SendReceiveEap(null).TcpReceiveSendGetsCanceledByDispose(receiveOrSend: false); + }, options).Dispose(); + } + } +} \ No newline at end of file diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs index 68fcd850ce537e..d3bc0f5a84aaaf 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs @@ -1764,6 +1764,7 @@ public async Task DisposedSocket_ThrowsOperationCanceledException() public async Task BlockingAsyncContinuations_OperationsStillCompleteSuccessfully() { if (UsesSync) return; + if (Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_CONTINUATIONS") == "1") return; using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj index ab01170df35f44..1468528c4260d6 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj @@ -18,6 +18,7 @@ + From 066a045a79cfd95e2b46ec2e5b28cf04b8898ef7 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 13 May 2020 23:20:53 +0200 Subject: [PATCH 2/8] Add assert to verify Error is filtered out --- .../src/System/Net/Sockets/SocketAsyncContext.Unix.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs index 146521204c7e7d..a4c5167932836d 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs @@ -2058,6 +2058,8 @@ public Interop.Sys.SocketEvents HandleSyncAndInlineEvents(Interop.Sys.SocketEven // Called on ThreadPool thread. public unsafe void HandleEvents(Interop.Sys.SocketEvents events) { + Debug.Assert((events & Interop.Sys.SocketEvents.Error) == 0); + AsyncOperation? receiveOperation = (events & Interop.Sys.SocketEvents.Read) != 0 ? _receiveQueue.ProcessSyncEventOrGetAsyncEvent(this) : null; AsyncOperation? sendOperation = From 038db3f710139f71fddbc34e13426c6d991292ab Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Thu, 21 May 2020 22:31:01 +0200 Subject: [PATCH 3/8] Add internal Socket.PreferInlineCompletions --- .../src/System.Net.Sockets.csproj | 2 + .../System/Net/Sockets/SafeSocketHandle.cs | 2 + .../src/System/Net/Sockets/Socket.cs | 6 +++ .../Net/Sockets/SocketAsyncContext.Unix.cs | 27 +++++++------ .../Net/Sockets/SocketAsyncEngine.Unix.cs | 38 +++++++++++-------- 5 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj index f8a2cce64caf21..a98007ddb7c3a1 100644 --- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj +++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj @@ -214,6 +214,8 @@ Link="Common\System\Net\SocketProtocolSupportPal.Unix" /> + #region Properties + internal bool PreferInlineCompletions + { + get => _handle.PreferInlineCompletions; + set => _handle.PreferInlineCompletions = value; + } + // The CLR allows configuration of these properties, separately from whether the OS supports IPv4/6. We // do not provide these config options, so SupportsIPvX === OSSupportsIPvX. [Obsolete("SupportsIPv4 is obsoleted for this type, please use OSSupportsIPv4 instead. https://go.microsoft.com/fwlink/?linkid=14202")] diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs index a4c5167932836d..0316216440484a 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs @@ -843,7 +843,7 @@ public bool StartAsyncOperation(SocketAsyncContext context, TOperation operation } } - public AsyncOperation? ProcessSyncEventOrGetAsyncEvent(SocketAsyncContext context, bool skipAsyncEvents = false, bool inlineAsyncEvents = true) + public AsyncOperation? ProcessSyncEventOrGetAsyncEvent(SocketAsyncContext context, bool skipAsyncEvents = false, bool processAsyncEvents = true) { AsyncOperation op; using (Lock()) @@ -864,7 +864,7 @@ public bool StartAsyncOperation(SocketAsyncContext context, TOperation operation Debug.Assert(_isNextOperationSynchronous == (op.Event != null)); if (skipAsyncEvents && !_isNextOperationSynchronous) { - Debug.Assert(!inlineAsyncEvents); + Debug.Assert(!processAsyncEvents); // Return the operation to indicate that the async operation was not processed, without making // any state changes because async operations are being skipped return op; @@ -902,7 +902,7 @@ public bool StartAsyncOperation(SocketAsyncContext context, TOperation operation { // Async operation. The caller will figure out how to process the IO. Debug.Assert(!skipAsyncEvents); - if (inlineAsyncEvents) + if (processAsyncEvents) { op.Process(); return null; @@ -1195,6 +1195,13 @@ public SocketAsyncContext(SafeSocketHandle socket) _sendQueue.Init(); } + public bool PreferInlineCompletions + { + [PreserveDependency("get_PreferInlineCompletions", "System.Net.Sockets.Socket")] + [PreserveDependency("set_PreferInlineCompletions", "System.Net.Sockets.Socket")] + get => _socket.PreferInlineCompletions; + } + private void Register() { Debug.Assert(_nonBlockingSet); @@ -2030,7 +2037,7 @@ public Interop.Sys.SocketEvents HandleSyncEventsSpeculatively(Interop.Sys.Socket } // Called on the epoll thread. - public Interop.Sys.SocketEvents HandleSyncAndInlineEvents(Interop.Sys.SocketEvents events) + public void HandleEventsInline(Interop.Sys.SocketEvents events) { if ((events & Interop.Sys.SocketEvents.Error) != 0) { @@ -2040,19 +2047,15 @@ public Interop.Sys.SocketEvents HandleSyncAndInlineEvents(Interop.Sys.SocketEven events |= Interop.Sys.SocketEvents.Read | Interop.Sys.SocketEvents.Write; } - if ((events & Interop.Sys.SocketEvents.Read) != 0 && - _receiveQueue.ProcessSyncEventOrGetAsyncEvent(this, inlineAsyncEvents: true) == null) + if ((events & Interop.Sys.SocketEvents.Read) != 0) { - events ^= Interop.Sys.SocketEvents.Read; + _receiveQueue.ProcessSyncEventOrGetAsyncEvent(this, processAsyncEvents: true); } - if ((events & Interop.Sys.SocketEvents.Write) != 0 && - _sendQueue.ProcessSyncEventOrGetAsyncEvent(this, inlineAsyncEvents: true) == null) + if ((events & Interop.Sys.SocketEvents.Write) != 0) { - events ^= Interop.Sys.SocketEvents.Write; + _sendQueue.ProcessSyncEventOrGetAsyncEvent(this, processAsyncEvents: true); } - - return events; } // Called on ThreadPool thread. diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs index 088a787f2c30e5..d807628e7cb9dc 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs @@ -15,10 +15,11 @@ internal sealed unsafe class SocketAsyncEngine : IThreadPoolWorkItem // Socket continuations are dispatched to the ThreadPool from the event thread. // This avoids continuations blocking the event handling. // Setting DOTNET_SYSTEM_NET_SOCKETS_INLINE_CONTINUATIONS to '1' - // runs continuations directly on the event thread. + // allows continuations to run directly on the event thread by setting + // Socket.PreferInlineCompletions to true. // This removes the overhead of context switching to the ThreadPool, // at the risk of potentially blocking the event thread. - internal static readonly bool InlineSocketContinuations = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_CONTINUATIONS") == "1"; + internal static readonly bool InlineSocketContinuationsEnabled = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_CONTINUATIONS") == "1"; // // Encapsulates a particular SocketAsyncContext object's access to a SocketAsyncEngine. @@ -84,7 +85,7 @@ private static int GetEngineCount() } // // When inlining continuations, we default to ProcessorCount to make sure event threads cannot be a bottleneck. - if (InlineSocketContinuations) + if (InlineSocketContinuationsEnabled) { return Environment.ProcessorCount; } @@ -311,7 +312,7 @@ private void EventLoop() Interop.Sys.SocketEvent* buffer = _buffer; ConcurrentDictionary handleToContextMap = _handleToContextMap; ConcurrentQueue eventQueue = _eventQueue; - bool inlineSocketContinuations = InlineSocketContinuations; + bool inlineSocketContinuationsEnabled = InlineSocketContinuationsEnabled; IntPtr shutdownHandle = ShutdownHandle; SocketAsyncContext? context = null; while (!shutdown) @@ -335,18 +336,25 @@ private void EventLoop() { Debug.Assert(handle.ToInt64() < MaxHandles.ToInt64(), $"Unexpected values: handle={handle}, MaxHandles={MaxHandles}"); - Interop.Sys.SocketEvents events = inlineSocketContinuations ? context.HandleSyncAndInlineEvents(socketEvent.Events) : - context.HandleSyncEventsSpeculatively(socketEvent.Events); - if (events != Interop.Sys.SocketEvents.None) + if (inlineSocketContinuationsEnabled && context.PreferInlineCompletions) { - var ev = new SocketIOEvent(context, events); - eventQueue.Enqueue(ev); - enqueuedEvent = true; - - // This is necessary when the JIT generates unoptimized code (debug builds, live debugging, - // quick JIT, etc.) to ensure that the context does not remain referenced by this method, as - // such code may keep the stack location live for longer than necessary - ev = default; + context.HandleEventsInline(socketEvent.Events); + } + else + { + Interop.Sys.SocketEvents events = context.HandleSyncEventsSpeculatively(socketEvent.Events); + + if (events != Interop.Sys.SocketEvents.None) + { + var ev = new SocketIOEvent(context, events); + eventQueue.Enqueue(ev); + enqueuedEvent = true; + + // This is necessary when the JIT generates unoptimized code (debug builds, live debugging, + // quick JIT, etc.) to ensure that the context does not remain referenced by this method, as + // such code may keep the stack location live for longer than necessary + ev = default; + } } // This is necessary when the JIT generates unoptimized code (debug builds, live debugging, From 3017821a641d19a0b8f169d6cf24d534a17029be Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 26 May 2020 16:55:08 +0200 Subject: [PATCH 4/8] Add envvar to control PreferInlineCompletions default --- .../src/System/Net/Sockets/SafeSocketHandle.cs | 4 +++- .../src/System/Net/Sockets/SocketAsyncEngine.Unix.cs | 10 +++++----- ...Continuations.Unix.cs => InlineCompletions.Unix.cs} | 3 ++- .../tests/FunctionalTests/SendReceive.cs | 2 +- .../FunctionalTests/System.Net.Sockets.Tests.csproj | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) rename src/libraries/System.Net.Sockets/tests/FunctionalTests/{InlineContinuations.Unix.cs => InlineCompletions.Unix.cs} (91%) diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs index 655a22254f577e..67d542db904ab2 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs @@ -43,7 +43,9 @@ public SafeSocketHandle(IntPtr preexistingHandle, bool ownsHandle) internal bool OwnsHandle { get; } - internal bool PreferInlineCompletions { get; set; } = true; + private static readonly bool PreferInlineCompletionsDefault = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS_DEFAULT") == "1"; + + internal bool PreferInlineCompletions { get; set; } = PreferInlineCompletionsDefault; private bool TryOwnClose() { diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs index d807628e7cb9dc..cb0af024e1d4ff 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs @@ -14,12 +14,12 @@ internal sealed unsafe class SocketAsyncEngine : IThreadPoolWorkItem { // Socket continuations are dispatched to the ThreadPool from the event thread. // This avoids continuations blocking the event handling. - // Setting DOTNET_SYSTEM_NET_SOCKETS_INLINE_CONTINUATIONS to '1' + // Setting DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS_ENABLED to '1' // allows continuations to run directly on the event thread by setting // Socket.PreferInlineCompletions to true. // This removes the overhead of context switching to the ThreadPool, // at the risk of potentially blocking the event thread. - internal static readonly bool InlineSocketContinuationsEnabled = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_CONTINUATIONS") == "1"; + internal static readonly bool InlineSocketCompletionsEnabled = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS_ENABLED") == "1"; // // Encapsulates a particular SocketAsyncContext object's access to a SocketAsyncEngine. @@ -85,7 +85,7 @@ private static int GetEngineCount() } // // When inlining continuations, we default to ProcessorCount to make sure event threads cannot be a bottleneck. - if (InlineSocketContinuationsEnabled) + if (InlineSocketCompletionsEnabled) { return Environment.ProcessorCount; } @@ -312,7 +312,7 @@ private void EventLoop() Interop.Sys.SocketEvent* buffer = _buffer; ConcurrentDictionary handleToContextMap = _handleToContextMap; ConcurrentQueue eventQueue = _eventQueue; - bool inlineSocketContinuationsEnabled = InlineSocketContinuationsEnabled; + bool inlineSocketCompletionsEnabled = InlineSocketCompletionsEnabled; IntPtr shutdownHandle = ShutdownHandle; SocketAsyncContext? context = null; while (!shutdown) @@ -336,7 +336,7 @@ private void EventLoop() { Debug.Assert(handle.ToInt64() < MaxHandles.ToInt64(), $"Unexpected values: handle={handle}, MaxHandles={MaxHandles}"); - if (inlineSocketContinuationsEnabled && context.PreferInlineCompletions) + if (inlineSocketCompletionsEnabled && context.PreferInlineCompletions) { context.HandleEventsInline(socketEvent.Events); } diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineContinuations.Unix.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineCompletions.Unix.cs similarity index 91% rename from src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineContinuations.Unix.cs rename to src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineCompletions.Unix.cs index 6d1e891e000740..72334d5139f11b 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineContinuations.Unix.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineCompletions.Unix.cs @@ -19,7 +19,8 @@ public class InlineContinuations public void InlineSocketContinuations() { RemoteInvokeOptions options = new RemoteInvokeOptions(); - options.StartInfo.EnvironmentVariables.Add("DOTNET_SYSTEM_NET_SOCKETS_INLINE_CONTINUATIONS", "1"); + options.StartInfo.EnvironmentVariables.Add("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS", "1"); + options.StartInfo.EnvironmentVariables.Add("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS_DEFAULT", "1"); options.TimeOut = (int)TimeSpan.FromMinutes(20).TotalMilliseconds; RemoteExecutor.Invoke(async () => diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs index d3bc0f5a84aaaf..9e236e59e66ab7 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs @@ -1764,7 +1764,7 @@ public async Task DisposedSocket_ThrowsOperationCanceledException() public async Task BlockingAsyncContinuations_OperationsStillCompleteSuccessfully() { if (UsesSync) return; - if (Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_CONTINUATIONS") == "1") return; + if (Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS") == "1") return; using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj index 1468528c4260d6..c684dc6463b797 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj @@ -18,7 +18,7 @@ - + From b88d8286972d423dceb2d97cd01669dbed90ee92 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 26 May 2020 22:13:29 +0200 Subject: [PATCH 5/8] PR feedback --- .../src/System/Net/Sockets/SafeSocketHandle.cs | 2 ++ .../src/System/Net/Sockets/SocketAsyncContext.Unix.cs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs index 67d542db904ab2..d06bccb385058f 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs @@ -43,6 +43,8 @@ public SafeSocketHandle(IntPtr preexistingHandle, bool ownsHandle) internal bool OwnsHandle { get; } + // This is a temporary environment variable to control the default value of the PreferInlineCompletions property. + // This must be removed when PreferInlineCompletions is removed/made public. private static readonly bool PreferInlineCompletionsDefault = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS_DEFAULT") == "1"; internal bool PreferInlineCompletions { get; set; } = PreferInlineCompletionsDefault; diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs index 0316216440484a..8e6a6f63485155 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs @@ -1197,7 +1197,8 @@ public SocketAsyncContext(SafeSocketHandle socket) public bool PreferInlineCompletions { - [PreserveDependency("get_PreferInlineCompletions", "System.Net.Sockets.Socket")] + // Socket.PreferInlineCompletions is an experimental API with internal access modifier. + // PreserveDependency ensures the setter is available externally using reflection. [PreserveDependency("set_PreferInlineCompletions", "System.Net.Sockets.Socket")] get => _socket.PreferInlineCompletions; } From 76b95795d8a7b69f172e43ffbe0b4802e42ef8e3 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 2 Jun 2020 14:29:58 +0200 Subject: [PATCH 6/8] Squash DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS envvars --- .../src/System/Net/Sockets/SafeSocketHandle.cs | 6 +----- .../src/System/Net/Sockets/SocketAsyncEngine.Unix.cs | 12 ++++-------- .../tests/FunctionalTests/InlineCompletions.Unix.cs | 1 - 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs index d06bccb385058f..505ba9e576af48 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs @@ -43,11 +43,7 @@ public SafeSocketHandle(IntPtr preexistingHandle, bool ownsHandle) internal bool OwnsHandle { get; } - // This is a temporary environment variable to control the default value of the PreferInlineCompletions property. - // This must be removed when PreferInlineCompletions is removed/made public. - private static readonly bool PreferInlineCompletionsDefault = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS_DEFAULT") == "1"; - - internal bool PreferInlineCompletions { get; set; } = PreferInlineCompletionsDefault; + internal bool PreferInlineCompletions { get; set; } = SocketAsyncEngine.InlineSocketCompletionsEnabled; private bool TryOwnClose() { diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs index cb0af024e1d4ff..c2cd9578d51d28 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs @@ -14,12 +14,9 @@ internal sealed unsafe class SocketAsyncEngine : IThreadPoolWorkItem { // Socket continuations are dispatched to the ThreadPool from the event thread. // This avoids continuations blocking the event handling. - // Setting DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS_ENABLED to '1' - // allows continuations to run directly on the event thread by setting - // Socket.PreferInlineCompletions to true. - // This removes the overhead of context switching to the ThreadPool, - // at the risk of potentially blocking the event thread. - internal static readonly bool InlineSocketCompletionsEnabled = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS_ENABLED") == "1"; + // Setting PreferInlineCompletions allows continuations to run directly on the event thread. + // PreferInlineCompletions defaults to false and can be set to true using the DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS envvar. + internal static readonly bool InlineSocketCompletionsEnabled = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS") == "1"; // // Encapsulates a particular SocketAsyncContext object's access to a SocketAsyncEngine. @@ -312,7 +309,6 @@ private void EventLoop() Interop.Sys.SocketEvent* buffer = _buffer; ConcurrentDictionary handleToContextMap = _handleToContextMap; ConcurrentQueue eventQueue = _eventQueue; - bool inlineSocketCompletionsEnabled = InlineSocketCompletionsEnabled; IntPtr shutdownHandle = ShutdownHandle; SocketAsyncContext? context = null; while (!shutdown) @@ -336,7 +332,7 @@ private void EventLoop() { Debug.Assert(handle.ToInt64() < MaxHandles.ToInt64(), $"Unexpected values: handle={handle}, MaxHandles={MaxHandles}"); - if (inlineSocketCompletionsEnabled && context.PreferInlineCompletions) + if (context.PreferInlineCompletions) { context.HandleEventsInline(socketEvent.Events); } diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineCompletions.Unix.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineCompletions.Unix.cs index 72334d5139f11b..a49dc45a6f1fd4 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineCompletions.Unix.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineCompletions.Unix.cs @@ -20,7 +20,6 @@ public void InlineSocketContinuations() { RemoteInvokeOptions options = new RemoteInvokeOptions(); options.StartInfo.EnvironmentVariables.Add("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS", "1"); - options.StartInfo.EnvironmentVariables.Add("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS_DEFAULT", "1"); options.TimeOut = (int)TimeSpan.FromMinutes(20).TotalMilliseconds; RemoteExecutor.Invoke(async () => From 674d7ebb7c8dc9c77703c8218c7ee3cf30478e00 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 2 Jun 2020 17:03:22 +0200 Subject: [PATCH 7/8] Don't use Unix specific SocketAsyncEngine in common SafeHandle code --- .../src/System/Net/Sockets/SafeSocketHandle.cs | 8 +++++++- .../src/System/Net/Sockets/SocketAsyncContext.Unix.cs | 1 + .../src/System/Net/Sockets/SocketAsyncEngine.Unix.cs | 10 ++-------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs index 505ba9e576af48..280d39c1783ec0 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs @@ -43,7 +43,13 @@ public SafeSocketHandle(IntPtr preexistingHandle, bool ownsHandle) internal bool OwnsHandle { get; } - internal bool PreferInlineCompletions { get; set; } = SocketAsyncEngine.InlineSocketCompletionsEnabled; + // Socket continuations are dispatched to the ThreadPool from the event thread. + // This avoids continuations blocking the event handling. + // Setting PreferInlineCompletions allows continuations to run directly on the event thread. + // PreferInlineCompletions defaults to false and can be set to true using the DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS envvar. + internal static readonly bool InlineSocketCompletionsEnabled = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS") == "1"; + + internal bool PreferInlineCompletions { get; set; } = InlineSocketCompletionsEnabled; private bool TryOwnClose() { diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs index 8e6a6f63485155..a91e615f5b4ec9 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using Microsoft.Win32.SafeHandles; using System.Collections.Generic; using System.Diagnostics; diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs index c2cd9578d51d28..1e3457e1b618e0 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs @@ -12,12 +12,6 @@ namespace System.Net.Sockets { internal sealed unsafe class SocketAsyncEngine : IThreadPoolWorkItem { - // Socket continuations are dispatched to the ThreadPool from the event thread. - // This avoids continuations blocking the event handling. - // Setting PreferInlineCompletions allows continuations to run directly on the event thread. - // PreferInlineCompletions defaults to false and can be set to true using the DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS envvar. - internal static readonly bool InlineSocketCompletionsEnabled = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS") == "1"; - // // Encapsulates a particular SocketAsyncContext object's access to a SocketAsyncEngine. // @@ -81,8 +75,8 @@ private static int GetEngineCount() return (int)count; } - // // When inlining continuations, we default to ProcessorCount to make sure event threads cannot be a bottleneck. - if (InlineSocketCompletionsEnabled) + // When inlining continuations, we default to ProcessorCount to make sure event threads cannot be a bottleneck. + if (SafeSocketHandle.InlineSocketCompletionsEnabled) { return Environment.ProcessorCount; } From 6ea1256bf8bd9ce6c1db278603b332cec144ba75 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 16 Jun 2020 18:03:20 +0200 Subject: [PATCH 8/8] PR feedback --- .../src/System/Net/Sockets/SafeSocketHandle.Unix.cs | 1 + .../src/System/Net/Sockets/SafeSocketHandle.cs | 8 -------- .../src/System/Net/Sockets/Socket.Unix.cs | 6 ++++++ .../System.Net.Sockets/src/System/Net/Sockets/Socket.cs | 6 ------ .../src/System/Net/Sockets/SocketAsyncContext.Unix.cs | 1 - .../src/System/Net/Sockets/SocketAsyncEngine.Unix.cs | 8 +++++++- 6 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Unix.cs index 39bbe582ec7333..a7d71a3a523902 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Unix.cs @@ -23,6 +23,7 @@ public partial class SafeSocketHandle internal bool LastConnectFailed { get; set; } internal bool DualMode { get; set; } internal bool ExposedHandleOrUntrackedConfiguration { get; private set; } + internal bool PreferInlineCompletions { get; set; } = SocketAsyncEngine.InlineSocketCompletionsEnabled; internal void RegisterConnectResult(SocketError error) { diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs index 280d39c1783ec0..dff1d2739b2c8c 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs @@ -43,14 +43,6 @@ public SafeSocketHandle(IntPtr preexistingHandle, bool ownsHandle) internal bool OwnsHandle { get; } - // Socket continuations are dispatched to the ThreadPool from the event thread. - // This avoids continuations blocking the event handling. - // Setting PreferInlineCompletions allows continuations to run directly on the event thread. - // PreferInlineCompletions defaults to false and can be set to true using the DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS envvar. - internal static readonly bool InlineSocketCompletionsEnabled = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS") == "1"; - - internal bool PreferInlineCompletions { get; set; } = InlineSocketCompletionsEnabled; - private bool TryOwnClose() { return OwnsHandle && Interlocked.CompareExchange(ref _ownClose, 1, 0) == 0; diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs index 1540d733a40843..9327c870962a21 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs @@ -26,6 +26,12 @@ public SocketInformation DuplicateAndClose(int targetProcessId) throw new PlatformNotSupportedException(SR.net_sockets_duplicateandclose_notsupported); } + internal bool PreferInlineCompletions + { + get => _handle.PreferInlineCompletions; + set => _handle.PreferInlineCompletions = value; + } + partial void ValidateForMultiConnect(bool isMultiEndpoint) { // ValidateForMultiConnect is called before any {Begin}Connect{Async} call, diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs index 5da598bd9d3213..e307dc4d6812b3 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs @@ -247,12 +247,6 @@ private static SafeSocketHandle ValidateHandle(SafeSocketHandle handle) => #region Properties - internal bool PreferInlineCompletions - { - get => _handle.PreferInlineCompletions; - set => _handle.PreferInlineCompletions = value; - } - // The CLR allows configuration of these properties, separately from whether the OS supports IPv4/6. We // do not provide these config options, so SupportsIPvX === OSSupportsIPvX. [Obsolete("SupportsIPv4 is obsoleted for this type, please use OSSupportsIPv4 instead. https://go.microsoft.com/fwlink/?linkid=14202")] diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs index 61392e4c2ab7e7..3a85246ee0e010 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using Microsoft.Win32.SafeHandles; using System.Collections.Generic; using System.Diagnostics; diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs index b7273f6b360381..99380b31cb0270 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs @@ -19,6 +19,12 @@ internal sealed unsafe class SocketAsyncEngine : IThreadPoolWorkItem 1024; #endif + // Socket continuations are dispatched to the ThreadPool from the event thread. + // This avoids continuations blocking the event handling. + // Setting PreferInlineCompletions allows continuations to run directly on the event thread. + // PreferInlineCompletions defaults to false and can be set to true using the DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS envvar. + internal static readonly bool InlineSocketCompletionsEnabled = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS") == "1"; + private static int GetEngineCount() { // The responsibility of SocketAsyncEngine is to get notifications from epoll|kqueue @@ -39,7 +45,7 @@ private static int GetEngineCount() } // When inlining continuations, we default to ProcessorCount to make sure event threads cannot be a bottleneck. - if (SafeSocketHandle.InlineSocketCompletionsEnabled) + if (InlineSocketCompletionsEnabled) { return Environment.ProcessorCount; }