Skip to content

Commit 2da561c

Browse files
committed
Garbage collecting safe handles need to queue the uv_close
All of the uv_* calls must be called on the original thread the finalizer thread cleaning up safehandle classes needs special handling see #16
1 parent 836be55 commit 2da561c

File tree

10 files changed

+149
-33
lines changed

10 files changed

+149
-33
lines changed

src/Microsoft.AspNet.Server.Kestrel/Http/Listener.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public Task StartAsync(
6969
try
7070
{
7171
ListenSocket = new UvTcpHandle();
72-
ListenSocket.Init(Thread.Loop);
72+
ListenSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
7373
ListenSocket.Bind(new IPEndPoint(IPAddress.Any, port));
7474
ListenSocket.Listen(10, _connectionCallback, this);
7575
tcs.SetResult(0);
@@ -85,7 +85,7 @@ public Task StartAsync(
8585
private void OnConnection(UvStreamHandle listenSocket, int status)
8686
{
8787
var acceptSocket = new UvTcpHandle();
88-
acceptSocket.Init(Thread.Loop);
88+
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
8989
listenSocket.Accept(acceptSocket);
9090

9191
var connection = new Connection(this, acceptSocket);

src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.AspNet.Server.Kestrel.Networking;
55
using System;
66
using System.Collections.Generic;
7+
using System.Diagnostics;
78
using System.Runtime.ExceptionServices;
89
using System.Threading;
910
using System.Threading.Tasks;
@@ -21,6 +22,8 @@ public class KestrelThread
2122
UvAsyncHandle _post;
2223
Queue<Work> _workAdding = new Queue<Work>();
2324
Queue<Work> _workRunning = new Queue<Work>();
25+
Queue<CloseHandle> _closeHandleAdding = new Queue<CloseHandle>();
26+
Queue<CloseHandle> _closeHandleRunning = new Queue<CloseHandle>();
2427
object _workSync = new Object();
2528
bool _stopImmediate = false;
2629
private ExceptionDispatchInfo _closeError;
@@ -31,10 +34,13 @@ public KestrelThread(KestrelEngine engine)
3134
_loop = new UvLoopHandle();
3235
_post = new UvAsyncHandle();
3336
_thread = new Thread(ThreadStart);
37+
QueueCloseHandle = PostCloseHandle;
3438
}
3539

3640
public UvLoopHandle Loop { get { return _loop; } }
3741

42+
public Action<Action<IntPtr>, IntPtr> QueueCloseHandle { get; internal set; }
43+
3844
public Task StartAsync()
3945
{
4046
var tcs = new TaskCompletionSource<int>();
@@ -92,6 +98,15 @@ public Task PostAsync(Action<object> callback, object state)
9298
return tcs.Task;
9399
}
94100

101+
private void PostCloseHandle(Action<IntPtr> callback, IntPtr handle)
102+
{
103+
lock (_workSync)
104+
{
105+
_closeHandleAdding.Enqueue(new CloseHandle { Callback = callback, Handle = handle });
106+
}
107+
_post.Send();
108+
}
109+
95110
private void ThreadStart(object parameter)
96111
{
97112
var tcs = (TaskCompletionSource<int>)parameter;
@@ -119,7 +134,7 @@ private void ThreadStart(object parameter)
119134
_post.Reference();
120135
_post.DangerousClose();
121136
_engine.Libuv.walk(
122-
_loop,
137+
_loop,
123138
(ptr, arg) =>
124139
{
125140
var handle = UvMemory.FromIntPtr<UvHandle>(ptr);
@@ -138,12 +153,19 @@ private void ThreadStart(object parameter)
138153

139154
private void OnPost()
140155
{
141-
var queue = _workAdding;
156+
DoPostWork();
157+
DoPostCloseHandle();
158+
}
159+
160+
private void DoPostWork()
161+
{
162+
Queue<Work> queue;
142163
lock (_workSync)
143164
{
165+
queue = _workAdding;
144166
_workAdding = _workRunning;
167+
_workRunning = queue;
145168
}
146-
_workRunning = queue;
147169
while (queue.Count != 0)
148170
{
149171
var work = queue.Dequeue();
@@ -156,7 +178,7 @@ private void OnPost()
156178
tcs =>
157179
{
158180
((TaskCompletionSource<int>)tcs).SetResult(0);
159-
},
181+
},
160182
work.Completion);
161183
}
162184
}
@@ -168,17 +190,44 @@ private void OnPost()
168190
}
169191
else
170192
{
171-
// TODO: unobserved exception?
193+
Trace.WriteLine("KestrelThread.DoPostWork " + ex.ToString());
172194
}
173195
}
174196
}
175197
}
198+
private void DoPostCloseHandle()
199+
{
200+
Queue<CloseHandle> queue;
201+
lock (_workSync)
202+
{
203+
queue = _closeHandleAdding;
204+
_closeHandleAdding = _closeHandleRunning;
205+
_closeHandleRunning = queue;
206+
}
207+
while (queue.Count != 0)
208+
{
209+
var closeHandle = queue.Dequeue();
210+
try
211+
{
212+
closeHandle.Callback(closeHandle.Handle);
213+
}
214+
catch (Exception ex)
215+
{
216+
Trace.WriteLine("KestrelThread.DoPostCloseHandle " + ex.ToString());
217+
}
218+
}
219+
}
176220

