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.ResolveEventHandler0x019e4300 156,448 29,216,964 System.String0x000cf740 238 66,842,468 FreeTotal 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); }