Fix memory leak caused by closure allocations in KestrelThread (#1264).

This commit is contained in:
Cesar Blum Silveira 2017-01-25 15:43:22 -08:00
parent 9051bbf322
commit e2a2e9a620
1 changed files with 29 additions and 13 deletions

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.ExceptionServices; using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
@ -23,6 +24,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
private static readonly Action<object, object> _postCallbackAdapter = (callback, state) => ((Action<object>)callback).Invoke(state); private static readonly Action<object, object> _postCallbackAdapter = (callback, state) => ((Action<object>)callback).Invoke(state);
private static readonly Action<object, object> _postAsyncCallbackAdapter = (callback, state) => ((Action<object>)callback).Invoke(state); private static readonly Action<object, object> _postAsyncCallbackAdapter = (callback, state) => ((Action<object>)callback).Invoke(state);
private static readonly Libuv.uv_walk_cb _heartbeatWalkCallback = (ptr, arg) =>
{
var streamHandle = UvMemory.FromIntPtr<UvHandle>(ptr) as UvStreamHandle;
var thisHandle = GCHandle.FromIntPtr(arg);
var kestrelThread = (KestrelThread)thisHandle.Target;
streamHandle?.Connection?.Tick(kestrelThread.Now);
};
// maximum times the work queues swapped and are processed in a single pass // maximum times the work queues swapped and are processed in a single pass
// as completing a task may immediately have write data to put on the network // as completing a task may immediately have write data to put on the network
@ -48,6 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
private readonly IKestrelTrace _log; private readonly IKestrelTrace _log;
private readonly IThreadPool _threadPool; private readonly IThreadPool _threadPool;
private readonly TimeSpan _shutdownTimeout; private readonly TimeSpan _shutdownTimeout;
private IntPtr _thisPtr;
public KestrelThread(KestrelEngine engine) public KestrelThread(KestrelEngine engine)
{ {
@ -94,6 +103,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
private Action<Action<IntPtr>, IntPtr> QueueCloseAsyncHandle { get; } private Action<Action<IntPtr>, IntPtr> QueueCloseAsyncHandle { get; }
// The cached result of Loop.Now() which is a timestamp in milliseconds
private long Now { get; set; }
public Task StartAsync() public Task StartAsync()
{ {
var tcs = new TaskCompletionSource<int>(); var tcs = new TaskCompletionSource<int>();
@ -245,14 +257,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
} }
public void Walk(Action<IntPtr> callback) public void Walk(Action<IntPtr> callback)
{
Walk((ptr, arg) => callback(ptr), IntPtr.Zero);
}
private void Walk(Libuv.uv_walk_cb callback, IntPtr arg)
{ {
_engine.Libuv.walk( _engine.Libuv.walk(
_loop, _loop,
(ptr, arg) => callback,
{ arg
callback(ptr); );
},
IntPtr.Zero);
} }
private void PostCloseHandle(Action<IntPtr> callback, IntPtr handle) private void PostCloseHandle(Action<IntPtr> callback, IntPtr handle)
@ -273,7 +288,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
{ {
lock (_startSync) lock (_startSync)
{ {
var tcs = (TaskCompletionSource<int>) parameter; var tcs = (TaskCompletionSource<int>)parameter;
try try
{ {
_loop.Init(_engine.Libuv); _loop.Init(_engine.Libuv);
@ -290,8 +305,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
} }
} }
// This is used to access a 64-bit timestamp (this.Now) using a potentially 32-bit IntPtr.
var thisHandle = GCHandle.Alloc(this, GCHandleType.Weak);
try try
{ {
_thisPtr = GCHandle.ToIntPtr(thisHandle);
_loop.Run(); _loop.Run();
if (_stopImmediate) if (_stopImmediate)
{ {
@ -318,6 +338,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
} }
finally finally
{ {
thisHandle.Free();
_threadTcs.SetResult(null); _threadTcs.SetResult(null);
} }
} }
@ -336,13 +357,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
private void OnHeartbeat(UvTimerHandle timer) private void OnHeartbeat(UvTimerHandle timer)
{ {
var now = Loop.Now(); Now = Loop.Now();
Walk(_heartbeatWalkCallback, _thisPtr);
Walk(ptr =>
{
var handle = UvMemory.FromIntPtr<UvHandle>(ptr);
(handle as UvStreamHandle)?.Connection?.Tick(now);
});
} }
private bool DoPostWork() private bool DoPostWork()