177221
private struct Work
178222
{
179223
public Action<object> Callback;
180224
public object State;
181225
public TaskCompletionSource<int> Completion;
182226
}
227+
private struct CloseHandle
228+
{
229+
public Action<IntPtr> Callback;
230+
public IntPtr Handle;
231+
}
183232
}
184233
}

src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ public void close(UvHandle handle, uv_close_cb close_cb)
128128
handle.Validate(closed: true);
129129
_uv_close(handle.InternalGetHandle(), close_cb);
130130
}
131+
public void close(IntPtr handle, uv_close_cb close_cb)
132+
{
133+
_uv_close(handle, close_cb);
134+
}
131135

132136
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
133137
public delegate void uv_async_cb(IntPtr handle);

src/Microsoft.AspNet.Server.Kestrel/Networking/UcAsyncHandle.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ public class UvAsyncHandle : UvHandle
1212

1313
public void Init(UvLoopHandle loop, Action callback)
1414
{
15-
CreateHandle(loop, loop.Libuv.handle_size(Libuv.HandleType.ASYNC));
15+
CreateMemory(
16+
loop.Libuv,
17+
loop.ThreadId,
18+
loop.Libuv.handle_size(Libuv.HandleType.ASYNC));
19+
1620
_callback = callback;
1721
_uv.async_init(loop, this, _uv_async_cb);
1822
}

src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,40 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Threading;
56

