The Downsides of ASP.NET Session State

ASP.NET session state is an undeniably useful tool for dealing with the statelessness of http. But there are draw backs that many developers may not appreciate.

The first issue we'll look at is one that a lot developers don't know about; by default the ASP.NET pipeline will not process requests belonging to the same session concurrently. It serialises them, i.e. it queues them in the order that they were received so that they are processed serially rather than in parallel. This means that if a request is in progress and another request from the same session arrives, it will be queued to only begin executing when the first request has finished. Why does ASP.NET do this? For concurrency control, so that multiple requests (i.e. multiple threads) do not read and write to session state in an inconsistent way.

So what sort of scenarios could produce concurrent requests from the same session?

  • A user opening multiple tabs/windows
  • Multiple concurrent asynchronous AJAX requests
  • Http handlers that stream resources like images - your browser may make concurrent requests for these.

I would guess that the most common case is that of a page making multiple AJAX requests, that the developer assumes will run concurrently. It's common for a developer to use multiple asynchronous requests when the operations behind these are I/O bound (like waiting for data from a remote server), as you can increase performance by having your server do more work while it's waiting. But if you don't take into account session state, your requests may effectively become synchronous (in terms of total execution time). Let's look at a concrete example; a page making 3 asynchronous AJAX requests to the server, with session state enabled (also note that session must actually be used, as ASP.NET is smart enough not to serialise requests if you never use session state, even if it's enabled):

jQuery client
$(document).ready(function () {
    //Make 3 concurrent requests to /ajaxtest/test
    for (var i = 0; i < 3; i++) {        
        $.post("/ajaxtest/test/" + i,
            function (data) {                        
                //Do something with data...
            },
            "json");
    }
});
ASP.NET MVC controller
public class AjaxTestController : Controller
{        
    [HttpPost]
    public JsonResult Test(int? id)
    {            
        //simulate an action that takes 500ms, e.g. connecting to a remote host etc
        Thread.Sleep(500); 
        return Json(/*Some object*/);
    }
}
Network profile from the browser

You can see the effect of serialised requests in the network profile; each request takes roughly 500ms longer than the previous one. So it means we're not getting any benefit from making these AJAX calls asynchronously. Let's look at the profile again with session state disabled for our AjaxTestController (using the [SessionState] attribute).

ASP.NET MVC controller with session state disabled
[SessionState(SessionStateBehavior.Disabled)]
public class AjaxTestController : Controller
{        
    //...As above
}
Network profile from the browser - session state disabled

Much better! You can see how the 3 requests are being processed in parallel, and take a total of 500ms to complete, rather than 1500ms we saw in our first example.

But what do you do if you actually need session state in your AJAX requests? If you only need to read session data, you're in luck; you can use [SessionState(SessionStateBehavior.ReadOnly)] which gives you read access without any requests being blocked (although they will be blocked if there is a concurrent request set to read-write mode). But if you need to write session data and you're expecting these requests to run concurrently, you need to ask yourself whether you need concurrency control, or whether it's unimportant. If you do need concurrency control, it may not be enough just to rely on the default behaviour, as these controls don't hold up in a web farm environment - read on.

If you run off a web farm, you've probably set your session state mode to StateServer or SqlServer so that requests can go to any server and still maintain the same session state. However, the behaviour of serialising same-session requests only applies for requests to the same server. So in our example above, it would be possible for the 3 AJAX requests to be processed concurrently by 3 different servers, even if session state is enabled. Is this a good thing? I'd say no, because it becomes undefined as to whether the concurrency controls are in affect or not, depending on which servers process the requests, because of load-balancing. If all 3 requests are processed on different servers, then no concurrency controls are in place, and the sequence of reads and writes to your session data can be inconsistent.

These concurrency problems are actually exacerbated by the way in which session data is read and written from its data store (no matter if you're using InProc,StateServer or SqlServer mode). Before your request is processed, ASP.NET loads all session data into the session dictionary, then processes your request and then writes back to the data store before the response is sent. So when you write to session, e.g. Session["SomeKey"] = "SomeValue", the data is not persisted immediately, its only written later. This means that the delay between reading and writing your session data is as long as your request takes to execute. So if your request is connecting to and waiting for a remote server, the delay could be significant. This increases the probability of concurrency problems, because it means it's more likely that the session data is stale by the time your request sees it (i.e. another request has written data to the session since you read it), or you are overwriting an earlier modification made by a different request. These are common problems we deal with in our app, where we use things like optimistic or pessimistic locking to solve them. But concurrency issues with session state are usually overlooked.

Am I suggesting you stop using session state entirely? Not quite, I just want to highlight the potential pitfalls. The severity of the problem depends how you use session, and based on that, how serious concurrency issues can be to your system. I would suggest avoiding session state as much as possible, so that it gives you more opportunity to disable it on a per-controller basis. You could of course handle session data manually. Lets take a very common (and valid) use of session state; storing the contents of a users shopping basket in an e-commerce app. Instead of storing the basket items in session, if you stored it manually in your database and retrieved it only when you needed it, you would get the benefit of reducing the amount of time that your basket data is held in memory, and thus reduce the likelihood of stale data. So looking at some other common uses of session state, what are the alternatives?

  • Store currently logged in user in session - forms authentication already does this for you with an encrypted cookie. Go back to DB or cache if forms auth cookie doesn't have the data you need.
  • Store user preferences in session - use a cookie (as long as the amount of data is small and not sensitive)
  • Store data in session as user progresses through a wizard - this is usually a mistake and can lead to bugs, as you are assuming your user is only completing a single wizard in a single browser tab/window. You could store all data in form fields and post all the data to the each step in the wizard.

So quite a bit to digest, but the basic moral of the story is to disable session state whenever it's not in use, and if it is in use, try specify read-only and read-write modes where applicable. Also keep an open mind as to whether ASP.NET session state is even the right approach for you system.

Comments (7)

Arjmahal
Yes, nice article.
Such issue was already described in this post: http://odetocode.com/Blogs/scott/archive/2006/05/21/session-state-uses-a-reader-writer-lock.aspx
Wednesday, 26 October 2011 15:42
Manoj Maheswari
Very nice article. I really enjoying it, and it also cleared lot of my doubts about Asp.Net session state. Thanks for sharing with us. Following link also helped me lot to understand the Asp.Net Session
State...

http://mindstick.com/Articles/273b0c21-a027-46ec-8c84-ad4e05c62b23/?Session%20state%20in%20ASP.Net

Thanks everyone for your precious post!!
Thursday, 12 January 2012 15:11
Decease
Hello! Please help me. Maybe you know, how to implement this(or similar) in ASP .NET MVC 1. Thanks!
Friday, 09 March 2012 03:16
Decease
Oh! I'm find: http://weblogs.asp.net/imranbaloch/archive/2010/07/10/concurrent-requests-in-asp-net-mvc.aspx
Friday, 09 March 2012 08:26
Marty
Hi good article. The only correction is the currency part in webfarms. The session state provider ensures atomic updates are made to a central db or in our case cache server. Therefore there will not be inconsistent results. The session state module will retry acquiring the lock at 1/2 second intervals per the documentation. Other than that you are correct, the serialization to accomodate mutiple concurrent threads is an issue but given the design, it is as good as it is going to get. You don't want a chatty state server and you need atomic transactions.
cheers!
Marty
Thursday, 14 February 2013 18:10
efaruk
Lock mechanism exist on both provider and session module (IIS Session Module). You can develop custom session module, but you still need provider without locking or You can develop custom provider without locking but you still need IIS session module and it is not so simple to implement at that level.

The Solution is [UnlockedStateProvider][1] [aka Unlocked]


[1]: https://github.com/efaruk/playground/tree/master/UnlockedStateProvider


Follow the white rabbit :P (Check the demo project, it explains everything.)
Monday, 15 December 2014 09:51
Add a Comment