Reputation: 4602
I have a c# console application running as a windows service. The main thread of the application uses a System.Threading.Timer
to manage 'sweeps' of a database. When the sweep starts on timer tick, the timer creates an object to find work in the database, and that object raises an event when work is found to spawn off the job into another thread.
I appear to be suffering a memory leak, and my limited windbg
abilities querying memory dumps has me thinking that the timer is not releasing something/itself to GC.
windbg:
!dumpheap -type System.Object[]
...
02b01ae0 73a20cbc 32656
02b09a80 73a20cbc 4112
02b0e8e0 73a20cbc 16336
02b5bf88 73a20cbc 1040
02b5c3a8 73a20cbc 2064
10ee1010 73a20cbc 268435472
Statistics:
MT Count TotalSize Class Name
04210964 1 32 System.Func`2[[System.Type, mscorlib],[System.Func`2[[System.Object[], mscorlib],[Newtonsoft.Json.JsonConverter, Newtonsoft.Json]], mscorlib]]
7366a6a4 1 48 System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object[], mscorlib]]
04210a5c 1 48 System.Collections.Generic.Dictionary`2[[System.Type, mscorlib],[System.Func`2[[System.Object[], mscorlib],[Newtonsoft.Json.JsonConverter, Newtonsoft.Json]], mscorlib]]
73a20cbc 1194 268556916 System.Object[]
Total 1198 objects
0:000> !gcroot 10ee1010
Thread 2dfc:
04f1f70c 72db0687 System.Net.TimerThread.ThreadProc()
ebp+50: 04f1f710 (interior)
-> 02ac33a8 System.Object[]
-> 072a5a38 System.Net.ServerCertValidationCallback
-> 072a5a18 System.Net.Security.RemoteCertificateValidationCallback
-> 10ee1010 System.Object[]
Simplified and condensed code:
TimeSpan timerInterval = TimeSpan.FromSeconds(5);
static Timer t;
public void startTimer()
{
t = new System.Threading.Timer(TimerTick, null, TimeSpan.Zero, timerInterval);
}
public void TimerTick(Object TimerState)
{
//run each query sweep synchronously
t.Change(Timeout.Infinite, Timeout.Infinite);
List<Query> queries = GetQueries();
foreach (var query in queries)
{
var search = new QueryProcessor(query);
//short term publisher?
search.resultFoundEvent += QueryResultEventListener;
search.RunSearch();
}
t.Change(timerInterval, timerInterval);
}
public void RunJob(JobDetails job)
{
Task.Factory.StartNew(() => job.Execute(JobCallback));
}
//long term subscriber
public void QueryResultEventListener(object sender, FakeEventArgs e)
{
RunJob(e.jobdetails);
}
public void JobCallback(JobDetails jobsuccess)
{
//job was completed
}
As commented above, i don't think this is a traditional event handler leak, and windbg
above has only this one massive entry at the bottom of !dumpheap -stat
for System.Object[]
.
Upvotes: 0
Views: 1520
Reputation: 4602
Using jetBrains dotMemory I was able to determine that the handle open was from a line of code being called from referenced managed code down the stack in the job.Execute()
. It was setting a delegate globally and causing ServerCertificateValidationCallback
to pin a handle and never release.
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => { };
Feels like it behaves in the "short term publisher, long term subscriber" classic memory leak fashion, as the parent object of the constructor calling this line was disposed shortly after.
Upvotes: 1