Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit d092bcb

Browse files
committed
Avoid allocating a SafeNativeOverlapped per SocketAsyncEventArgs operation
Each operation ends up allocating a SafeNativeOverlapped, which results in a ton of allocations when trying to optimize code via SocketAsyncEventArgs. This commit reuses the same SafeHandle object for many / all of the operations on the event args instance.
1 parent 71070c5 commit d092bcb

File tree

4 files changed

+98
-48
lines changed

4 files changed

+98
-48
lines changed

src/Common/src/Interop/Windows/Winsock/SafeNativeOverlapped.cs

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,17 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using Microsoft.Win32.SafeHandles;
6-
75
using System.Diagnostics;
8-
using System.Runtime.CompilerServices;
96
using System.Runtime.InteropServices;
107
using System.Threading;
118

129
namespace System.Net.Sockets
1310
{
14-
internal class SafeNativeOverlapped : SafeHandle
11+
internal sealed class SafeNativeOverlapped : SafeHandle
1512
{
16-
private static readonly SafeNativeOverlapped s_zero = new SafeNativeOverlapped();
17-
private SafeCloseSocket _safeCloseSocket;
18-
19-
internal static SafeNativeOverlapped Zero { get { return s_zero; } }
13+
internal static SafeNativeOverlapped Zero { get; } = new SafeNativeOverlapped();
2014

21-
protected SafeNativeOverlapped()
15+
private SafeNativeOverlapped()
2216
: this(IntPtr.Zero)
2317
{
2418
if (GlobalLog.IsEnabled)
@@ -27,7 +21,7 @@ protected SafeNativeOverlapped()
2721
}
2822
}
2923

30-
protected SafeNativeOverlapped(IntPtr handle)
24+
private SafeNativeOverlapped(IntPtr handle)
3125
: base(IntPtr.Zero, true)
3226
{
3327
SetHandle(handle);
@@ -36,32 +30,26 @@ protected SafeNativeOverlapped(IntPtr handle)
3630
public unsafe SafeNativeOverlapped(SafeCloseSocket socketHandle, NativeOverlapped* handle)
3731
: this((IntPtr)handle)
3832
{
39-
_safeCloseSocket = socketHandle;
33+
SocketHandle = socketHandle;
4034

4135
if (GlobalLog.IsEnabled)
4236
{
4337
GlobalLog.Print("SafeNativeOverlapped#" + LoggingHash.HashString(this) + "::ctor(socket#" + LoggingHash.HashString(socketHandle) + ")");
4438
}
4539

4640
#if DEBUG
47-
_safeCloseSocket.AddRef();
41+
SocketHandle.AddRef();
4842
#endif
4943
}
5044

51-
protected override void Dispose(bool disposing)
45+
internal unsafe void ReplaceHandle(NativeOverlapped* overlapped)
5246
{
53-
if (disposing)
54-
{
55-
// It is important that the boundHandle is released immediately to allow new overlapped operations.
56-
if (GlobalLog.IsEnabled)
57-
{
58-
GlobalLog.Print("SafeNativeOverlapped#" + LoggingHash.HashString(this) + "::Dispose(true)");
59-
}
60-
61-
FreeNativeOverlapped();
62-
}
47+
Debug.Assert(handle == IntPtr.Zero, "We should only be replacing the handle when it's already been freed.");
48+
SetHandle((IntPtr)overlapped);
6349
}
6450

51+
internal SafeCloseSocket SocketHandle { get; }
52+
6553
public override bool IsInvalid
6654
{
6755
get { return handle == IntPtr.Zero; }
@@ -75,37 +63,31 @@ protected override bool ReleaseHandle()
7563
}
7664

7765
FreeNativeOverlapped();
66+
67+
#if DEBUG
68+
SocketHandle.Release();
69+
#endif
7870
return true;
7971
}
8072

81-
private void FreeNativeOverlapped()
73+
internal void FreeNativeOverlapped()
8274
{
83-
IntPtr oldHandle = Interlocked.Exchange(ref handle, IntPtr.Zero);
84-
8575
// Do not call free during AppDomain shutdown, there may be an outstanding operation.
8676
// Overlapped will take care calling free when the native callback completes.
77+
IntPtr oldHandle = Interlocked.Exchange(ref handle, IntPtr.Zero);
8778
if (oldHandle != IntPtr.Zero && !Environment.HasShutdownStarted)
8879
{
8980
unsafe
9081
{
91-
Debug.Assert(_safeCloseSocket != null, "m_SafeCloseSocket is null.");
92-
93-
ThreadPoolBoundHandle boundHandle = _safeCloseSocket.IOCPBoundHandle;
94-
Debug.Assert(boundHandle != null, "SafeNativeOverlapped::ImmediatelyFreeNativeOverlapped - boundHandle is null");
82+
Debug.Assert(SocketHandle != null, "SocketHandle is null.");
9583

96-
if (boundHandle != null)
97-
{
98-
// FreeNativeOverlapped will be called even if boundHandle was previously disposed.
99-
boundHandle.FreeNativeOverlapped((NativeOverlapped*)oldHandle);
100-
}
84+
ThreadPoolBoundHandle boundHandle = SocketHandle.IOCPBoundHandle;
85+
Debug.Assert(boundHandle != null, "SafeNativeOverlapped::FreeNativeOverlapped - boundHandle is null");
10186

102-
#if DEBUG
103-
_safeCloseSocket.Release();
104-
#endif
105-
_safeCloseSocket = null;
87+
// FreeNativeOverlapped will be called even if boundHandle was previously disposed.
88+
boundHandle?.FreeNativeOverlapped((NativeOverlapped*)oldHandle);
10689
}
10790
}
108-
return;
10991
}
11092
}
11193
}

