private Task EnsureControlStreamAcceptedAsync() { if (_inboundControlStream != null) { return(Task.CompletedTask); } return(EnsureControlStreamAcceptedInternalAsync()); async Task EnsureControlStreamAcceptedInternalAsync() { Http3LoopbackStream controlStream; while (true) { QuicStream quicStream = await _connection.AcceptInboundStreamAsync().ConfigureAwait(false); if (!quicStream.CanWrite) { // control stream accepted controlStream = new Http3LoopbackStream(quicStream); break; } // control streams are unidirectional, so this must be a request stream // keep it for later and wait for another stream _delayedStreams.Enqueue(quicStream); } long?streamType = await controlStream.ReadIntegerAsync(); Assert.Equal(Http3LoopbackStream.ControlStream, streamType); List <(long settingId, long settingValue)> settings = await controlStream.ReadSettingsAsync(); (long settingId, long settingValue) = Assert.Single(settings); Assert.Equal(Http3LoopbackStream.MaxHeaderListSize, settingId); MaxHeaderListSize = settingValue; _inboundControlStream = controlStream; } }
public override async ValueTask <ConnectionContext?> AcceptAsync(CancellationToken cancellationToken = default) { try { var stream = await _connection.AcceptInboundStreamAsync(cancellationToken); QuicStreamContext?context = null; // Only use pool for bidirectional streams. Just a handful of unidirecitonal // streams are created for a connection and they live for the lifetime of the connection. if (stream.CanRead && stream.CanWrite) { lock (_poolLock) { StreamPool.TryPop(out context); } } if (context == null) { context = new QuicStreamContext(this, _context); context.Initialize(stream); } else { context.ResetFeatureCollection(); context.ResetItems(); context.Initialize(stream); QuicLog.StreamReused(_log, context); } context.Start(); QuicLog.AcceptedStream(_log, context); return(context); } catch (QuicException ex) when(ex.QuicError == QuicError.ConnectionAborted) { // Shutdown initiated by peer, abortive. _error = ex.ApplicationErrorCode; QuicLog.ConnectionAborted(_log, this, ex.ApplicationErrorCode.GetValueOrDefault(), ex); ThreadPool.UnsafeQueueUserWorkItem(state => { state.CancelConnectionClosedToken(); }, this, preferLocal: false); // Throw error so consumer sees the connection is aborted by peer. throw new ConnectionResetException(ex.Message, ex); } catch (QuicException ex) when(ex.QuicError == QuicError.OperationAborted) { lock (_shutdownLock) { // This error should only happen when shutdown has been initiated by the server. // If there is no abort reason and we have this error then the connection is in an // unexpected state. Abort connection and throw reason error. if (_abortReason == null) { Abort(new ConnectionAbortedException("Unexpected error when accepting stream.", ex)); } _abortReason !.Throw(); } } catch (OperationCanceledException) { Debug.Assert(cancellationToken.IsCancellationRequested, "Error requires cancellation is requested."); lock (_shutdownLock) { // Connection has been aborted. Throw reason exception. _abortReason?.Throw(); } } catch (Exception ex) { Debug.Fail($"Unexpected exception in {nameof(QuicConnectionContext)}.{nameof(AcceptAsync)}: {ex}"); throw; } // Return null for graceful closure or cancellation. return(null); }
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; } }