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

Commit 7ce9270

Browse files
stephentoubdanmoseley
authored andcommitted
Fix Sockets hang caused by concurrent Socket disposal (#29786) (#29846)
* Fix Sockets hang caused by concurrent Socket disposal On Windows, if Socket.Send/ReceiveAsync is used, and there's a race condition where the Socket is disposed of between the time that the Socket checks for disposal and attempts to get a NativeOverlapped for use with the operation, the getting of the NativeOverlapped can throw an exception and cause us to leave a field in an inconsistent state, which in turn can cause the operation to hang, waiting for that state to be reset. The fix is to correct the order in which we get the NativeOverlapped vs setting the relevant field. This is a regression in 2.1. * Change ArgumentException to ObjectDisposedException from concurrent disposal If Socket is disposed of concurrently with the first operation on it that allocates a native overlapped, if the disposal happens between the check for disposal and the call to BindHandle, BindHandle may end up throwing an ArgumentException, which is not expected out of SendAsync. We should instead throw an ObjectDisposedException. * Add test for concurrent Send/ReceiveAsync and Dispose on Socket
1 parent ed23f53 commit 7ce9270

File tree

3 files changed

+117
-40
lines changed

3 files changed

+117
-40
lines changed

src/Common/src/System/Net/SafeCloseSocket.Windows.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,15 @@ public ThreadPoolBoundHandle GetOrAllocateThreadPoolBoundHandle(bool trySkipComp
6464
}
6565
catch (Exception exception) when (!ExceptionCheck.IsFatal(exception))
6666
{
67+
bool closed = IsClosed;
6768
CloseAsIs();
69+
if (closed)
70+
{
71+
// If the handle was closed just before the call to BindHandle,
72+
// we could end up getting an ArgumentException, which we should
73+
// instead propagate as an ObjectDisposedException.
74+
throw new ObjectDisposedException(typeof(Socket).FullName, exception);
75+
}
6876
throw;
6977
}
7078

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

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,9 @@ internal unsafe SocketError DoOperationAccept(Socket socket, SafeCloseSocket han
227227
}
228228
catch
229229
{
230+
_singleBufferHandleState = SingleBufferHandleState.None;
230231
FreeNativeOverlapped(overlapped);
231232
_singleBufferHandle.Dispose();
232-
_singleBufferHandleState = SingleBufferHandleState.None;
233233
throw;
234234
}
235235
}
@@ -261,9 +261,9 @@ internal unsafe SocketError DoOperationConnect(Socket socket, SafeCloseSocket ha
261261
}
262262
catch
263263
{
264+
_singleBufferHandleState = SingleBufferHandleState.None;
264265
FreeNativeOverlapped(overlapped);
265266
_singleBufferHandle.Dispose();
266-
_singleBufferHandleState = SingleBufferHandleState.None;
267267
throw;
268268
}
269269
}
@@ -296,13 +296,13 @@ internal unsafe SocketError DoOperationReceiveSingleBuffer(SafeCloseSocket handl
296296
{
297297
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(_buffer.Span))
298298
{
299-
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None, $"Expected None, got {_singleBufferHandleState}");
300-
_singleBufferHandleState = SingleBufferHandleState.InProcess;
301-
var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) };
302-
303299
NativeOverlapped* overlapped = AllocateNativeOverlapped();
304300
try
305301
{
302+
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None, $"Expected None, got {_singleBufferHandleState}");
303+
_singleBufferHandleState = SingleBufferHandleState.InProcess;
304+
var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) };
305+
306306
SocketFlags flags = _socketFlags;
307307
SocketError socketError = Interop.Winsock.WSARecv(
308308
handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead
@@ -318,8 +318,8 @@ internal unsafe SocketError DoOperationReceiveSingleBuffer(SafeCloseSocket handl
318318
}
319319
catch
320320
{
321-
FreeNativeOverlapped(overlapped);
322321
_singleBufferHandleState = SingleBufferHandleState.None;
322+
FreeNativeOverlapped(overlapped);
323323
throw;
324324
}
325325
}
@@ -368,13 +368,13 @@ internal unsafe SocketError DoOperationReceiveFromSingleBuffer(SafeCloseSocket h
368368
{
369369
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(_buffer.Span))
370370
{
371-
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
372-
_singleBufferHandleState = SingleBufferHandleState.InProcess;
373-
var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) };
374-
375371
NativeOverlapped* overlapped = AllocateNativeOverlapped();
376372
try
377373
{
374+
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
375+
_singleBufferHandleState = SingleBufferHandleState.InProcess;
376+
var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) };
377+
378378
SocketFlags flags = _socketFlags;
379379
SocketError socketError = Interop.Winsock.WSARecvFrom(
380380
handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead
@@ -392,8 +392,8 @@ internal unsafe SocketError DoOperationReceiveFromSingleBuffer(SafeCloseSocket h
392392
}
393393
catch
394394
{
395-
FreeNativeOverlapped(overlapped);
396395
_singleBufferHandleState = SingleBufferHandleState.None;
396+
FreeNativeOverlapped(overlapped);
397397
throw;
398398
}
399399
}
@@ -549,9 +549,9 @@ internal unsafe SocketError DoOperationReceiveMessageFrom(Socket socket, SafeClo
549549
}
550550
catch
551551
{
552+
_singleBufferHandleState = SingleBufferHandleState.None;
552553
FreeNativeOverlapped(overlapped);
553554
_singleBufferHandle.Dispose();
554-
_singleBufferHandleState = SingleBufferHandleState.None;
555555
throw;
556556
}
557557
}
@@ -564,13 +564,13 @@ internal unsafe SocketError DoOperationSendSingleBuffer(SafeCloseSocket handle)
564564
{
565565
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(_buffer.Span))
566566
{
567-
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
568-
_singleBufferHandleState = SingleBufferHandleState.InProcess;
569-
var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) };
570-
571567
NativeOverlapped* overlapped = AllocateNativeOverlapped();
572568
try
573569
{
570+
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
571+
_singleBufferHandleState = SingleBufferHandleState.InProcess;
572+
var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) };
573+
574574
SocketError socketError = Interop.Winsock.WSASend(
575575
handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead
576576
ref wsaBuffer,
@@ -585,8 +585,8 @@ internal unsafe SocketError DoOperationSendSingleBuffer(SafeCloseSocket handle)
585585
}
586586
catch
587587
{
588-
FreeNativeOverlapped(overlapped);
589588
_singleBufferHandleState = SingleBufferHandleState.None;
589+
FreeNativeOverlapped(overlapped);
590590
throw;
591591
}
592592
}
@@ -738,13 +738,13 @@ internal unsafe SocketError DoOperationSendToSingleBuffer(SafeCloseSocket handle
738738
{
739739
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(_buffer.Span))
740740
{
741-
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
742-
_singleBufferHandleState = SingleBufferHandleState.InProcess;
743-
var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) };
744-
745741
NativeOverlapped* overlapped = AllocateNativeOverlapped();
746742
try
747743
{
744+
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
745+
_singleBufferHandleState = SingleBufferHandleState.InProcess;
746+
var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) };
747+
748748
SocketError socketError = Interop.Winsock.WSASendTo(
749749
handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead
750750
ref wsaBuffer,
@@ -761,8 +761,8 @@ internal unsafe SocketError DoOperationSendToSingleBuffer(SafeCloseSocket handle
761761
}
762762
catch
763763
{
764-
FreeNativeOverlapped(overlapped);
765764
_singleBufferHandleState = SingleBufferHandleState.None;
765+
FreeNativeOverlapped(overlapped);
766766
throw;
767767
}
768768
}
@@ -916,8 +916,8 @@ private void FreePinHandles()
916916

