Handling concurrent access to ASP.NET MVC’s session state

Handling concurrent access to ASP.NET MVC’s session state

How concurrent access to the session is handled

Access to the session state is per session exclusive, which means that a request to the session state applies an exclusive lock on the session object. Other requests trying to access the same session object concurrently will then have to wait until the first request has completed and released the lock. On the other hand, concurrent requests accessing different sessions can be handled concurrently.

To illustrate the exclusive access to the same session state I’ve created a simple MVC application with a controller that contains the action Work with the following setup:

 [HttpGet]

        publicActionResult Work()

        {

            Thread.Sleep(6000);

 

            return Json(Session["user"], JsonRequestBehavior.AllowGet);

        }

In the client, three requests are calling the Work action trying to concurrently access session information:

        $(function() {

            var work = function(id) {

                $.get('home/work/' + id, function() {

                    console.log(arguments);

                });

            };

 

            for (var i = 0; i < 3; i++) {

                work(i);

            }

        });

 

Inspecting the requests in firebug one can see that the total time to process these three requests is 18 seconds, as illustrated in the figure below. The first request locks the session and the two other requests waits for it to complete. When the first request has completed, the second request access the session and adds a new lock to it. And the chain continues.

Overriding the session state

The example above contains no modifying operation to the session state and the session lock is just an unnecessary performance killer. Fortunately, it’s possible to override and declare how the controller will treat the session state. This can be accomplished by decorating the controller with the SessionState attribute. The attribute takes the SessionStateBehaviour enumeration as an in parameter to the constructor. The enumeration has the following members:

  • Disabled – The session state is disabled for all requests towards the controller.
  • ReadOnly –Read access to the session state is granted but the controller cannot update any session information. Requests towards a read-only session does not apply any locks to the session state.
  • Required – Both write and reads access are granted for all requests.
  • Default – ASP.NET uses the default logic to determine the session state for each request.

To show this in action, the controller from the previous example is now decorated with the SessionStateAttribute with ReadOnly access:

   [SessionState(SessionStateBehavior.ReadOnly)]

    publicclassHomeController : Controller

The timeline now shows that there are no exclusive locks on the session and all requests are handled concurrently:

Changing the session state behaviour conditionally

The attribute might not be sufficient in all scenarios. It might be required to change the state of controllers conditionally. The controllers’ session state behaviour can as well be set in a ControllerFactory’s GetControllerSessionBehaviour method. To exemplify this, I’ve created a controller factory that inherits from the DefaultControllerFactory and overrides the GetControllerSessionBehavior method.

The rule in this example is as follows: If no SessionStateAttribute has been added to the controller, use ReadOnly access (ReadOnly is now the default session state behaviour throughout the application).

 

 publicclassSessionStateControllerFactory : DefaultControllerFactory

    {

        protectedoverrideSessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)

        {

            if (controllerType == null || !Attribute.IsDefined(controllerType, typeof (SessionStateAttribute)))

            {

                returnSessionStateBehavior.ReadOnly;

            }

           

            // Use the default method to look up the actual value of the session attribute. It uses a cache for lookup

            returnbase.GetControllerSessionBehavior(requestContext, controllerType);

        }

    }

And register the new controller factory somewhere in the application start up:

ControllerBuilder.Current.SetControllerFactory(newSessionStateControllerFactory());

Summary

Unnecessary locks to the session state can be avoided by declaring the intent of how a controller uses the session data. Here I’ve showed how the intent can be declared using either the SessionState attribute or a controller factory. Try it out and let me know how it goes!