I just discovered that every request in an ASP.Net web application gets a Session lock at the beginning of a request, and then releases it at the end of the request!
In case the implications of this are lost on you, as it was for me at first, this basically means the following:
Anytime an ASP.Net webpage is taking a long time to load (maybe due to a slow database call or whatever), and the user decides they want to navigate to a different page because they are tired of waiting, THEY CAN'T! The ASP.Net session lock forces the new page request to wait until the original request has finished its painfully slow load. Arrrgh.
Anytime an UpdatePanel is loading slowly, and the user decides to navigate to a different page before the UpdatePanel has finished updating... THEY CAN'T! The ASP.net session lock forces the new page request to wait until the original request has finished its painfully slow load. Double Arrrgh!
So what are the options? So far I have come up with:
- Implement a Custom SessionStateDataStore, which ASP.Net supports. I haven't found too many out there to copy, and it seems kind of high risk and easy to mess up.
- Keep track of all requests in progress, and if a request comes in from the same user, cancel the original request. Seems kind of extreme, but it would work (I think).
- Don't use Session! When I need some kind of state for the user, I could just use Cache instead, and key items on the authenticated username, or some such thing. Again seems kind of extreme.
I really can't believe that the ASP.Net Microsoft team would have left such a huge performance bottleneck in the framework at version 4.0! Am I missing something obvious? How hard would it be to use a ThreadSafe collection for the Session?
I prepared a library based on links posted in this thread. It uses the examples from MSDN and CodeProject. Thanks to James.
I also made modifications advised by Joel Mueller.
Code is here:
https://github.com/dermeister0/LockFreeSessionState
HashTable module:
ScaleOut StateServer module:
Custom module:
If you want to implement support of Memcached or Redis, install this package. Then inherit the LockFreeSessionStateModule class and implement abstract methods.
Some lock-free session providers using Redis:
If your page does not modify any session variables, you can opt out of most of this lock.
If your page does not read any session variables, you can opt out of this lock entirely, for that page.
If none of your pages use session variables, just turn off session state in the web.config.
I'm curious, what do you think "a ThreadSafe collection" would do to become thread-safe, if it doesn't use locks?
Edit: I should probably explain by what I mean by "opt out of most of this lock". Any number of read-only-session or no-session pages can be processed for a given session at the same time without blocking each other. However, a read-write-session page can't start processing until all read-only requests have completed, and while it is running it must have exclusive access to that user's session in order to maintain consistency. Locking on individual values wouldn't work, because what if one page changes a set of related values as a group? How would you ensure that other pages running at the same time would get a consistent view of the user's session variables?
I would suggest that you try to minimize the modifying of session variables once they have been set, if possible. This would allow you to make the majority of your pages read-only-session pages, increasing the chance that multiple simultaneous requests from the same user would not block each other.