views:

144

answers:

3

I just discovered that every request in an ASP.Net web application gets a Session lock at the begging of a request, and then releases it at the end of the request!!! I mean, WTF Microsoft!

In case the implication is lost on you, as it was from 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 CANT! 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 UpdadePanel has finished updating... THEY CANT! 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 user Session! When I need some kind of state for the user, I could just user Cache instead, and key items on the authenticated user's name, 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? Arrrrghhhhhh.

Any advice much appreciated.

+8  A: 

If your page does not modify any session variables, you can opt out of most of this lock.

<% @Page EnableSessionState="ReadOnly" %>

If your page does not read any session variables, you can opt out of this lock entirely, for that page.

<% @Page EnableSessionState="False" %>

If none of your pages use session variables, just turn off session state in the web.config.

<sessionState mode="Off" />

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.

Joel Mueller
Hi JoelThanks for your time on this answer. These are some good suggestions and some food for thought. I don't understand your reasoning for saying all values for a session must be exclusively locked across the whole request. ASP.Net Cache values can be altered at any time by any thread. Why should this be different for session?As an aside - one problem I have with the readonly option is that if a developer does add a value to the session when it is readonly mode, it silently fails (no exception). In fact it keeps the value for the rest of the request - but not beyond.
James
@James - I'm just guessing at the motivations of the designers here, but I imagine it's more common to have multiple values depend on each other in a single user's session than in a cache that can be purged for lack of use or low-memory reasons at any time. If one page sets 4 related session variables, and another reads them after only two have been modified, that could easily lead to some very difficult-to-diagnose bugs. I imagine the designers chose to view "the current state of a user's session" as a single unit for locking purposes for that reason.
Joel Mueller
In addition, keep in mind that when you're using StateServer or SQL Server session state providers, all session variables for a given user are serialized and shipped off to the server in a single binary blob, at the end of a request. Trying to synchronize individual variables within this blob, as they are changed in realtime, would be pretty terrible for performance. It would mean potentially dozens of calls to the server per request, instead of just one read and one write.
Joel Mueller
A: 

Don't use the UpdatePanel as it has a lot of unnecessary overhead. Use ajax to get the data you need and add it to the page yourself. The UpdatePanel was intended as a stop-gap for adding ajax to existing applications.

See 2nd paragraph here: http://msdn.microsoft.com/en-us/magazine/cc163413.aspx

Silkster
+1  A: 

OK, so big Props to Joel Muller for all his input. My ultimate solution was to use the Custom SessionStateModule detailed at the end of this MSDN article:

http://msdn.microsoft.com/en-us/library/system.web.sessionstate.sessionstateutility.aspx

This was:

  • Very quick to implement (actually seemed easier than going the provider route)
  • Used a lot of the standard ASP.Net session handling out of the box (via the SessionStateUtility class)

This has made a HUGE difference to the feeling of "snapiness" to our application. I still can't believe the custom implementation of ASP.Net Session locks the session for the whole request. This adds such a huge amount of sluggishness to websites. Judging from the amount of online research I had to do (and conversations with several really experienced ASP.Net developers), a lot of people have experienced this issue, but very few people have ever got to the bottom of the cause. Maybe I will write a letter to Scott Gu...

I hope this helps a few people out there!

James
That reference is an interesting find, but I must caution you about a few things - the sample code has some problems: First, `ReaderWriterLock` has been deprecated in favor of `ReaderWriterLockSlim` - you should use that instead. Second, `lock (typeof(...))` has also been deprecated - you should lock instead on a private static object instance. Third, the phrase "This application does not prevent simultaneous Web requests from using the same session identifier" is a warning, not a feature.
Joel Mueller
Imagine that PageA and PageB are both running at the same time, in the same user session. Both pages modify session variables. PageA finishes slightly before PageB. Using this custom session state module, what happens when both pages modify the same session variable at the same time? This module will hand the same SessionStateItemCollection to both pages. That class is documented as not being thread-safe. Bad things ensue under load.
Joel Mueller
I think you can make this work, but you must replace the usage of `SessionStateItemCollection` in the sample code with a thread-safe class (perhaps based on `ConcurrentDictionary`) if you want to avoid difficult-to-reproduce errors under load.
Joel Mueller
Thanks again Joel. I spotted the lock (typeof) issue, and that was an easy switch out (interesting article here about this: http://bytes.com/topic/c-sharp/answers/249277-dont-lock-type-objects). I am going to look at switching out the pSessionItems HashTable for a ConcurrentDictionary, and ReaderWriterLock for a ReaderWriterLockSlim. Hopefully these will both be a straight forward upgrade. I will post back with any updates.
James
Those are all good changes, James, but until you replace `SessionStateItemCollection` (which is a property of the SessionItem class in the example) with a thread-safe implementation of `ISessionStateItemCollection` you've still got potential issues. Another thing to consider: do you store any class instances in session variables? Are those classes thread-safe? Because you've just allowed for the possibility of multiple threads accessing instances of those classes at the same time.
Joel Mueller
I just looked into this a little more, and unfortunately `ISessionStateItemCollection` requires the `Keys` property to be of type `System.Collections.Specialized.NameObjectCollectionBase.KeysCollection` - which has no public constructors. Gee, thanks guys. That's very convenient.
Joel Mueller
MSDN has an article with a sample implementation of ISessionStateItem here: http://msdn.microsoft.com/en-us/library/system.web.sessionstate.isessionstateitemcollection(VS.80).aspx. I will mess with that, and report back. This rabbit hole is going deeper than I first thought!
James
OK, I believe I finally have a full threadsafe, non read locking implementation of Session working. The last steps involved implementing a custom threadsafe SessionStateItem collection, which was was based on the MDSN article linked to in the above comment. The final piece of the puzzle with this was creating a threadsafe enumerator based on this great article: http://www.codeproject.com/KB/cs/safe_enumerable.aspx.
James
Joel, thanks again for your input.
James