Skip to content

Commit de6c32d

Browse files
committed
Guarding against leaking GCHandles in read/write operations
See #19
1 parent 1406ec9 commit de6c32d

File tree

3 files changed

+97
-39
lines changed

3 files changed

+97
-39
lines changed

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,9 @@ private static void WriteCallback(UvWriteReq req, int status, Exception error, o
5353

5454
SocketOutput _self;
5555
ArraySegment<byte> _buffer;
56-
Action<Exception> _drained;
5756
UvStreamHandle _socket;
5857
Action<Exception, object> _callback;
5958
object _state;
60-
GCHandle _pin;
6159

6260
internal void Contextualize(
6361
SocketOutput socketOutput,
@@ -87,8 +85,14 @@ private void OnWrite(UvWriteReq req, int status, Exception error)
8785
{
8886
KestrelTrace.Log.ConnectionWriteCallback(0, status);
8987
//NOTE: pool this?
88+
89+
var callback = _callback;
90+
_callback = null;
91+
var state = _state;
92+
_state = null;
93+
9094
Dispose();
91-
_callback(error, _state);
95+
callback(error, state);
9296
}
9397
}
9498

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

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,27 @@ protected override bool ReleaseHandle()
3737

3838
public void Listen(int backlog, Action<UvStreamHandle, int, Exception, object> callback, object state)
3939
{
40-
_listenCallback = callback;
41-
_listenState = state;
42-
_listenVitality = GCHandle.Alloc(this, GCHandleType.Normal);
43-
_uv.listen(this, 10, _uv_connection_cb);
40+
if (_listenVitality.IsAllocated)
41+
{
42+
throw new InvalidOperationException("TODO: Listen may not be called more than once");
43+
}
44+
try
45+
{
46+
_listenCallback = callback;
47+
_listenState = state;
48+
_listenVitality = GCHandle.Alloc(this, GCHandleType.Normal);
49+
_uv.listen(this, 10, _uv_connection_cb);
50+
}
51+
catch
52+
{
53+
_listenCallback = null;
54+
_listenState = null;
55+
if (_listenVitality.IsAllocated)
56+
{
57+
_listenVitality.Free();
58+
}
59+
throw;
60+
}
4461
}
4562

4663
public void Accept(UvStreamHandle handle)
@@ -53,15 +70,37 @@ public void ReadStart(
5370
Action<UvStreamHandle, int, Exception, object> readCallback,
5471
object state)
5572
{
56-
_allocCallback = allocCallback;
57-
_readCallback = readCallback;
58-
_readState = state;
59-
_readVitality = GCHandle.Alloc(this, GCHandleType.Normal);
60-
_uv.read_start(this, _uv_alloc_cb, _uv_read_cb);
73+
if (_readVitality.IsAllocated)
74+
{
75+
throw new InvalidOperationException("TODO: ReadStop must be called before ReadStart may be called again");
76+
}
77+
try
78+
{
79+
_allocCallback = allocCallback;
80+
_readCallback = readCallback;
81+
_readState = state;
82+
_readVitality = GCHandle.Alloc(this, GCHandleType.Normal);
83+
_uv.read_start(this, _uv_alloc_cb, _uv_read_cb);
84+
}
85+
catch
86+
{
87+
_allocCallback = null;
88+
_readCallback = null;
89+
_readState = null;
90+
if (_readVitality.IsAllocated)
91+
{
92+
_readVitality.Free();
93+
}
94+
throw;
95+
}
6196
}
6297

6398
public void ReadStop()
6499
{
100+
if (!_readVitality.IsAllocated)
101+
{
102+
throw new InvalidOperationException("TODO: ReadStart must be called before ReadStop may be called");
103+
}
65104
_allocCallback = null;
66105
_readCallback = null;
67106
_readState = null;

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

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -40,45 +40,60 @@ public unsafe void Write(
4040
Action<UvWriteReq, int, Exception, object> callback,
4141
object state)
4242
{
43-
// add GCHandle to keeps this SafeHandle alive while request processing
44-
_pins.Add(GCHandle.Alloc(this, GCHandleType.Normal));
45-
46-
var pBuffers = (Libuv.uv_buf_t*)_bufs;
47-
var nBuffers = bufs.Count;
48-
if (nBuffers > BUFFER_COUNT)
43+
try
4944
{
50-
// create and pin buffer array when it's larger than the pre-allocated one
51-
var bufArray = new Libuv.uv_buf_t[nBuffers];
52-
var gcHandle = GCHandle.Alloc(bufArray, GCHandleType.Pinned);
53-
_pins.Add(gcHandle);
54-
pBuffers = (Libuv.uv_buf_t*)gcHandle.AddrOfPinnedObject();
45+
// add GCHandle to keeps this SafeHandle alive while request processing
46+
_pins.Add(GCHandle.Alloc(this, GCHandleType.Normal));
47+
48+
var pBuffers = (Libuv.uv_buf_t*)_bufs;
49+
var nBuffers = bufs.Count;
50+
if (nBuffers > BUFFER_COUNT)
51+
{
52+
// create and pin buffer array when it's larger than the pre-allocated one
53+
var bufArray = new Libuv.uv_buf_t[nBuffers];
54+
var gcHandle = GCHandle.Alloc(bufArray, GCHandleType.Pinned);
55+
_pins.Add(gcHandle);
56+
pBuffers = (Libuv.uv_buf_t*)gcHandle.AddrOfPinnedObject();
57+
}
58+
59+
for (var index = 0; index != nBuffers; ++index)
60+
{
61+
// create and pin each segment being written
62+
var buf = bufs.Array[bufs.Offset + index];
63+
64+
var gcHandle = GCHandle.Alloc(buf.Array, GCHandleType.Pinned);
65+
_pins.Add(gcHandle);
66+
pBuffers[index] = Libuv.buf_init(
67+
gcHandle.AddrOfPinnedObject() + buf.Offset,
68+
buf.Count);
69+
}
70+
71+
_callback = callback;
72+
_state = state;
73+
_uv.write(this, handle, pBuffers, nBuffers, _uv_write_cb);
5574
}
56-
57-
for (var index = 0; index != nBuffers; ++index)
75+
catch
5876
{
59-
// create and pin each segment being written
60-
var buf = bufs.Array[bufs.Offset + index];
61-
62-
var gcHandle = GCHandle.Alloc(buf.Array, GCHandleType.Pinned);
63-
_pins.Add(gcHandle);
64-
pBuffers[index] = Libuv.buf_init(
65-
gcHandle.AddrOfPinnedObject() + buf.Offset,
66-
buf.Count);
77+
_callback = null;
78+
_state = null;
79+
Unpin(this);
80+
throw;
6781
}
68-
69-
_callback = callback;
70-
_state = state;
71-
_uv.write(this, handle, pBuffers, nBuffers, _uv_write_cb);
7282
}
7383

74-
private static void UvWriteCb(IntPtr ptr, int status)
84+
private static void Unpin(UvWriteReq req)
7585
{
76-
var req = FromIntPtr<UvWriteReq>(ptr);
7786
foreach (var pin in req._pins)
7887
{
7988
pin.Free();
8089
}
8190
req._pins.Clear();
91+
}
92+
93+
private static void UvWriteCb(IntPtr ptr, int status)
94+
{
95+
var req = FromIntPtr<UvWriteReq>(ptr);
96+
Unpin(req);
8297

8398
var callback = req._callback;
8499
req._callback = null;

0 commit comments

Comments
 (0)