propagated
propagated

Reputation: 4602

System.Threading.Timer windows service memory leak

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

Answers (1)

propagated
propagated

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

Related Questions