Пример #1
0
        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(TransferFormat.Text).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();
                });
            }
        }
Пример #2
0
            /// <summary>
            /// Creates a re-entrant function that waits for sync points in sequence.
            /// </summary>
            /// <param name="count">The number of sync points to expect</param>
            /// <param name="syncPoints">The <see cref="SyncPoint"/> objects that can be used to coordinate the sync point</param>
            /// <returns></returns>
            public static Func <Task> Create(int count, out SyncPoint[] syncPoints)
            {
                // Need to use a local so the closure can capture it. You can't use out vars in a closure.
                var localSyncPoints = new SyncPoint[count];

                for (var i = 0; i < count; i += 1)
                {
                    localSyncPoints[i] = new SyncPoint();
                }

                syncPoints = localSyncPoints;

                var counter = 0;

                return(() =>
                {
                    if (counter >= localSyncPoints.Length)
                    {
                        return Task.CompletedTask;
                    }
                    else
                    {
                        var syncPoint = localSyncPoints[counter];

                        counter += 1;
                        return syncPoint.WaitToContinue();
                    }
                });
            }
Пример #3
0
            public static Func <Task> Create(out SyncPoint syncPoint)
            {
                var handler = Create(1, out var syncPoints);

                syncPoint = syncPoints[0];
                return(handler);
            }
            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();
                    });
                }
            }
Пример #5
0
        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;
                }
        }
            public Task StopAsyncWhileAbortingTriggersClosedEventWithoutException()
            {
                return(WithConnectionAsync(CreateConnection(transport: new TestTransport(onTransportStop: SyncPoint.Create(2, out var syncPoints))), async(connection, closed) =>
                {
                    // Start the connection
                    await connection.StartAsync(TransferFormat.Text).OrTimeout();

                    // Abort with an error
                    var expected = new Exception("Ruh roh!");
                    var abortTask = connection.AbortAsync(expected).OrTimeout();

                    // Wait to reach the first sync point
                    await syncPoints[0].WaitForSyncPoint().OrTimeout();

                    // Stop normally, without a sync point.
                    // This should clear the exception, meaning Closed will not "throw"
                    syncPoints[1].Continue();
                    await connection.StopAsync();
                    await closed.OrTimeout();

                    // Clean-up
                    syncPoints[0].Continue();
                    await abortTask.OrTimeout();
                }));
            }
            public Task AbortAsyncWhileStoppingTriggersClosedEventWithException()
            {
                return(WithConnectionAsync(CreateConnection(transport: new TestTransport(onTransportStop: SyncPoint.Create(2, out var syncPoints))), async(connection, closed) =>
                {
                    // Start the connection
                    await connection.StartAsync(TransferFormat.Text).OrTimeout();

                    // Stop normally
                    var stopTask = connection.StopAsync().OrTimeout();

                    // Wait to reach the first sync point
                    await syncPoints[0].WaitForSyncPoint().OrTimeout();

                    // Abort with an error
                    var expected = new Exception("Ruh roh!");
                    var abortTask = connection.AbortAsync(expected).OrTimeout();

                    // Wait for the sync point to hit again
                    await syncPoints[1].WaitForSyncPoint().OrTimeout();

                    // Release sync point 0
                    syncPoints[0].Continue();

                    // We should close with the error from Abort (because it was set by the call to Abort even though Stop triggered the close)
                    var actual = await Assert.ThrowsAsync <Exception>(async() => await closed.OrTimeout());
                    Assert.Same(expected, actual);

                    // Clean-up
                    syncPoints[1].Continue();
                    await Task.WhenAll(stopTask, abortTask).OrTimeout();
                }));
            }