public async Task EventQueueTimeout() { using (StartLog(out var loggerFactory)) { var logger = loggerFactory.CreateLogger <HttpConnectionTests>(); var testTransport = new TestTransport(); await WithConnectionAsync( CreateConnection(transport : testTransport), async (connection, closed) => { var onReceived = new SyncPoint(); connection.OnReceived(_ => onReceived.WaitToContinue().OrTimeout()); logger.LogInformation("Starting connection"); await connection.StartAsync().OrTimeout(); logger.LogInformation("Started connection"); await testTransport.Application.Output.WriteAsync(new byte[] { 1 }); await onReceived.WaitForSyncPoint().OrTimeout(); // Dispose should complete, even though the receive callbacks are completely blocked up. logger.LogInformation("Disposing connection"); await connection.DisposeAsync().OrTimeout(TimeSpan.FromSeconds(10)); logger.LogInformation("Disposed connection"); // Clear up blocked tasks. onReceived.Continue(); }); } }
public async Task CanCancelStartingConnectionAfterNegotiate() { using (StartVerifiableLog()) { // Set up a SyncPoint within Negotiate, so we can verify // that the call has gotten that far var negotiateSyncPoint = new SyncPoint(); var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false); testHttpHandler.OnNegotiate(async(request, cancellationToken) => { // Wait here for the test code to cancel the "outer" token await negotiateSyncPoint.WaitToContinue().OrTimeout(); // Cancel cancellationToken.ThrowIfCancellationRequested(); return(ResponseUtils.CreateResponse(HttpStatusCode.OK)); }); await WithConnectionAsync( CreateConnection(testHttpHandler), async (connection) => { // Kick off StartAsync, but don't wait for it var cts = new CancellationTokenSource(); var startTask = connection.StartAsync(cts.Token); // Wait for the connection to get to the "WaitToContinue" call above, // which means it has gotten to Negotiate await negotiateSyncPoint.WaitForSyncPoint().OrTimeout(); // Assert that StartAsync has not yet been canceled Assert.False(startTask.IsCanceled); // Cancel StartAsync, then "release" the SyncPoint // so the negotiate handler can keep going cts.Cancel(); negotiateSyncPoint.Continue(); // Assert that StartAsync was canceled await Assert.ThrowsAsync <OperationCanceledException>(() => startTask).OrTimeout(); }); } }
public async Task EventsAreNotRunningOnMainLoop() { var testTransport = new TestTransport(); await WithConnectionAsync( CreateConnection(transport : testTransport), async (connection, closed) => { // Block up the OnReceived callback until we finish the test. var onReceived = new SyncPoint(); connection.OnReceived(_ => onReceived.WaitToContinue().OrTimeout()); await connection.StartAsync().OrTimeout(); // This will trigger the received callback await testTransport.Application.Output.WriteAsync(new byte[] { 1 }); // Wait to hit the sync point. We are now blocking up the TaskQueue await onReceived.WaitForSyncPoint().OrTimeout(); // Now we write something else and we want to test that the HttpConnection receive loop is still // removing items from the channel even though OnReceived is blocked up. await testTransport.Application.Output.WriteAsync(new byte[] { 1 }); // Now that we've written, we wait for WaitToReadAsync to return an INCOMPLETE task. It will do so // once HttpConnection reads the message. We also use a CTS to timeout in case the loop is indeed blocked var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(5)); while (testTransport.Application.Input.WaitToReadAsync().IsCompleted&& !cts.IsCancellationRequested) { // Yield to allow the HttpConnection to dequeue the message await Task.Yield(); } // If we exited because we were cancelled, throw. cts.Token.ThrowIfCancellationRequested(); // We're free! Unblock onreceived onReceived.Continue(); }); }
public async Task SSETransportCancelsSendOnStop() { var eventStreamTcs = new TaskCompletionSource <object>(); var copyToAsyncTcs = new TaskCompletionSource <object>(); var sendSyncPoint = new SyncPoint(); var mockHttpHandler = new Mock <HttpMessageHandler>(); mockHttpHandler.Protected() .Setup <Task <HttpResponseMessage> >("SendAsync", ItExpr.IsAny <HttpRequestMessage>(), ItExpr.IsAny <CancellationToken>()) .Returns <HttpRequestMessage, CancellationToken>(async(request, cancellationToken) => { await Task.Yield(); if (request.Headers.Accept?.Contains(new MediaTypeWithQualityHeaderValue("text/event-stream")) == true) { // Receive loop started - allow stopping the transport eventStreamTcs.SetResult(null); // returns unfinished task to block pipelines var mockStream = new Mock <Stream>(); mockStream .Setup(s => s.CopyToAsync(It.IsAny <Stream>(), It.IsAny <int>(), It.IsAny <CancellationToken>())) .Returns <Stream, int, CancellationToken>(async(stream, bufferSize, t) => { await copyToAsyncTcs.Task; throw new TaskCanceledException(); }); mockStream.Setup(s => s.CanRead).Returns(true); return(new HttpResponseMessage { Content = new StreamContent(mockStream.Object) }); } // Throw TaskCanceledException from SSE send's SendAsync on stop cancellationToken.Register(s => ((SyncPoint)s).Continue(), sendSyncPoint); await sendSyncPoint.WaitToContinue(); throw new TaskCanceledException(); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) using (StartVerifiableLog()) { var sseTransport = new ServerSentEventsTransport(httpClient, LoggerFactory); await sseTransport.StartAsync( new Uri("http://fakeuri.org"), TransferFormat.Text).OrTimeout(); await eventStreamTcs.Task; await sseTransport.Output.WriteAsync(new byte[] { 0x42 }); // For send request to be in progress await sendSyncPoint.WaitForSyncPoint(); var stopTask = sseTransport.StopAsync(); copyToAsyncTcs.SetResult(null); sendSyncPoint.Continue(); await stopTask; } }