public override async ValueTask DisposeAsync() { // Close any remaining request streams (but NOT control streams, as these should not be closed while the connection is open) foreach (Http3LoopbackStream stream in _openStreams.Values) { await stream.DisposeAsync().ConfigureAwait(false); } foreach (QuicStream stream in _delayedStreams) { await stream.DisposeAsync().ConfigureAwait(false); } // Dispose the connection // If we already waited for graceful shutdown from the client, then the connection is already closed and this will simply release the handle. // If not, then this will silently abort the connection. await _connection.DisposeAsync(); // Dispose control streams so that we release their handles too. if (_inboundControlStream is not null) { await _inboundControlStream.DisposeAsync().ConfigureAwait(false); } if (_outboundControlStream is not null) { await _outboundControlStream.DisposeAsync().ConfigureAwait(false); } }
/// <summary> /// Called when shutting down, this checks for when shutdown is complete (no more active requests) and does actual disposal. /// </summary> /// <remarks>Requires <see cref="SyncObj"/> to be locked.</remarks> private void CheckForShutdown() { Debug.Assert(Monitor.IsEntered(SyncObj)); Debug.Assert(ShuttingDown); if (_activeRequests.Count != 0) { return; } if (_connection != null) { // Close the QuicConnection in the background. _connectionClosedTask ??= _connection.CloseAsync((long)Http3ErrorCode.NoError).AsTask(); QuicConnection connection = _connection; _connection = null; _ = _connectionClosedTask.ContinueWith(async closeTask => { if (closeTask.IsFaulted && NetEventSource.Log.IsEnabled()) { Trace($"{nameof(QuicConnection)} failed to close: {closeTask.Exception!.InnerException}"); } try { await connection.DisposeAsync().ConfigureAwait(false); } catch (Exception ex) { Trace($"{nameof(QuicConnection)} failed to dispose: {ex}"); } if (_clientControl != null) { _clientControl.Dispose(); _clientControl = null; } }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); if (HttpTelemetry.Log.IsEnabled()) { if (Interlocked.Exchange(ref _markedByTelemetryStatus, TelemetryStatus_Closed) == TelemetryStatus_Opened) { HttpTelemetry.Log.Http30ConnectionClosed(); } } } }
public override async ValueTask DisposeAsync() { try { lock (_shutdownLock) { _closeTask ??= _connection.CloseAsync(errorCode: _context.Options.DefaultCloseErrorCode).AsTask(); } await _closeTask; } catch (Exception ex) { _log.LogWarning(ex, "Failed to gracefully shutdown connection."); } await _connection.DisposeAsync(); }
public async Task Connect_PeerCertificateDisposed(bool useGetter) { await using QuicListener listener = await CreateQuicListener(); QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); X509Certificate? peerCertificate = null; clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { peerCertificate = certificate; return(true); }; ValueTask <QuicConnection> connectTask = CreateQuicConnection(clientOptions); ValueTask <QuicConnection> acceptTask = listener.AcceptConnectionAsync(); await new Task[] { connectTask.AsTask(), acceptTask.AsTask() }.WhenAllOrAnyFailed(PassingTestTimeoutMilliseconds); await using QuicConnection serverConnection = acceptTask.Result; QuicConnection clientConnection = connectTask.Result; Assert.NotNull(peerCertificate); if (useGetter) { Assert.Equal(peerCertificate, clientConnection.RemoteCertificate); } // Dispose connection, if we touched RemoteCertificate (useGetter), the cert should not be disposed; otherwise, it should be disposed. await clientConnection.DisposeAsync(); if (useGetter) { Assert.NotEqual(IntPtr.Zero, peerCertificate.Handle); } else { Assert.Equal(IntPtr.Zero, peerCertificate.Handle); } peerCertificate.Dispose(); }
/// <summary> /// Creates a new <see cref="QuicConnection"/> and connects it to the peer. /// </summary> /// <param name="options">Options for the connection.</param> /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param> /// <returns>An asynchronous task that completes with the connected connection.</returns> public static async ValueTask <QuicConnection> ConnectAsync(QuicClientConnectionOptions options, CancellationToken cancellationToken = default) { if (!IsSupported) { throw new PlatformNotSupportedException(SR.SystemNetQuic_PlatformNotSupported); } // Validate and fill in defaults for the options. options.Validate(nameof(options)); QuicConnection connection = new QuicConnection(); try { await connection.FinishConnectAsync(options, cancellationToken).ConfigureAwait(false); } catch { await connection.DisposeAsync().ConfigureAwait(false); throw; } return(connection); }
protected override async Task <StreamPair> CreateConnectedStreamsAsync() { var listener = await QuicListener.ListenAsync(new QuicListenerOptions() { ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), ApplicationProtocols = new List <SslApplicationProtocol>() { new SslApplicationProtocol("quictest") }, ConnectionOptionsCallback = (_, _, _) => ValueTask.FromResult(new QuicServerConnectionOptions() { DefaultStreamErrorCode = QuicTestBase.DefaultStreamErrorCodeServer, DefaultCloseErrorCode = QuicTestBase.DefaultCloseErrorCodeServer, ServerAuthenticationOptions = GetSslServerAuthenticationOptions() }) }); byte[] buffer = new byte[1] { 42 }; QuicConnection connection1 = null, connection2 = null; QuicStream stream1 = null, stream2 = null; try { await WhenAllOrAnyFailed( Task.Run(async() => { connection1 = await listener.AcceptConnectionAsync(); stream1 = await connection1.AcceptInboundStreamAsync(); Assert.Equal(1, await stream1.ReadAsync(buffer)); }), Task.Run(async() => { try { connection2 = await QuicConnection.ConnectAsync(new QuicClientConnectionOptions() { DefaultStreamErrorCode = QuicTestBase.DefaultStreamErrorCodeClient, DefaultCloseErrorCode = QuicTestBase.DefaultCloseErrorCodeClient, RemoteEndPoint = listener.LocalEndPoint, ClientAuthenticationOptions = GetSslClientAuthenticationOptions() }); stream2 = await connection2.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); // OpenBidirectionalStream only allocates ID. We will force stream opening // by Writing there and receiving data on the other side. await stream2.WriteAsync(buffer); } catch (Exception ex) { _output?.WriteLine($"Failed to {ex.Message}"); throw; } })); // No need to keep the listener once we have connected connection and streams await listener.DisposeAsync(); var result = new StreamPairWithOtherDisposables(stream1, stream2); result.Disposables.Add(connection1); result.Disposables.Add(connection2); return(result); } catch { if (stream1 is not null) { await stream1.DisposeAsync(); } if (stream2 is not null) { await stream2.DisposeAsync(); } if (connection1 is not null) { await connection1.DisposeAsync(); } if (connection2 is not null) { await connection2.DisposeAsync(); } throw; } }