Last post Apr 20, 2007 06:22 PM by kolosy
Apr 20, 2007 06:22 PM|kolosy|LINK
We recently migrated our system from 1.1 to 2.0. one of the issues 1.1 had (2.0 may still have? i haven't looked into it) was that the session end event didn't work properly (i don't remember the details). the code we had used to circumvent that is the standard
System.Web.Cache trick - an object that expires a bit before the session does:
cache = context.Context.Cache;
and onSessionExpired we'd remove our session object from the .net session. everything was fine when we had timeout = 1. when we moved timeout to 0.1, we got wonderful behavior - the worker process started crashing on us (incidentally - the correlation between the worker process crashing and the switch to 0.1 was made after 2 days of beating my head against the wall). The crash we were getting was
message=Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
at System.Collections.ArrayList.get_Item(Int32 index)
at System.Collections.Specialized.NameObjectCollectionBase.BaseGet(Int32 index)
at System.Web.SessionState.SessionStateItemCollection.get_Item(Int32 index)
at System.Web.SessionState.HttpSessionStateContainer.get_Item(Int32 index)
at System.Web.Util.AspCompatApplicationStep.AnyStaObjectsInSessionState(HttpSessionState session)
at System.Web.HttpApplicationFactory.FireSessionOnEnd(HttpSessionState session, Object eventSource, EventArgs eventArgs)
at System.Web.Util.WorkItem.CallCallbackWithAssert(WorkItemCallback callback)
at System.Web.Util.WorkItem.OnQueueUserWorkItemCompletion(Object state)
at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)
with the help of Reflector, we decompiled AnyStaObjectsInSessionState to get this:
internal static bool AnyStaObjectsInSessionState(HttpSessionState session)
if (session != null)
int count = session.Count;
for (int i = 0; i < count; i++)
object obj2 = session[i];
if (((obj2 != null) && (obj2.GetType().FullName == "System.__ComObject")) && (UnsafeNativeMethods.AspCompatIsApartmentComponent(obj2) != 0))
Closing the loop - it appears that when we had moved our timeout to occur to close to the .net timeout, our cleanup routines collided - we were removing ourselves from the .net session, while the runtime was iterating through the session looking for objects
of a particular type. Classic multithreading faux pas.
Now, you might ask what's to prevent this from happening in a much simpler scenario? Picture this - you have a button on a form, that onclick removes something from the session. Now, if we click that button at just the right time, we could fall into the
same trap. I followed through the entire stack trace - there is no explicit synchronization on the Session object, at least none that i could find. Well - what will stop it is the fact that in a classic scenario, the session object will be removed from the
cache before the callback fires. so either the request comes in prior to the callback, and prevents the callback from ever firing, or it'll come in after, and at that point the session object is no longer reachable through the cache, and "session timed out"
behavior follows. The specific issue here is that we have a separate thread with a reference to the session object. That separate thread is unaccounted for (nor should it be) by the callback method.
Moral of the story - be careful with cache callbacks that touch system objects.