Skip to content

Commit bdb78f4

Browse files
authored
Fixed GC collecting listener and/or connection while waiting on new connection/stream event (#74740)
1 parent 099304f commit bdb78f4

File tree

5 files changed

+62
-11
lines changed

5 files changed

+62
-11
lines changed

src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ public async ValueTask<QuicStream> AcceptInboundStreamAsync(CancellationToken ca
392392
throw new InvalidOperationException(SR.net_quic_accept_not_allowed);
393393
}
394394

395+
GCHandle keepObject = GCHandle.Alloc(this);
395396
try
396397
{
397398
return await _acceptQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false);
@@ -401,6 +402,10 @@ public async ValueTask<QuicStream> AcceptInboundStreamAsync(CancellationToken ca
401402
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
402403
throw;
403404
}
405+
finally
406+
{
407+
keepObject.Free();
408+
}
404409
}
405410

406411
/// <summary>

src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken c
162162
{
163163
ObjectDisposedException.ThrowIf(_disposed == 1, this);
164164

165+
GCHandle keepObject = GCHandle.Alloc(this);
165166
try
166167
{
167168
PendingConnection pendingConnection = await _acceptQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false);
@@ -175,6 +176,10 @@ public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken c
175176
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
176177
throw;
177178
}
179+
finally
180+
{
181+
keepObject.Free();
182+
}
178183
}
179184

180185
private unsafe int HandleEventNewConnection(ref NEW_CONNECTION_DATA data)

src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,5 +336,33 @@ public async Task Connect_PeerCertificateDisposed(bool useGetter)
336336
}
337337
peerCertificate.Dispose();
338338
}
339+
340+
[Fact]
341+
public async Task Connection_AwaitsStream_ConnectionSurvivesGC()
342+
{
343+
const byte data = 0xDC;
344+
345+
TaskCompletionSource<IPEndPoint> listenerEndpointTcs = new TaskCompletionSource<IPEndPoint>();
346+
await Task.WhenAll(
347+
Task.Run(async () =>
348+
{
349+
await using var listener = await CreateQuicListener();
350+
listenerEndpointTcs.SetResult(listener.LocalEndPoint);
351+
await using var connection = await listener.AcceptConnectionAsync();
352+
await using var stream = await connection.AcceptInboundStreamAsync();
353+
var buffer = new byte[1];
354+
Assert.Equal(1, await stream.ReadAsync(buffer));
355+
Assert.Equal(data, buffer[0]);
356+
}).WaitAsync(TimeSpan.FromSeconds(5)),
357+
Task.Run(async () =>
358+
{
359+
var endpoint = await listenerEndpointTcs.Task;
360+
await using var connection = await CreateQuicConnection(endpoint);
361+
await Task.Delay(TimeSpan.FromSeconds(0.5));
362+
GC.Collect();
363+
await using var stream = await connection.OpenOutboundStreamAsync(QuicStreamType.Unidirectional);
364+
await stream.WriteAsync(new byte[1] { data }, completeWrites: true);
365+
}).WaitAsync(TimeSpan.FromSeconds(5)));
366+
}
339367
}
340368
}

src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public async Task TwoListenersOnSamePort_DisjointAlpn_Success()
106106
QuicListenerOptions listenerOptions = CreateQuicListenerOptions();
107107
listenerOptions.ListenEndPoint = listener1.LocalEndPoint;
108108
listenerOptions.ApplicationProtocols[0] = new SslApplicationProtocol("someprotocol");
109-
listenerOptions.ConnectionOptionsCallback = (_, _, _) =>
109+
listenerOptions.ConnectionOptionsCallback = (_, _, _) =>
110110
{
111111
var options = CreateQuicServerOptions();
112112
options.ServerAuthenticationOptions.ApplicationProtocols[0] = listenerOptions.ApplicationProtocols[0];
@@ -144,5 +144,27 @@ public async Task TwoListenersOnSamePort_SameAlpn_Throws()
144144
//
145145
await AssertThrowsQuicExceptionAsync(QuicError.InternalError, async () => await CreateQuicListener(listener.LocalEndPoint));
146146
}
147+
148+
[Fact]
149+
public async Task Listener_AwaitsConnection_ListenerSurvivesGC()
150+
{
151+
TaskCompletionSource<IPEndPoint> listenerEndpointTcs = new TaskCompletionSource<IPEndPoint>();
152+
await Task.WhenAll(
153+
Task.Run(async () =>
154+
{
155+
await using var listener = await CreateQuicListener();
156+
listenerEndpointTcs.SetResult(listener.LocalEndPoint);
157+
var connection = await listener.AcceptConnectionAsync();
158+
await connection.DisposeAsync();
159+
}).WaitAsync(TimeSpan.FromSeconds(5)),
160+
Task.Run(async () =>
161+
{
162+
var endpoint = await listenerEndpointTcs.Task;
163+
await Task.Delay(TimeSpan.FromSeconds(0.5));
164+
GC.Collect();
165+
var connection = await CreateQuicConnection(endpoint);
166+
await connection.DisposeAsync();
167+
}).WaitAsync(TimeSpan.FromSeconds(5)));
168+
}
147169
}
148170
}

src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ await WhenAllOrAnyFailed(
102102
}
103103
catch (Exception ex)
104104
{
105-
_output?.WriteLine($"Failed to {ex.Message}");
105+
_output?.WriteLine($"Failed to connect: {ex.Message}");
106106
throw;
107107
}
108108
}));
@@ -153,14 +153,5 @@ public override void Dispose()
153153
}
154154
}
155155
}
156-
157-
[OuterLoop("May take several seconds")]
158-
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
159-
[SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")]
160-
[ActiveIssue("https://github.com/dotnet/runtime/issues/73377")]
161-
public override Task Parallel_ReadWriteMultipleStreamsConcurrently()
162-
{
163-
return Task.CompletedTask;
164-
}
165156
}
166157
}

0 commit comments

Comments
 (0)