@@ -98,6 +98,11 @@ static async ValueTask<QuicConnection> StartConnectAsync(QuicClientConnectionOpt
98
98
/// </summary>
99
99
private int _disposed ;
100
100
101
+ /// <summary>
102
+ /// Completed when connection shutdown is initiated.
103
+ /// </summary>
104
+ private TaskCompletionSource _connectionCloseTcs = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
105
+
101
106
private readonly ValueTaskSource _connectedTcs = new ValueTaskSource ( ) ;
102
107
private readonly ValueTaskSource _shutdownTcs = new ValueTaskSource ( ) ;
103
108
@@ -376,16 +381,25 @@ public async ValueTask<QuicStream> OpenOutboundStreamAsync(QuicStreamType type,
376
381
stream = new QuicStream ( _handle , type , _defaultStreamErrorCode ) ;
377
382
await stream . StartAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
378
383
}
379
- catch
384
+ catch ( Exception ex )
380
385
{
381
386
if ( stream is not null )
382
387
{
383
388
await stream . DisposeAsync ( ) . ConfigureAwait ( false ) ;
384
389
}
390
+
391
+ // Propagate ODE if disposed in the meantime.
392
+ ObjectDisposedException . ThrowIf ( _disposed == 1 , this ) ;
393
+
394
+ // In case of an incoming race when the connection is closed by the peer just before we open the stream,
395
+ // we receive QUIC_STATUS_ABORTED from MsQuic, but we don't know how the connection was closed. We throw
396
+ // special exception and handle it here where we can determine the shutdown reason.
397
+ bool connectionAbortedByPeer = ThrowHelper . IsConnectionAbortedWhenStartingStreamException ( ex ) ;
398
+
385
399
// Propagate connection error if present.
386
- if ( _acceptQueue . Reader . Completion . IsFaulted )
400
+ if ( _connectionCloseTcs . Task . IsFaulted || connectionAbortedByPeer )
387
401
{
388
- await _acceptQueue . Reader . Completion . ConfigureAwait ( false ) ;
402
+ await _connectionCloseTcs . Task . ConfigureAwait ( false ) ;
389
403
}
390
404
throw ;
391
405
}
@@ -475,17 +489,21 @@ private unsafe int HandleEventShutdownInitiatedByTransport(ref SHUTDOWN_INITIATE
475
489
{
476
490
Exception exception = ExceptionDispatchInfo . SetCurrentStackTrace ( ThrowHelper . GetExceptionForMsQuicStatus ( data . Status , ( long ) data . ErrorCode ) ) ;
477
491
_connectedTcs . TrySetException ( exception ) ;
492
+ _connectionCloseTcs . TrySetException ( exception ) ;
478
493
_acceptQueue . Writer . TryComplete ( exception ) ;
479
494
return QUIC_STATUS_SUCCESS ;
480
495
}
481
496
private unsafe int HandleEventShutdownInitiatedByPeer ( ref SHUTDOWN_INITIATED_BY_PEER_DATA data )
482
497
{
483
- _acceptQueue . Writer . TryComplete ( ExceptionDispatchInfo . SetCurrentStackTrace ( ThrowHelper . GetConnectionAbortedException ( ( long ) data . ErrorCode ) ) ) ;
498
+ Exception exception = ExceptionDispatchInfo . SetCurrentStackTrace ( ThrowHelper . GetConnectionAbortedException ( ( long ) data . ErrorCode ) ) ;
499
+ _connectionCloseTcs . TrySetException ( exception ) ;
500
+ _acceptQueue . Writer . TryComplete ( exception ) ;
484
501
return QUIC_STATUS_SUCCESS ;
485
502
}
486
503
private unsafe int HandleEventShutdownComplete ( )
487
504
{
488
- Exception exception = ExceptionDispatchInfo . SetCurrentStackTrace ( ThrowHelper . GetOperationAbortedException ( ) ) ;
505
+ Exception exception = ExceptionDispatchInfo . SetCurrentStackTrace ( _disposed == 1 ? new ObjectDisposedException ( GetType ( ) . FullName ) : ThrowHelper . GetOperationAbortedException ( ) ) ;
506
+ _connectionCloseTcs . TrySetException ( exception ) ;
489
507
_acceptQueue . Writer . TryComplete ( exception ) ;
490
508
_connectedTcs . TrySetException ( exception ) ;
491
509
_shutdownTcs . TrySetResult ( ) ;
0 commit comments