67
namespace Microsoft.AspNet.Server.Kestrel.Networking
78
{
89
public abstract class UvHandle : UvMemory
910
{
10-
static Libuv.uv_close_cb _close_cb = DestroyHandle;
11+
static Libuv.uv_close_cb _destroyMemory = DestroyMemory;
12+
Action<Action<IntPtr>, IntPtr> _queueCloseHandle;
13+
14+
unsafe protected void CreateHandle(
15+
Libuv uv,
16+
int threadId,
17+
int size,
18+
Action<Action<IntPtr>, IntPtr> queueCloseHandle)
19+
{
20+
_queueCloseHandle = queueCloseHandle;
21+
CreateMemory(uv, threadId, size);
22+
}
1123

1224
protected override bool ReleaseHandle()
1325
{
1426
var memory = handle;
1527
if (memory != IntPtr.Zero)
1628
{
17-
_uv.close(this, _close_cb);
1829
handle = IntPtr.Zero;
30+
31+
if (Thread.CurrentThread.ManagedThreadId == ThreadId)
32+
{
33+
_uv.close(memory, _destroyMemory);
34+
}
35+
else
36+
{
37+
_queueCloseHandle(memory2 => _uv.close(memory2, _destroyMemory), memory);
38+
}
1939
}
2040
return true;
2141
}

src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopHandle.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Threading;
56

67
namespace Microsoft.AspNet.Server.Kestrel.Networking
78
{
89
public class UvLoopHandle : UvHandle
910
{
1011
public void Init(Libuv uv)
1112
{
12-
CreateHandle(uv, uv.loop_size());
13+
CreateMemory(
14+
uv,
15+
Thread.CurrentThread.ManagedThreadId,
16+
uv.loop_size());
17+
1318
_uv.loop_init(this);
1419
}
1520

@@ -30,7 +35,7 @@ protected override bool ReleaseHandle()
3035
{
3136
_uv.loop_close(this);
3237
handle = IntPtr.Zero;
33-
DestroyHandle(memory);
38+
DestroyMemory(memory);
3439
}
3540
return true;
3641
}

src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemory.cs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
1414
public abstract class UvMemory : SafeHandle
1515
{
1616
protected Libuv _uv;
17-
int _threadId;
17+
private int _threadId;
1818

1919
public UvMemory() : base(IntPtr.Zero, true)
2020
{
@@ -30,19 +30,36 @@ public override bool IsInvalid
3030
}
3131
}
3232

33-
unsafe protected void CreateHandle(Libuv uv, int size)
33+
public int ThreadId
3434
{
35-
_uv = uv;
36-
_threadId = Thread.CurrentThread.ManagedThreadId;
35+
get
36+
{
37+
return _threadId;
38+
}
39+
private set
40+
{
41+
_threadId = value;
42+
}
43+
}
3744

45+
unsafe protected void CreateMemory(Libuv uv, int threadId, int size)
46+
{
47+
_uv = uv;
48+
ThreadId = threadId;
49+
3850
handle = Marshal.AllocCoTaskMem(size);
3951
*(IntPtr*)handle = GCHandle.ToIntPtr(GCHandle.Alloc(this, GCHandleType.Weak));
4052
}
4153

42-
protected void CreateHandle(UvLoopHandle loop, int size)
54+
unsafe protected static void DestroyMemory(IntPtr memory)
4355
{
44-
CreateHandle(loop._uv, size);
45-
_threadId = loop._threadId;
56+
var gcHandlePtr = *(IntPtr*)memory;
57+
if (gcHandlePtr != IntPtr.Zero)
58+
{
59+
var gcHandle = GCHandle.FromIntPtr(gcHandlePtr);
60+
gcHandle.Free();
61+
}
62+
Marshal.FreeCoTaskMem(memory);
4663
}
4764

4865
internal IntPtr InternalGetHandle()
@@ -57,16 +74,6 @@ public void Validate(bool closed = false)
5774
Trace.Assert(_threadId == Thread.CurrentThread.ManagedThreadId, "ThreadId is incorrect");
5875
}
5976

60-
unsafe protected static void DestroyHandle(IntPtr memory)
61-
{
62-
var gcHandlePtr = *(IntPtr*)memory;
63-
if (gcHandlePtr != IntPtr.Zero)
64-
{
65-
GCHandle.FromIntPtr(gcHandlePtr).Free();
66-
}
67-
Marshal.FreeCoTaskMem(memory);
68-
}
69-
7077
unsafe public static THandle FromIntPtr<THandle>(IntPtr handle)
7178
{
7279
GCHandle gcHandle = GCHandle.FromIntPtr(*(IntPtr*)handle);

src/Microsoft.AspNet.Server.Kestrel/Networking/UvShutdownReq.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ public class UvShutdownReq : UvReq
1717

1818
public void Init(UvLoopHandle loop)
1919
{
20-
CreateHandle(loop, loop.Libuv.req_size(Libuv.RequestType.SHUTDOWN));
20+
CreateMemory(
21+
loop.Libuv,
22+
loop.ThreadId,
23+
loop.Libuv.req_size(Libuv.RequestType.SHUTDOWN));
2124
}
2225

2326
public void Shutdown(UvStreamHandle handle, Action<UvShutdownReq, int, object> callback, object state)

src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,21 @@ public class UvTcpHandle : UvStreamHandle
1010
{
1111
public void Init(UvLoopHandle loop)
1212
{
13-
CreateHandle(loop, loop.Libuv.handle_size(Libuv.HandleType.TCP));
13+
CreateMemory(
14+
loop.Libuv,
15+
loop.ThreadId,
16+
loop.Libuv.handle_size(Libuv.HandleType.TCP));
17+
18+
_uv.tcp_init(loop, this);
19+
}
20+
21+
public void Init(UvLoopHandle loop, Action<Action<IntPtr>, IntPtr> queueCloseHandle)
22+
{
23+
CreateHandle(
24+
loop.Libuv,
25+
loop.ThreadId,
26+
loop.Libuv.handle_size(Libuv.HandleType.TCP), queueCloseHandle);
27+
1428
_uv.tcp_init(loop, this);
1529
}
1630

0 commit comments

Comments
 (0)