Guarding against leaking GCHandles in read/write operations

See #19
This commit is contained in:
Louis DeJardin 2014-07-08 18:11:09 -07:00
parent 1406ec94c3
commit de6c32dc4b
3 changed files with 97 additions and 39 deletions

View File

@ -53,11 +53,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
SocketOutput _self; SocketOutput _self;
ArraySegment<byte> _buffer; ArraySegment<byte> _buffer;
Action<Exception> _drained;
UvStreamHandle _socket; UvStreamHandle _socket;
Action<Exception, object> _callback; Action<Exception, object> _callback;
object _state; object _state;
GCHandle _pin;
internal void Contextualize( internal void Contextualize(
SocketOutput socketOutput, SocketOutput socketOutput,
@ -87,8 +85,14 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{ {
KestrelTrace.Log.ConnectionWriteCallback(0, status); KestrelTrace.Log.ConnectionWriteCallback(0, status);
//NOTE: pool this? //NOTE: pool this?
var callback = _callback;
_callback = null;
var state = _state;
_state = null;
Dispose(); Dispose();
_callback(error, _state); callback(error, state);
} }
} }

View File

@ -37,10 +37,27 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
public void Listen(int backlog, Action<UvStreamHandle, int, Exception, object> callback, object state) public void Listen(int backlog, Action<UvStreamHandle, int, Exception, object> callback, object state)
{ {
_listenCallback = callback; if (_listenVitality.IsAllocated)
_listenState = state; {
_listenVitality = GCHandle.Alloc(this, GCHandleType.Normal); throw new InvalidOperationException("TODO: Listen may not be called more than once");
_uv.listen(this, 10, _uv_connection_cb); }
try
{
_listenCallback = callback;
_listenState = state;
_listenVitality = GCHandle.Alloc(this, GCHandleType.Normal);
_uv.listen(this, 10, _uv_connection_cb);
}
catch
{
_listenCallback = null;
_listenState = null;
if (_listenVitality.IsAllocated)
{
_listenVitality.Free();
}
throw;
}
} }
public void Accept(UvStreamHandle handle) public void Accept(UvStreamHandle handle)
@ -53,15 +70,37 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
Action<UvStreamHandle, int, Exception, object> readCallback, Action<UvStreamHandle, int, Exception, object> readCallback,
object state) object state)
{ {
_allocCallback = allocCallback; if (_readVitality.IsAllocated)
_readCallback = readCallback; {
_readState = state; throw new InvalidOperationException("TODO: ReadStop must be called before ReadStart may be called again");
_readVitality = GCHandle.Alloc(this, GCHandleType.Normal); }
_uv.read_start(this, _uv_alloc_cb, _uv_read_cb); try
{
_allocCallback = allocCallback;
_readCallback = readCallback;
_readState = state;
_readVitality = GCHandle.Alloc(this, GCHandleType.Normal);
_uv.read_start(this, _uv_alloc_cb, _uv_read_cb);
}
catch
{
_allocCallback = null;
_readCallback = null;
_readState = null;
if (_readVitality.IsAllocated)
{
_readVitality.Free();
}
throw;
}
} }
public void ReadStop() public void ReadStop()
{ {
if (!_readVitality.IsAllocated)
{
throw new InvalidOperationException("TODO: ReadStart must be called before ReadStop may be called");
}
_allocCallback = null; _allocCallback = null;
_readCallback = null; _readCallback = null;
_readState = null; _readState = null;

View File

@ -40,45 +40,60 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
Action<UvWriteReq, int, Exception, object> callback, Action<UvWriteReq, int, Exception, object> callback,
object state) object state)
{ {
// add GCHandle to keeps this SafeHandle alive while request processing try
_pins.Add(GCHandle.Alloc(this, GCHandleType.Normal));
var pBuffers = (Libuv.uv_buf_t*)_bufs;
var nBuffers = bufs.Count;
if (nBuffers > BUFFER_COUNT)
{ {
// create and pin buffer array when it's larger than the pre-allocated one // add GCHandle to keeps this SafeHandle alive while request processing
var bufArray = new Libuv.uv_buf_t[nBuffers]; _pins.Add(GCHandle.Alloc(this, GCHandleType.Normal));
var gcHandle = GCHandle.Alloc(bufArray, GCHandleType.Pinned);
_pins.Add(gcHandle);
pBuffers = (Libuv.uv_buf_t*)gcHandle.AddrOfPinnedObject();
}
for (var index = 0; index != nBuffers; ++index) var pBuffers = (Libuv.uv_buf_t*)_bufs;
var nBuffers = bufs.Count;
if (nBuffers > BUFFER_COUNT)
{
// create and pin buffer array when it's larger than the pre-allocated one
var bufArray = new Libuv.uv_buf_t[nBuffers];
var gcHandle = GCHandle.Alloc(bufArray, GCHandleType.Pinned);
_pins.Add(gcHandle);
pBuffers = (Libuv.uv_buf_t*)gcHandle.AddrOfPinnedObject();
}
for (var index = 0; index != nBuffers; ++index)
{
// create and pin each segment being written
var buf = bufs.Array[bufs.Offset + index];
var gcHandle = GCHandle.Alloc(buf.Array, GCHandleType.Pinned);
_pins.Add(gcHandle);
pBuffers[index] = Libuv.buf_init(
gcHandle.AddrOfPinnedObject() + buf.Offset,
buf.Count);
}
_callback = callback;
_state = state;
_uv.write(this, handle, pBuffers, nBuffers, _uv_write_cb);
}
catch
{ {
// create and pin each segment being written _callback = null;
var buf = bufs.Array[bufs.Offset + index]; _state = null;
Unpin(this);
var gcHandle = GCHandle.Alloc(buf.Array, GCHandleType.Pinned); throw;
_pins.Add(gcHandle);
pBuffers[index] = Libuv.buf_init(
gcHandle.AddrOfPinnedObject() + buf.Offset,
buf.Count);
} }
_callback = callback;
_state = state;
_uv.write(this, handle, pBuffers, nBuffers, _uv_write_cb);
} }
private static void UvWriteCb(IntPtr ptr, int status) private static void Unpin(UvWriteReq req)
{ {
var req = FromIntPtr<UvWriteReq>(ptr);
foreach (var pin in req._pins) foreach (var pin in req._pins)
{ {
pin.Free(); pin.Free();
} }
req._pins.Clear(); req._pins.Clear();
}
private static void UvWriteCb(IntPtr ptr, int status)
{
var req = FromIntPtr<UvWriteReq>(ptr);
Unpin(req);
var callback = req._callback; var callback = req._callback;
req._callback = null; req._callback = null;