-
Notifications
You must be signed in to change notification settings - Fork 514
Multiple threads processing connections #145
Changes from all commits
ceeb4ed
42246fd
3d45602
598250a
b93845b
f935567
62ec11b
17a846a
baeb3e7
e39fe60
10bce6a
5e6e5fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| namespace Kestrel | ||
| { | ||
| public interface IKestrelServerInformation | ||
| { | ||
| int ThreadCount { get; set; } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using Microsoft.AspNet.Server.Kestrel.Infrastructure; | ||
| using Microsoft.AspNet.Server.Kestrel.Networking; | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace Microsoft.AspNet.Server.Kestrel.Http | ||
| { | ||
| public class ListenerPrimary : Listener | ||
| { | ||
| UvPipeHandle ListenPipe { get; set; } | ||
|
|
||
| List<UvPipeHandle> _dispatchPipes = new List<UvPipeHandle>(); | ||
| int _dispatchIndex; | ||
| ArraySegment<ArraySegment<byte>> _1234 = new ArraySegment<ArraySegment<byte>>(new[] { new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 }) }); | ||
|
|
||
| public ListenerPrimary(IMemoryPool memory) : base(memory) | ||
| { | ||
| } | ||
|
|
||
| public async Task StartAsync( | ||
| string pipeName, | ||
| string scheme, | ||
| string host, | ||
| int port, | ||
| KestrelThread thread, | ||
| Func<Frame, Task> application) | ||
| { | ||
| await StartAsync(scheme, host, port, thread, application); | ||
|
|
||
| await Thread.PostAsync(_ => | ||
| { | ||
| ListenPipe = new UvPipeHandle(); | ||
| ListenPipe.Init(Thread.Loop, false); | ||
| ListenPipe.Bind(pipeName); | ||
| ListenPipe.Listen(Constants.ListenBacklog, OnListenPipe, null); | ||
| }, null); | ||
| } | ||
|
|
||
| private void OnListenPipe(UvStreamHandle pipe, int status, Exception error, object state) | ||
| { | ||
| if (status < 0) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var dispatchPipe = new UvPipeHandle(); | ||
| dispatchPipe.Init(Thread.Loop, true); | ||
| try | ||
| { | ||
| pipe.Accept(dispatchPipe); | ||
| } | ||
| catch (Exception) | ||
| { | ||
| dispatchPipe.Dispose(); | ||
| return; | ||
| } | ||
| _dispatchPipes.Add(dispatchPipe); | ||
| } | ||
|
|
||
| protected override void DispatchConnection(UvTcpHandle socket) | ||
| { | ||
| var index = _dispatchIndex++ % (_dispatchPipes.Count + 1); | ||
| if (index == _dispatchPipes.Count) | ||
| { | ||
| base.DispatchConnection(socket); | ||
| } | ||
| else | ||
| { | ||
| var dispatchPipe = _dispatchPipes[index]; | ||
| var write = new UvWriteReq(); | ||
| write.Init(Thread.Loop); | ||
| write.Write2( | ||
| dispatchPipe, | ||
| _1234, | ||
| socket, | ||
| (write2, status, error, state) => | ||
| { | ||
| write2.Dispose(); | ||
| ((UvTcpHandle)state).Dispose(); | ||
| }, | ||
| socket); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using Microsoft.AspNet.Server.Kestrel.Networking; | ||
| using System; | ||
| using System.Diagnostics; | ||
| using System.Runtime.InteropServices; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace Microsoft.AspNet.Server.Kestrel.Http | ||
| { | ||
| public class ListenerSecondary : ListenerContext, IDisposable | ||
| { | ||
| UvPipeHandle DispatchPipe { get; set; } | ||
|
|
||
| public ListenerSecondary(IMemoryPool memory) | ||
| { | ||
| Memory = memory; | ||
| } | ||
|
|
||
| public Task StartAsync( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I have convinced myself, that this code does not have a resource leak in the exceptional cases, but it's really, really hard to get to that conclusion. I know it's asynchronous code, but is there any way to streamline the control flow?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm sure there is - I'll file that as another issue in the interest of getting this PR merged. |
||
| string pipeName, | ||
| KestrelThread thread, | ||
| Func<Frame, Task> application) | ||
| { | ||
| Thread = thread; | ||
| Application = application; | ||
|
|
||
| DispatchPipe = new UvPipeHandle(); | ||
|
|
||
| var tcs = new TaskCompletionSource<int>(); | ||
| Thread.Post(_ => | ||
| { | ||
| try | ||
| { | ||
| DispatchPipe.Init(Thread.Loop, true); | ||
| var connect = new UvConnectRequest(); | ||
| connect.Init(Thread.Loop); | ||
| connect.Connect( | ||
| DispatchPipe, | ||
| pipeName, | ||
| (connect2, status, error, state) => | ||
| { | ||
| connect.Dispose(); | ||
| if (error != null) | ||
| { | ||
| tcs.SetException(error); | ||
| return; | ||
| } | ||
|
|
||
| try | ||
| { | ||
| var ptr = Marshal.AllocHGlobal(16); | ||
| var buf = Thread.Loop.Libuv.buf_init(ptr, 16); | ||
|
|
||
| DispatchPipe.ReadStart( | ||
| (_1, _2, _3) => buf, | ||
| (_1, status2, error2, state2) => | ||
| { | ||
| if (status2 == 0) | ||
| { | ||
| DispatchPipe.Dispose(); | ||
| Marshal.FreeHGlobal(ptr); | ||
| return; | ||
| } | ||
|
|
||
| var acceptSocket = new UvTcpHandle(); | ||
| acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle); | ||
|
|
||
| try | ||
| { | ||
| DispatchPipe.Accept(acceptSocket); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Trace.WriteLine("DispatchPipe.Accept " + ex.Message); | ||
| acceptSocket.Dispose(); | ||
| return; | ||
| } | ||
|
|
||
| var connection = new Connection(this, acceptSocket); | ||
| connection.Start(); | ||
| }, | ||
| null); | ||
|
|
||
| tcs.SetResult(0); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| DispatchPipe.Dispose(); | ||
| tcs.SetException(ex); | ||
| } | ||
| }, | ||
| null); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| DispatchPipe.Dispose(); | ||
| tcs.SetException(ex); | ||
| } | ||
| }, null); | ||
| return tcs.Task; | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| // Ensure the event loop is still running. | ||
| // If the event loop isn't running and we try to wait on this Post | ||
| // to complete, then KestrelEngine will never be disposed and | ||
| // the exception that stopped the event loop will never be surfaced. | ||
| if (Thread.FatalError == null) | ||
| { | ||
| Thread.Send(_ => DispatchPipe.Dispose(), null); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace Microsoft.AspNet.Server.Kestrel.Infrastructure | ||
| { | ||
| internal class Constants | ||
| { | ||
| public const int ListenBacklog = 128; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,10 +38,10 @@ public KestrelEngine(ILibraryManager libraryManager, IApplicationShutdown appShu | |
| : "amd64"; | ||
|
|
||
| libraryPath = Path.Combine( | ||
| libraryPath, | ||
| libraryPath, | ||
| "native", | ||
| "windows", | ||
| architecture, | ||
| architecture, | ||
| "libuv.dll"); | ||
| } | ||
| else if (Libuv.IsDarwin) | ||
|
|
@@ -91,16 +91,37 @@ public void Dispose() | |
|
|
||
| public IDisposable CreateServer(string scheme, string host, int port, Func<Frame, Task> application) | ||
| { | ||
| var listeners = new List<Listener>(); | ||
| var listeners = new List<IDisposable>(); | ||
|
|
||
| try | ||
| { | ||
| var pipeName = (Libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've verified that both pipeName's work on OS X. I think the logic where it defaults to "/tmp/..." for non-Windows OSs is good. |
||
|
|
||
| var single = Threads.Count == 1; | ||
| var first = true; | ||
|
|
||
| foreach (var thread in Threads) | ||
| { | ||
| var listener = new Listener(Memory); | ||
| if (single) | ||
| { | ||
| var listener = new Listener(Memory); | ||
| listeners.Add(listener); | ||
| listener.StartAsync(scheme, host, port, thread, application).Wait(); | ||
| } | ||
| else if (first) | ||
| { | ||
| var listener = new ListenerPrimary(Memory); | ||
| listeners.Add(listener); | ||
| listener.StartAsync(pipeName, scheme, host, port, thread, application).Wait(); | ||
| } | ||
| else | ||
| { | ||
| var listener = new ListenerSecondary(Memory); | ||
| listeners.Add(listener); | ||
| listener.StartAsync(pipeName, thread, application).Wait(); | ||
| } | ||
|
|
||
| listeners.Add(listener); | ||
| listener.StartAsync(scheme, host, port, thread, application).Wait(); | ||
| first = false; | ||
| } | ||
| return new Disposable(() => | ||
| { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we dispose this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's done in the callback below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I now see that dispose is already being called in the callback. Thanks @loudej
I leave this here as a reminder to call write2.Dispose() to avoid the closure allocation.