We have an MVC.NET application that encounters fatal errors when it restarts. In our Session_Start event handler, we add the session id to a dictionary. In the Session_End handler, we remove it. Consider the following sequence of requests:
GET home.mvc
<application restarts>
GET main.css
GET banner.jpg
GET somedata.mvc
...
Because of the way the application is architected, this sort of sequence happens fairly frequently if you do a rebuild while the application is open in a browser window. That wouldn't be terribly concerning except that I see it in production environments too. For example, it will occur (albeit rarely) when you edit web.config.
The requests following the restart are all due to links in the home page or AJAX calls from JavaScript.
What I observe is that .NET handles the first 5 requests in parallel. Each such request causes it to fire the Session_Start event. After a short time, it fires the Session_End event 3 times. To be clear, each Session_Start corresponds to the exact same session. They all have the same session id and the IsNewSession property is true for all session state objects. Also, the Session_End events do not correspond to the session being killed. The session persists, along with any data stored in session state.
I need to either prevent it from firing Session_Start more than once or figure out how to tell when Session_End doesn't really mean that the session has ended.
The answer to this question turned out to be fairly straight-foward, though the behaviour was certainly confusing.
Normally, MVC will synchronize all requests to an application on the application's session state lock (because MVC's http module is marked as requiring session state). In my scenario, the application restarts after serving the main page. Thus, when the main page-related requests come in, there is no session state for that session id yet and the requests execute in parallel.
I see 5 parallel requests because I'm developing on XP and desktop versions of IIS are limited to 5 concurrent requests. Since the session state object does not exist for any of these requests, each request creates a new session state object and fires Session_Start. Four of the requests go to MVC action methods. Since these require session state, .NET tries to synchronize the created session state objects with the backing store once the requests complete.
Only the first synchronization can succeed. .NET simply discards the three extra session state objects and fires Session_End for each one. .NET does not attempt to synchronize the fifth session state object with the backing store because it was created for an asynchronous http module which is marked as requiring readonly session state.
So, the fix has two parts:
(1) In my Session_Start handler, I now check if the session state object is readonly. If it is, then I return immediately without doing anything. .NET will not fire a corresponding Session_End, and thus anything I do won't be cleaned up properly anyway.
(2) I now keep a reference count in my dictionary. I increment the count everytime a Session_Start handler tries to add a session id and decrement it everytime Session_End tries to remove one. Once the count reaches 0, I remove the session id from my dictionary.
The session id can be reused if the client sends you a value that has expired. Read about the <sessionState> element's regenerateExpiredSessionId attribute here and notice that the default value is "true"
You might also find this interesting:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With