public async Task ConnectionDoesNotResumeAfterSocketCloseIfBackpressureIsApplied()
        {
            var mockConnectionHandler = new MockConnectionHandler();
            var mockLibuv             = new MockLibuv();
            var transportContext      = new TestLibuvTransportContext()
            {
                ConnectionHandler = mockConnectionHandler
            };
            var transport = new LibuvTransport(mockLibuv, transportContext, null);
            var thread    = new LibuvThread(transport);

            mockConnectionHandler.InputOptions = pool =>
                                                 new PipeOptions(
                pool: pool,
                pauseWriterThreshold: 3,
                readerScheduler: PipeScheduler.Inline,
                writerScheduler: PipeScheduler.Inline,
                useSynchronizationContext: false);

            // We don't set the output writer scheduler here since we want to run the callback inline

            mockConnectionHandler.OutputOptions = pool => new PipeOptions(pool: pool, readerScheduler: thread, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);


            Task connectionTask = null;

            try
            {
                await thread.StartAsync();

                // Write enough to make sure back pressure will be applied
                await thread.PostAsync <object>(_ =>
                {
                    var listenerContext = new ListenerContext(transportContext)
                    {
                        Thread = thread
                    };
                    var socket     = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log);
                    var connection = new LibuvConnection(listenerContext, socket);
                    connectionTask = connection.Start();

                    mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored);
                    mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored);
                }, null);

                // Now assert that we removed the callback from libuv to stop reading
                Assert.Null(mockLibuv.AllocCallback);
                Assert.Null(mockLibuv.ReadCallback);

                // Now complete the output writer so that the connection closes
                mockConnectionHandler.Output.Writer.Complete();

                await connectionTask.TimeoutAfter(TestConstants.DefaultTimeout);

                // Assert that we don't try to start reading
                Assert.Null(mockLibuv.AllocCallback);
                Assert.Null(mockLibuv.ReadCallback);
            }
            finally
            {
                await thread.StopAsync(TimeSpan.FromSeconds(5));
            }
        }
        public async Task ConnectionDoesNotResumeAfterReadCallbackScheduledAndSocketCloseIfBackpressureIsApplied()
        {
            var mockConnectionHandler = new MockConnectionHandler();
            var mockLibuv             = new MockLibuv();
            var transportContext      = new TestLibuvTransportContext()
            {
                ConnectionHandler = mockConnectionHandler
            };
            var    transport     = new LibuvTransport(mockLibuv, transportContext, null);
            var    thread        = new LibuvThread(transport);
            var    mockScheduler = new Mock <PipeScheduler>();
            Action backPressure  = null;

            mockScheduler.Setup(m => m.Schedule(It.IsAny <Action <object> >(), It.IsAny <object>())).Callback <Action <object>, object>((a, o) =>
            {
                backPressure = () => a(o);
            });
            mockConnectionHandler.InputOptions = pool =>
                                                 new PipeOptions(
                pool: pool,
                pauseWriterThreshold: 3,
                resumeWriterThreshold: 3,
                writerScheduler: mockScheduler.Object,
                readerScheduler: PipeScheduler.Inline,
                useSynchronizationContext: false);

            mockConnectionHandler.OutputOptions = pool => new PipeOptions(pool: pool, readerScheduler: thread, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);

            Task connectionTask = null;

            try
            {
                await thread.StartAsync();

                // Write enough to make sure back pressure will be applied
                await thread.PostAsync <object>(_ =>
                {
                    var listenerContext = new ListenerContext(transportContext)
                    {
                        Thread = thread
                    };
                    var socket     = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log);
                    var connection = new LibuvConnection(listenerContext, socket);
                    connectionTask = connection.Start();

                    mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored);
                    mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored);
                }, null);

                // Now assert that we removed the callback from libuv to stop reading
                Assert.Null(mockLibuv.AllocCallback);
                Assert.Null(mockLibuv.ReadCallback);

                // Now release backpressure by reading the input
                var result = await mockConnectionHandler.Input.Reader.ReadAsync();

                // Calling advance will call into our custom scheduler that captures the back pressure
                // callback
                mockConnectionHandler.Input.Reader.AdvanceTo(result.Buffer.End);

                // Cancel the current pending flush
                mockConnectionHandler.Input.Writer.CancelPendingFlush();

                // Now release the back pressure
                await thread.PostAsync(a => a(), backPressure);

                // Assert that we don't try to start reading since the write was cancelled
                Assert.Null(mockLibuv.AllocCallback);
                Assert.Null(mockLibuv.ReadCallback);

                // Now complete the output writer and wait for the connection to close
                mockConnectionHandler.Output.Writer.Complete();

                await connectionTask.TimeoutAfter(TestConstants.DefaultTimeout);

                // Assert that we don't try to start reading
                Assert.Null(mockLibuv.AllocCallback);
                Assert.Null(mockLibuv.ReadCallback);
            }
            finally
            {
                await thread.StopAsync(TimeSpan.FromSeconds(5));
            }
        }
        public async Task ConnectionsGetRoundRobinedToSecondaryListeners()
        {
            var libuv         = new LibuvFunctions();
            var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));

            var serviceContextPrimary   = new TestServiceContext();
            var transportContextPrimary = new TestLibuvTransportContext();

            transportContextPrimary.ConnectionHandler = new ConnectionHandler <HttpContext>(
                listenOptions, serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")));

            var serviceContextSecondary   = new TestServiceContext();
            var transportContextSecondary = new TestLibuvTransportContext();

            transportContextSecondary.ConnectionHandler = new ConnectionHandler <HttpContext>(
                listenOptions, serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")));

            var libuvTransport = new LibuvTransport(libuv, transportContextPrimary, listenOptions);

            var pipeName    = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
            var pipeMessage = Guid.NewGuid().ToByteArray();

            // Start primary listener
            var libuvThreadPrimary = new LibuvThread(libuvTransport);
            await libuvThreadPrimary.StartAsync();

            var listenerPrimary = new ListenerPrimary(transportContextPrimary);
            await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary);

            var address = GetUri(listenOptions);

            // Until a secondary listener is added, TCP connections get dispatched directly
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));

            var listenerCount = listenerPrimary.UvPipeCount;
            // Add secondary listener
            var libuvThreadSecondary = new LibuvThread(libuvTransport);
            await libuvThreadSecondary.StartAsync();

            var listenerSecondary = new ListenerSecondary(transportContextSecondary);
            await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary);

            var maxWait = Task.Delay(TimeSpan.FromSeconds(30));

            // wait for ListenerPrimary.ReadCallback to add the secondary pipe
            while (listenerPrimary.UvPipeCount == listenerCount)
            {
                var completed = await Task.WhenAny(maxWait, Task.Delay(100));

                if (ReferenceEquals(completed, maxWait))
                {
                    throw new TimeoutException("Timed out waiting for secondary listener to become available");
                }
            }

            // Once a secondary listener is added, TCP connections start getting dispatched to it
            await AssertResponseEventually(address, "Secondary", allowed : new[] { "Primary" });

            // TCP connections will still get round-robined to the primary listener
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));

            await listenerSecondary.DisposeAsync();

            await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(1));

            await listenerPrimary.DisposeAsync();

            await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(1));
        }
        public async Task NonListenerPipeConnectionsAreLoggedAndIgnored()
        {
            var libuv         = new LibuvFunctions();
            var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));

            var logger = new TestApplicationErrorLogger();

            var serviceContextPrimary   = new TestServiceContext();
            var transportContextPrimary = new TestLibuvTransportContext()
            {
                Log = new LibuvTrace(logger)
            };

            transportContextPrimary.ConnectionHandler = new ConnectionHandler <HttpContext>(
                listenOptions, serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")));

            var serviceContextSecondary = new TestServiceContext
            {
                DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager,
                ServerOptions          = serviceContextPrimary.ServerOptions,
                ThreadPool             = serviceContextPrimary.ThreadPool,
                HttpParserFactory      = serviceContextPrimary.HttpParserFactory,
            };
            var transportContextSecondary = new TestLibuvTransportContext();

            transportContextSecondary.ConnectionHandler = new ConnectionHandler <HttpContext>(
                listenOptions, serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")));

            var libuvTransport = new LibuvTransport(libuv, transportContextPrimary, listenOptions);

            var pipeName    = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
            var pipeMessage = Guid.NewGuid().ToByteArray();

            // Start primary listener
            var libuvThreadPrimary = new LibuvThread(libuvTransport);
            await libuvThreadPrimary.StartAsync();

            var listenerPrimary = new ListenerPrimary(transportContextPrimary);
            await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary);

            var address = GetUri(listenOptions);

            // Add secondary listener
            var libuvThreadSecondary = new LibuvThread(libuvTransport);
            await libuvThreadSecondary.StartAsync();

            var listenerSecondary = new ListenerSecondary(transportContextSecondary);
            await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary);

            // TCP Connections get round-robined
            await AssertResponseEventually(address, "Secondary", allowed : new[] { "Primary" });

            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));

            // Create a pipe connection and keep it open without sending any data
            var connectTcs      = new TaskCompletionSource <object>();
            var connectionTrace = new LibuvTrace(new TestApplicationErrorLogger());
            var pipe            = new UvPipeHandle(connectionTrace);

            libuvThreadPrimary.Post(_ =>
            {
                var connectReq = new UvConnectRequest(connectionTrace);

                pipe.Init(libuvThreadPrimary.Loop, libuvThreadPrimary.QueueCloseHandle);
                connectReq.Init(libuvThreadPrimary);

                connectReq.Connect(
                    pipe,
                    pipeName,
                    (req, status, ex, __) =>
                {
                    req.Dispose();

                    if (ex != null)
                    {
                        connectTcs.SetException(ex);
                    }
                    else
                    {
                        connectTcs.SetResult(null);
                    }
                },
                    null);
            }, (object)null);

            await connectTcs.Task;

            // TCP connections will still get round-robined between only the two listeners
            Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address));

            await libuvThreadPrimary.PostAsync(_ => pipe.Dispose(), (object)null);

            // Wait up to 10 seconds for error to be logged
            for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++)
            {
                await Task.Delay(100);
            }

            // Same for after the non-listener pipe connection is closed
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));

            await listenerSecondary.DisposeAsync();

            await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(1));

            await listenerPrimary.DisposeAsync();

            await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(1));

            Assert.Equal(1, logger.TotalErrorsLogged);
            var errorMessage = logger.Messages.First(m => m.LogLevel == LogLevel.Error);

            Assert.Equal(TestConstants.EOF, Assert.IsType <UvException>(errorMessage.Exception).StatusCode);
        }
        public async Task PipeConnectionsWithWrongMessageAreLoggedAndIgnored()
        {
            var libuv         = new LibuvFunctions();
            var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));

            var logger = new TestApplicationErrorLogger();

            var serviceContextPrimary   = new TestServiceContext();
            var transportContextPrimary = new TestLibuvTransportContext()
            {
                Log = new LibuvTrace(logger)
            };

            transportContextPrimary.ConnectionHandler = new ConnectionHandler <HttpContext>(
                listenOptions, serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")));

            var serviceContextSecondary = new TestServiceContext
            {
                DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager,
                ServerOptions          = serviceContextPrimary.ServerOptions,
                ThreadPool             = serviceContextPrimary.ThreadPool,
                HttpParserFactory      = serviceContextPrimary.HttpParserFactory,
            };
            var transportContextSecondary = new TestLibuvTransportContext();

            transportContextSecondary.ConnectionHandler = new ConnectionHandler <HttpContext>(
                listenOptions, serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")));

            var libuvTransport = new LibuvTransport(libuv, transportContextPrimary, listenOptions);

            var pipeName    = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
            var pipeMessage = Guid.NewGuid().ToByteArray();

            // Start primary listener
            var libuvThreadPrimary = new LibuvThread(libuvTransport);
            await libuvThreadPrimary.StartAsync();

            var listenerPrimary = new ListenerPrimary(transportContextPrimary);
            await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary);

            var address = GetUri(listenOptions);

            // Add secondary listener with wrong pipe message
            var libuvThreadSecondary = new LibuvThread(libuvTransport);
            await libuvThreadSecondary.StartAsync();

            var listenerSecondary = new ListenerSecondary(transportContextSecondary);
            await listenerSecondary.StartAsync(pipeName, Guid.NewGuid().ToByteArray(), listenOptions, libuvThreadSecondary);

            // Wait up to 10 seconds for error to be logged
            for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++)
            {
                await Task.Delay(100);
            }

            // TCP Connections don't get round-robined
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));

            await listenerSecondary.DisposeAsync();

            await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(1));

            await listenerPrimary.DisposeAsync();

            await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(1));

            Assert.Equal(1, logger.TotalErrorsLogged);
            var errorMessage = logger.Messages.First(m => m.LogLevel == LogLevel.Error);

            Assert.IsType <IOException>(errorMessage.Exception);
            Assert.Contains("Bad data", errorMessage.Exception.ToString());
        }