src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,21 @@ private unsafe void PrepareIOCPOperation()
155155
"). Returned = " + ((IntPtr)overlapped).ToString("x"));
156156
}
157157
}
158-
159158
Debug.Assert(overlapped != null, "NativeOverlapped is null.");
160-
_ptrNativeOverlapped = new SafeNativeOverlapped(_currentSocket.SafeHandle, overlapped);
159+
160+
// If we already have a SafeNativeOverlapped SafeHandle and it's associated with the same
161+
// socket (due to the last operation that used this SocketAsyncEventArgs using the same socket),
162+
// then we can reuse the same SafeHandle object. Otherwise, this is either the first operation
163+
// or the last operation was with a different socket, so create a new SafeHandle.
164+
if (_ptrNativeOverlapped?.SocketHandle == _currentSocket.SafeHandle)
165+
{
166+
_ptrNativeOverlapped.ReplaceHandle(overlapped);
167+
}
168+
else
169+
{
170+
_ptrNativeOverlapped?.Dispose();
171+
_ptrNativeOverlapped = new SafeNativeOverlapped(_currentSocket.SafeHandle, overlapped);
172+
}
161173
}
162174

163175
private void CompleteIOCPOperation()
@@ -169,12 +181,10 @@ private void CompleteIOCPOperation()
169181
// it is guaranteed that the IOCP operation will be completed in the callback even if Socket.Success was
170182
// returned by the Win32 API.
171183

172-
// Required to allow another IOCP operation for the same handle.
173-
if (_ptrNativeOverlapped != null)
174-
{
175-
_ptrNativeOverlapped.Dispose();
176-
_ptrNativeOverlapped = null;
177-
}
184+
// Required to allow another IOCP operation for the same handle. We release the native overlapped
185+
// in the safe handle, but keep the safe handle object around so as to be able to reuse it
186+
// for other operations.
187+
_ptrNativeOverlapped?.FreeNativeOverlapped();
178188
}
179189

180190
private void InnerStartOperationAccept(bool userSuppliedBuffer)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Threading.Tasks;
6+
using Xunit;
7+
8+
namespace System.Net.Sockets.Tests
9+
{
10+
public class SocketAsyncEventArgsTest
11+
{
12+
[Fact]
13+
public async Task ReuseSocketAsyncEventArgs_SameInstance_MultipleSockets()
14+
{
15+
using (var listen = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
16+
using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
17+
{
18+
listen.Bind(new IPEndPoint(IPAddress.Loopback, 0));
19+
listen.Listen(1);
20+
21+
Task<Socket> acceptTask = listen.AcceptAsync();
22+
await Task.WhenAll(
23+
acceptTask,
24+
client.ConnectAsync(new IPEndPoint(IPAddress.Loopback, ((IPEndPoint)listen.LocalEndPoint).Port)));
25+
26+
using (Socket server = await acceptTask)
27+
{
28+
TaskCompletionSource<bool> tcs = null;
29+
30+
var args = new SocketAsyncEventArgs();
31+
args.SetBuffer(new byte[1024], 0, 1024);
32+
args.Completed += (_,__) => tcs.SetResult(true);
33+
34+
for (int i = 1; i <= 10; i++)
35+
{
36+
tcs = new TaskCompletionSource<bool>();
37+
args.Buffer[0] = (byte)i;
38+
args.SetBuffer(0, 1);
39+
if (server.SendAsync(args))
40+
{
41+
await tcs.Task;
42+
}
43+
44+
args.Buffer[0] = 0;
45+
tcs = new TaskCompletionSource<bool>();
46+
if (client.ReceiveAsync(args))
47+
{
48+
await tcs.Task;
49+
}
50+
Assert.Equal(1, args.BytesTransferred);
51+
Assert.Equal(i, args.Buffer[0]);
52+
}
53+
}
54+
}
55+
}
56+
}
57+
}

src/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<Compile Include="TcpClientTest.cs" />
4040
<Compile Include="Shutdown.cs" />
4141
<Compile Include="SocketAPMExtensions.cs" />
42+
<Compile Include="SocketAsyncEventArgsTest.cs" />
4243
<Compile Include="SocketAsyncExtensions.cs" />
4344
<Compile Include="SocketOptionNameTest.cs" />
4445
<Compile Include="UdpClientTest.cs" />

0 commit comments

Comments
 (0)