Event Handlers Gone Wild


Today I ran across an issue that my good buddy Tess hit with one of her customers a while back.

The application in question here is an ASP.NET web application that is exhibiting fairly high memory usage after almost a day of usage (approx 400-500 MB). Just as in Tess’s case the number of event handlers was really high as you can see from this snip of the !dumpheap -stat command:





MD,Count,Size,Name
=====================================
0x024cd82c 876,697 24,547,516 System.ResolveEventHandler
0x019e4300 156,448 29,216,964 System.String
0x000cf740 238 66,842,468 Free
Total 1,464,956 objects, Total size: 182,475,044


Note that when working managed high memory issues I always start with !dumpheap -stat to get the landscape.

The customer stated that they were registering for the AppDomain’s AssemblyResolve event in the global.asax and provided the code that showed what they were doing. Now the code below is incorrect and we will get back to that in a bit however even being wrong it still could not account for over 867,000 event handlers being added to AppDomain’s event.





public HttpApplication() : base() {
  AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(MyAssemblyResolver.ResolveAssembly);
}
So given the time the application had been up (approx 21hrs) there must be some common code that is getting called to register for these events elsewhere. I found the code in a class they use to derive all of their WebForm classes from; a custom BasePage class. Of course we are having them remove that code and that should get them back in much better shape.

Now how do we fix the global.asax code. Well the problem here is that a new HttpApplication is created each time one is needed by a web application and this means you could have more than one handler registered. Now if you have a really low volume site you will only ever see one of these in most cases, however as your traffic gets heavier additional HttpApplications will be spun up to handle the concurrent requests. Think of the number of HttpApplication objects as your high watermark for the number of concurrent requests your site has seen.

So the fix is to only register for this event handler once and doing this through a static constructor of the HttpApplication would probably be the best approach (though not the only). Since the CLR ensures that static constructors are only called once per AppDomain this will ensure we do not register for the handler multiple times.





static HttpApplication() {  
  AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(MyAssemblyResolver.ResolveAssembly);
}

Comments (4)

  1. Bill Pierce says:

    Todd,

    Thanks for the post.  Almost wandered down the path of Tess’s customer.  My apps are so small I probably wouldn’t have noticed.  I didn’t know ASP.Net will spawn more than one HttpApplication.

    Thanks,

    -Bill

  2. More on Atlas [Via: James Avery ] Smart Client Deployment with ClickOnce – Final Manuscript Complete!…

  3. Bill Pierce says:

    Todd,

    You’re post got me thinking about additional questions.

    If ASP.Net spawns a new HttpApplication, does the Application_Start event fire for the new HttpApp?

    When the load dies down and the additional HttpApplication is no longer needed, does it just get GC’d?  Does the Application_End event fire?

    Thanks for the knowledge.

  4. Toddca says:

    Good question Bill. The Application_Start event only fires on the first instance of an HttpApplication being created and the Application_End will only fire on the destruction of the last HttpApplication instance.

    You could argue (and I would probably agree) that a better solution would have been to register for the event in the Application_Start event handler.