public override ValueTask <ConnectionContext> ConnectAsync(IFeatureCollection?features = null, CancellationToken cancellationToken = default) { QuicStream quicStream; var streamDirectionFeature = features?.Get <IStreamDirectionFeature>(); if (streamDirectionFeature != null) { if (streamDirectionFeature.CanRead) { quicStream = _connection.OpenBidirectionalStream(); } else { quicStream = _connection.OpenUnidirectionalStream(); } } else { quicStream = _connection.OpenBidirectionalStream(); } // Only a handful of control streams are created by the server and they last for the // lifetime of the connection. No value in pooling them. QuicStreamContext?context = new QuicStreamContext(this, _context); context.Initialize(quicStream); context.Start(); QuicLog.ConnectedStream(_log, context); return(new ValueTask <ConnectionContext>(context)); }
public static void StreamPause(ILogger logger, QuicStreamContext streamContext) { if (logger.IsEnabled(LogLevel.Debug)) { StreamPauseCore(logger, streamContext.ConnectionId); } }
public static void ConnectedStream(ILogger logger, QuicStreamContext streamContext) { if (logger.IsEnabled(LogLevel.Debug)) { ConnectedStreamCore(logger, streamContext.ConnectionId, GetStreamType(streamContext)); } }
public static void StreamError(ILogger logger, QuicStreamContext streamContext, Exception ex) { if (logger.IsEnabled(LogLevel.Debug)) { StreamErrorCore(logger, streamContext.ConnectionId, ex); } }
public static void StreamAbortWrite(ILogger logger, QuicStreamContext streamContext, long errorCode, string reason) { if (logger.IsEnabled(LogLevel.Debug)) { StreamAbortWriteCore(logger, streamContext.ConnectionId, errorCode, reason); } }
public static void StreamAbortedRead(ILogger logger, QuicStreamContext streamContext, long errorCode) { if (logger.IsEnabled(LogLevel.Debug)) { StreamAbortedReadCore(logger, streamContext.ConnectionId, errorCode); } }
public static void StreamShutdownWrite(ILogger logger, QuicStreamContext streamContext, string reason) { if (logger.IsEnabled(LogLevel.Debug)) { StreamShutdownWriteCore(logger, streamContext.ConnectionId, reason); } }
public static void StreamReused(ILogger logger, QuicStreamContext streamContext) { if (logger.IsEnabled(LogLevel.Trace)) { StreamReusedCore(logger, streamContext.ConnectionId); } }
public QuicStream(QuicConnection connection, StreamId streamId) { StreamId = streamId; Type = streamId.Type; _connection = connection; Context = new QuicStreamContext(this, _connection.Context); }
static void Main(string[] args) { QuicClient client = new QuicClient(); QuicContext context = client.Connect("127.0.0.1", 11000); // Connect to peer (Server) QuicStreamContext sc = client.CreateStream(); // Create a data stream sc.Send(Encoding.UTF8.GetBytes("Hello from Client!")); // Send Data sc.Close(); // Close the stream after processing }
//private static void Listener_OnClientConnected(QuicContext obj) //{ // System.Console.WriteLine("Client connected."); // obj.OnDataReceived += Obj_OnDataReceived; //} private static void Obj_OnDataReceived(QuicStreamContext obj) { System.Console.WriteLine("Data received"); foreach (byte b in obj.Data) { System.Console.Write(string.Format("{0},", b)); } // Echo back to the client obj.Send(Encoding.UTF8.GetBytes("Echo!")); }
internal bool TryReturnStream(QuicStreamContext stream) { lock (_poolLock) { if (!_streamPoolHeartbeatInitialized) { // Heartbeat feature is added to connection features by Kestrel. // No event is on the context is raised between feature being added and serving // connections so initialize heartbeat the first time a stream is added to // the connection's stream pool. var heartbeatFeature = Features.Get <IConnectionHeartbeatFeature>(); if (heartbeatFeature == null) { throw new InvalidOperationException($"Required {nameof(IConnectionHeartbeatFeature)} not found in connection features."); } heartbeatFeature.OnHeartbeat(static state => ((QuicConnectionContext)state).RemoveExpiredStreams(), this);
public override async ValueTask <ConnectionContext?> AcceptAsync(CancellationToken cancellationToken = default) { try { var stream = await _connection.AcceptStreamAsync(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); } else { context.ResetFeatureCollection(); context.ResetItems(); } context.Initialize(stream); context.Start(); QuicLog.AcceptedStream(_log, context); return(context); } catch (QuicConnectionAbortedException ex) { // Shutdown initiated by peer, abortive. _error = ex.ErrorCode; QuicLog.ConnectionAborted(_log, this, ex.ErrorCode, 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 (QuicOperationAbortedException ex) { 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); }
public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() { // Arrange var now = new DateTimeOffset(2021, 7, 6, 12, 0, 0, TimeSpan.Zero); var testSystemClock = new TestSystemClock { UtcNow = now }; await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory, testSystemClock); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); using var clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options); await clientConnection.ConnectAsync().DefaultTimeout(); await using var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout(); var testHeartbeatFeature = new TestHeartbeatFeature(); serverConnection.Features.Set <IConnectionHeartbeatFeature>(testHeartbeatFeature); // Act & Assert var quicConnectionContext = Assert.IsType <QuicConnectionContext>(serverConnection); Assert.Equal(0, quicConnectionContext.StreamPool.Count); var stream1 = await QuicTestHelpers.CreateAndCompleteBidirectionalStreamGracefully(clientConnection, serverConnection); Assert.Equal(1, quicConnectionContext.StreamPool.Count); QuicStreamContext pooledStream = quicConnectionContext.StreamPool._array[0]; Assert.Same(stream1, pooledStream); Assert.Equal(now.Ticks + QuicConnectionContext.StreamPoolExpiryTicks, pooledStream.PoolExpirationTicks); now = now.AddMilliseconds(100); testSystemClock.UtcNow = now; testHeartbeatFeature.RaiseHeartbeat(); // Not removed. Assert.Equal(1, quicConnectionContext.StreamPool.Count); var stream2 = await QuicTestHelpers.CreateAndCompleteBidirectionalStreamGracefully(clientConnection, serverConnection); Assert.Equal(1, quicConnectionContext.StreamPool.Count); pooledStream = quicConnectionContext.StreamPool._array[0]; Assert.Same(stream1, pooledStream); Assert.Equal(now.Ticks + QuicConnectionContext.StreamPoolExpiryTicks, pooledStream.PoolExpirationTicks); Assert.Same(stream1, stream2); now = now.AddTicks(QuicConnectionContext.StreamPoolExpiryTicks); testSystemClock.UtcNow = now; testHeartbeatFeature.RaiseHeartbeat(); // Not removed. Assert.Equal(1, quicConnectionContext.StreamPool.Count); now = now.AddTicks(1); testSystemClock.UtcNow = now; testHeartbeatFeature.RaiseHeartbeat(); // Removed. Assert.Equal(0, quicConnectionContext.StreamPool.Count); }
private static StreamType GetStreamType(QuicStreamContext streamContext) => streamContext.CanRead && streamContext.CanWrite ? StreamType.Bidirectional : StreamType.Unidirectional;