917917
if (_singleBufferHandleState != SingleBufferHandleState.None)
918918
{
919-
_singleBufferHandle.Dispose();
920919
_singleBufferHandleState = SingleBufferHandleState.None;
920+
_singleBufferHandle.Dispose();
921921
}
922922

923923
if (_multipleBufferGCHandles != null)
@@ -1142,8 +1142,8 @@ void CompleteCoreSpin() // separate out to help inline the fast path
11421142

11431143
if (_singleBufferHandleState == SingleBufferHandleState.Set)
11441144
{
1145-
_singleBufferHandle.Dispose();
11461145
_singleBufferHandleState = SingleBufferHandleState.None;
1146+
_singleBufferHandle.Dispose();
11471147
}
11481148
}
11491149
}

src/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,90 @@ public void SocketSendReceiveBufferSize_SetZero_ThrowsSocketException()
837837
Assert.Equal(e.SocketErrorCode, SocketError.InvalidArgument);
838838
}
839839
}
840+
841+
[Fact]
842+
public async Task SendAsync_ConcurrentDispose_SucceedsOrThrowsAppropriateException()
843+
{
844+
if (UsesSync) return;
845+
846+
for (int i = 0; i < 20; i++) // run multiple times to attempt to force various interleavings
847+
{
848+
(Socket client, Socket server) = CreateConnectedSocketPair();
849+
using (client)
850+
using (server)
851+
using (var b = new Barrier(2))
852+
{
853+
Task dispose = Task.Factory.StartNew(() =>
854+
{
855+
b.SignalAndWait();
856+
client.Dispose();
857+
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
858+
859+
Task send = Task.Factory.StartNew(() =>
860+
{
861+
b.SignalAndWait();
862+
SendAsync(client, new ArraySegment<byte>(new byte[1])).GetAwaiter().GetResult();
863+
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
864+
865+
await dispose;
866+
Exception error = await Record.ExceptionAsync(() => send);
867+
if (error != null)
868+
{
869+
Assert.True(error is ObjectDisposedException || error is SocketException, error.ToString());
870+
}
871+
}
872+
}
873+
}
874+
875+
[Fact]
876+
public async Task ReceiveAsync_ConcurrentDispose_SucceedsOrThrowsAppropriateException()
877+
{
878+
if (UsesSync) return;
879+
880+
for (int i = 0; i < 20; i++) // run multiple times to attempt to force various interleavings
881+
{
882+
(Socket client, Socket server) = CreateConnectedSocketPair();
883+
using (client)
884+
using (server)
885+
using (var b = new Barrier(2))
886+
{
887+
Task dispose = Task.Factory.StartNew(() =>
888+
{
889+
b.SignalAndWait();
890+
client.Dispose();
891+
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
892+
893+
Task send = Task.Factory.StartNew(() =>
894+
{
895+
SendAsync(server, new ArraySegment<byte>(new byte[1])).GetAwaiter().GetResult();
896+
b.SignalAndWait();
897+
ReceiveAsync(client, new ArraySegment<byte>(new byte[1])).GetAwaiter().GetResult();
898+
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
899+
900+
await dispose;
901+
Exception error = await Record.ExceptionAsync(() => send);
902+
if (error != null)
903+
{
904+
Assert.True(error is ObjectDisposedException || error is SocketException, error.ToString());
905+
}
906+
}
907+
}
908+
}
909+
910+
protected static (Socket, Socket) CreateConnectedSocketPair()
911+
{
912+
using (Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
913+
{
914+
listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
915+
listener.Listen(1);
916+
917+
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
918+
client.Connect(listener.LocalEndPoint);
919+
Socket server = listener.Accept();
920+
921+
return (client, server);
922+
}
923+
}
840924
}
841925

842926
public class SendReceive
@@ -1248,21 +1332,6 @@ select Task.Factory.StartNew(() => pair.Item1.Receive(new byte[1]), Cancellation
12481332
}
12491333
}).Dispose();
12501334
}
1251-
1252-
private static (Socket, Socket) CreateConnectedSocketPair()
1253-
{
1254-
using (Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
1255-
{
1256-
listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
1257-
listener.Listen(1);
1258-
1259-
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
1260-
client.Connect(listener.LocalEndPoint);
1261-
Socket server = listener.Accept();
1262-
1263-
return (client, server);
1264-
}
1265-
}
12661335
}
12671336

12681337
public sealed class SendReceiveSyncForceNonBlocking : SendReceive<SocketHelperSyncForceNonBlocking> { }

0 commit comments

Comments
 (0)