Skip to content

Commit

Permalink
Add a Sockets test to validate undesired blocking of continuations (d…
Browse files Browse the repository at this point in the history
…otnet#1973)

We go out of our way (e.g. on Unix queueing work off of the epoll threads) to ensure that arbitrary user code running as part of socket continuations doesn't block those threads and potentially deadlock.  Add a test to help validate this.
  • Loading branch information
stephentoub authored Jan 22, 2020
1 parent 8861275 commit 2864610
Showing 1 changed file with 46 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1770,7 +1770,53 @@ public async Task DisposedSocket_ThrowsOperationCanceledException()
}
}
}

[Fact]
public async Task BlockingAsyncContinuations_OperationsStillCompleteSuccessfully()
{
if (UsesSync) return;

using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
listener.BindToAnonymousPort(IPAddress.Loopback);
listener.Listen(1);

await client.ConnectAsync(listener.LocalEndPoint);
using (Socket server = await listener.AcceptAsync())
{
await Task.Run(async delegate // escape the xunit sync context / task scheduler
{
const int SendDelayMs = 100;

Task sendTask = Task.Delay(SendDelayMs)
.ContinueWith(_ => server.SendAsync(new byte[1], SocketFlags.None))
.Unwrap();
await client.ReceiveAsync(new byte[1], SocketFlags.None);
sendTask.GetAwaiter().GetResult(); // should have already completed

// We may now be executing here as part of the continuation invoked synchronously
// when the client ReceiveAsync task was completed. Validate that if socket callbacks block
// (undesirably), other operations on that socket can still be processed.
var mre = new ManualResetEventSlim();
sendTask = Task.Delay(SendDelayMs)
.ContinueWith(_ => server.SendAsync(new byte[1], SocketFlags.None))
.Unwrap();
Task receiveTask = client
.ReceiveAsync(new byte[1], SocketFlags.None)
.ContinueWith(t => { mre.Set(); return t; }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default)
.Unwrap();
mre.Wait(); // block waiting for other operations on this socket to complete

sendTask.GetAwaiter().GetResult();
await sendTask;
await receiveTask;
});
}
}
}
}

public sealed class SendReceiveMemoryNativeTask : SendReceive<SocketHelperMemoryNativeTask>
{
public SendReceiveMemoryNativeTask(ITestOutputHelper output) : base(output) { }
Expand Down

0 comments on commit 2864610

Please sign in to comment.