After a lot more study, it looks like there are two separate issues that are causing the problem here.
- During AppDomain unload, it is permissible for the GC to collect reachable objects. I am fine with that and I certainly agree that this makes sense.
- Application_End occurs concurrently with the AppDomain unload.
Looking at the docs (and there are surprisingly few about this), it seems like 1 is expected, but 2 is a bug. The docs state:
Application_End - Called once per lifetime of the application before the application is unloaded.
It may be my English, but to me before doesn’t suggest at the same time as.
So I do believe it is a bug in the framework, but not in the GC, it is in the shutdown code for ASP.Net. This is still a very nasty problem.