public async Task LibuvThreadDoesNotThrowIfPostingWorkAfterDispose() { var mockConnectionDispatcher = new MockConnectionDispatcher(); var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext() { ConnectionDispatcher = mockConnectionDispatcher }; var transport = new LibuvTransport(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); var ranOne = false; var ranTwo = false; var ranThree = false; var ranFour = false; await thread.StartAsync(); await thread.PostAsync <object>(_ => { ranOne = true; }, null); Assert.Equal(1, mockLibuv.PostCount); // Shutdown the libuv thread await thread.StopAsync(TimeSpan.FromSeconds(5)); Assert.Equal(2, mockLibuv.PostCount); var task = thread.PostAsync <object>(_ => { ranTwo = true; }, null); Assert.Equal(2, mockLibuv.PostCount); thread.Post <object>(_ => { ranThree = true; }, null); Assert.Equal(2, mockLibuv.PostCount); thread.Schedule(_ => { ranFour = true; }, (object)null); Assert.Equal(2, mockLibuv.PostCount); Assert.True(task.IsCompleted); Assert.True(ranOne); Assert.False(ranTwo); Assert.False(ranThree); Assert.False(ranFour); }
public async Task DoesNotThrowIfOnReadCallbackCalledWithEOFButAllocCallbackNotCalled() { var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext(); var thread = new LibuvThread(mockLibuv, transportContext); var listenerContext = new ListenerContext(transportContext) { Thread = thread }; try { await thread.StartAsync(); await thread.PostAsync(_ => { var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); listenerContext.HandleConnection(socket); var ignored = new LibuvFunctions.uv_buf_t(); mockLibuv.ReadCallback(socket.InternalGetHandle(), TestConstants.EOF, ref ignored); }, (object)null); await using var connection = await listenerContext.AcceptAsync(); var readAwaitable = await connection.Transport.Input.ReadAsync(); Assert.True(readAwaitable.IsCompleted); } finally { await thread.StopAsync(TimeSpan.FromSeconds(5)); } }
public async Task DoesNotThrowIfOnReadCallbackCalledWithEOFButAllocCallbackNotCalled() { var mockConnectionDispatcher = new MockConnectionDispatcher(); var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext() { ConnectionDispatcher = mockConnectionDispatcher }; var transport = new LibuvTransport(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); Task connectionTask = null; try { await thread.StartAsync(); await thread.PostAsync(_ => { var listenerContext = new ListenerContext(transportContext) { Thread = thread }; var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); var connection = new LibuvConnection(socket, listenerContext.TransportContext.Log, thread, null, null); listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); connectionTask = connection.Start(); var ignored = new LibuvFunctions.uv_buf_t(); mockLibuv.ReadCallback(socket.InternalGetHandle(), TestConstants.EOF, ref ignored); }, (object)null); var readAwaitable = await mockConnectionDispatcher.Input.Reader.ReadAsync(); Assert.True(readAwaitable.IsCompleted); } finally { mockConnectionDispatcher.Input.Reader.Complete(); mockConnectionDispatcher.Output.Writer.Complete(); await connectionTask; await thread.StopAsync(TimeSpan.FromSeconds(5)); } }
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 builderPrimary = new ConnectionBuilder(); builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); var transportContextPrimary = new TestLibuvTransportContext() { Log = new LibuvTrace(logger) }; transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); var serviceContextSecondary = new TestServiceContext { DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager, ServerOptions = serviceContextPrimary.ServerOptions, Scheduler = serviceContextPrimary.Scheduler, HttpParser = serviceContextPrimary.HttpParser, }; var builderSecondary = new ConnectionBuilder(); builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); var transportContextSecondary = new TestLibuvTransportContext(); transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); 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>(TaskCreationOptions.RunContinuationsAsynchronously); 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(5)); await listenerPrimary.DisposeAsync(); await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); 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 ConnectionDoesNotResumeAfterReadCallbackScheduledAndSocketCloseIfBackpressureIsApplied() { var mockConnectionDispatcher = new MockConnectionDispatcher(); var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext() { ConnectionDispatcher = mockConnectionDispatcher }; 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); }); mockConnectionDispatcher.InputOptions = pool => new PipeOptions( pool: pool, pauseWriterThreshold: 3, resumeWriterThreshold: 3, writerScheduler: mockScheduler.Object, readerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); mockConnectionDispatcher.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(socket, listenerContext.TransportContext.Log, thread, null, null); listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); 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 mockConnectionDispatcher.Input.Reader.ReadAsync(); // Calling advance will call into our custom scheduler that captures the back pressure // callback mockConnectionDispatcher.Input.Reader.AdvanceTo(result.Buffer.End); // Cancel the current pending flush mockConnectionDispatcher.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 mockConnectionDispatcher.Output.Writer.Complete(); await connectionTask.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 NullMaxResponseBufferSizeAllowsUnlimitedBuffer() { var completeQueue = new ConcurrentQueue <Action <int> >(); // Arrange _mockLibuv.OnWrite = (socket, buffers, triggerCompleted) => { completeQueue.Enqueue(triggerCompleted); return(0); }; // ConnectionHandler will set Pause/ResumeWriterThreshold to zero when MaxResponseBufferSize is null. // This is verified in PipeOptionsTests.OutputPipeOptionsConfiguredCorrectly. var pipeOptions = new PipeOptions ( pool: _memoryPool, readerScheduler: _libuvThread, pauseWriterThreshold: 0, resumeWriterThreshold: 0 ); using (var outputProducer = CreateOutputProducer(pipeOptions)) { // Don't want to allocate anything too huge for perf. This is at least larger than the default buffer. var bufferSize = 1024 * 1024; var buffer = new ArraySegment <byte>(new byte[bufferSize], 0, bufferSize); // Act var writeTask = outputProducer.WriteDataAsync(buffer); // Assert await writeTask.TimeoutAfter(TestConstants.DefaultTimeout); // Cleanup outputProducer.Dispose(); // Wait for all writes to complete so the completeQueue isn't modified during enumeration. await _mockLibuv.OnPostTask; // Drain the write queue while (completeQueue.TryDequeue(out var triggerNextCompleted)) { await _libuvThread.PostAsync(cb => cb(0), triggerNextCompleted); } } }
public async Task NonListenerPipeConnectionsAreLoggedAndIgnored() { var libuv = new LibuvFunctions(); var endpoint = new IPEndPoint(IPAddress.Loopback, 0); var logger = new TestApplicationErrorLogger(); var transportContextPrimary = new TestLibuvTransportContext { Log = logger }; var transportContextSecondary = new TestLibuvTransportContext(); var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); var pipeMessage = Guid.NewGuid().ToByteArray(); // Start primary listener var libuvThreadPrimary = new LibuvThread(libuv, transportContextPrimary); await libuvThreadPrimary.StartAsync(); var listenerPrimary = new ListenerPrimary(transportContextPrimary); await listenerPrimary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadPrimary); var address = GetUri(listenerPrimary.EndPoint); // Add secondary listener var libuvThreadSecondary = new LibuvThread(libuv, transportContextSecondary); await libuvThreadSecondary.StartAsync(); var listenerSecondary = new ListenerSecondary(transportContextSecondary); await listenerSecondary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadSecondary); // TCP Connections get round-robined var primary = await WaitForSecondaryListener(address, listenerPrimary, listenerSecondary); // Make sure the pending accept get yields using (var socket = await HttpClientSlim.GetSocket(address)) { await(await primary.DefaultTimeout()).DisposeAsync(); } // Create a pipe connection and keep it open without sending any data var connectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var connectionTrace = 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); }, (object)null); await connectTcs.Task; // TCP connections will still get round-robined between only the two listeners await AssertRoundRobin(address, listenerPrimary, listenerSecondary, listenerPrimary); 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 await AssertRoundRobin(address, listenerPrimary, listenerSecondary, listenerPrimary); await listenerSecondary.DisposeAsync(); await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); await listenerPrimary.DisposeAsync(); await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); Assert.Equal(0, logger.TotalErrorsLogged); var logMessage = logger.Messages.Single(m => m.Message == "An internal pipe was opened unexpectedly."); Assert.Equal(LogLevel.Debug, logMessage.LogLevel); }
static void Main(string[] args) { Console.WriteLine("Main: {0}", Thread.CurrentThread.ManagedThreadId); // Use the factory LibuvTransportContext transport = new LibuvTransportContext { Options = new LibuvTransportOptions(), AppLifetime = new ApplicationLifetime(null), Log = new LibuvTrace(LoggerFactory.Create(builder => { builder.AddConsole(); }).CreateLogger("core")) }; LibuvThread uvThread = new LibuvThread(libUv, transport); uvThread.StartAsync().Wait(); Task.Run(async() => { Console.WriteLine("NIO: {0}", Thread.CurrentThread.ManagedThreadId); LibuvConnectionListener listener = new LibuvConnectionListener(libUv, transport, new IPEndPoint(IPAddress.Parse("0.0.0.0"), 2593)); Console.WriteLine("Binding... ({0})", Thread.CurrentThread.ManagedThreadId); await listener.BindAsync(); Console.WriteLine("Listening... ({0})", Thread.CurrentThread.ManagedThreadId); ConnectionContext connectionContext = await listener.AcceptAsync(); Console.WriteLine("Accepted Connection from {0}", connectionContext.RemoteEndPoint); PipeReader reader = connectionContext.Transport.Input; while (true) { ReadResult readResult = await reader.ReadAsync(); int packetId = readResult.Buffer.FirstSpan[0]; Console.WriteLine($"[0x{packetId:X}] Processing Packet"); int length = PacketLengths[packetId]; int bodyLength = length - 1; int bodyStart = 1; if (length == 0) { length = BinaryPrimitives.ReadUInt16BigEndian(readResult.Buffer.FirstSpan.Slice(1, 2)); bodyLength = length - 3; bodyStart = 3; } else if (length == 0xFFFF) { Console.WriteLine($"[0x{packetId:X}] Unknown Packet"); throw new Exception($"[0x{packetId:X}] Unknown Packet"); } Console.WriteLine($"[0x{packetId:X}] Found length {length}"); Console.WriteLine($"Packet Data: {ByteArrayToString(readResult.Buffer.FirstSpan.ToArray()):X}"); Memory <byte> mem = new Memory <byte>(readResult.Buffer.FirstSpan.Slice(bodyStart, bodyLength).ToArray()); Console.WriteLine($"[0x{packetId:X}] Buffer length {mem.Length}"); _ = uvThread.PostAsync((Tuple <ConnectionContext, Memory <byte> > t) => { // stuff var(conn, mem) = t; // Do stuff wtih memOwner.Memory.Span; Console.WriteLine($"Packet ID: 0x{packetId:X} - Length: {length} - Data: 0x{ByteArrayToString(mem.ToArray())}"); }, Tuple.Create(connectionContext, mem)); reader.AdvanceTo(readResult.Buffer.GetPosition(length)); } }); // Manually putting something on the queue from another thread (or the main thread) uvThread.PostAsync <object>(_ => { Console.WriteLine("Game: {0}", Thread.CurrentThread.ManagedThreadId); }, null); // Send an Initialization Request for Timers /* * uvThread.PostAsync<object>(_ => * { * UvTimerHandle timerHandle = new UvTimerHandle(null); * timerHandle.Init(uvThread.Loop, (callback, handle) => * { * Console.WriteLine("Closed ({0})", Thread.CurrentThread.ManagedThreadId); * }); * Console.WriteLine("Timer Stuff {0}", Thread.CurrentThread.ManagedThreadId); * int count = 10; * void cb2(UvTimerHandle handle) * { * Console.WriteLine("Called!2 {0} ({1})", DateTime.Now, Thread.CurrentThread.ManagedThreadId); * timerHandle.Start(cb2, 2000, 0); * } * void cb1(UvTimerHandle handle) * { * Console.WriteLine("Called!1 {0} ({1})", DateTime.Now, Thread.CurrentThread.ManagedThreadId); * count--; * if (count < 0) * timerHandle.Start(cb2, 2000, 0); * else * timerHandle.Start(cb1, 1000, 0); * } * timerHandle.Start(cb1, 1000, 0); * }, null); */ Console.ReadLine(); }