Session lock causes ASP.Net websites to be slow
Asked Answered
E

12

301

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:

  • Any time 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?

Emend answered 2/9, 2010 at 17:44 Comment(7)
You do realize that this site is built atop .NET. That said, I think it scales quite nicely.Schuyler
OK, so I was being a little facetious with my title. Still, IMHO the performance chocking that the out of the box implementation of session imposes is startling. Also, I bet the Stack Overflow guys have had to do a good bit of highly custom dev to get the performance and scalability they have achieved - and kudos to them. Lastly, Stack Overflow is an MVC APP, not WebForms, which I bet helps (although admittedly this still used the same session infrastructure).Emend
Article on this from an MVC perspectiveExistence
If Joel Mueller gave you the information to fix your issue, why didn't you mark his answer as the correct answer? Just a thought.Parsimony
@Parsimony - Joel Muller provided lots of good information, and I wanted to thank him for that. However, I ultimately went with a different route than the one suggested in his post. Hence, marking a different post as the answer.Emend
Have you checked if your site starts faster without https? If so, try the fix in this MCS post: blogs.msdn.microsoft.com/mcsuksoldev/2011/01/19/…Diurnal
How did you understand that your session is locked? Is it possible to understand programmatically that session is locked?Tews
E
86

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...

Emend answered 7/9, 2010 at 16:56 Comment(14)
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.Ringlet
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.Ringlet
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.Ringlet
Thanks again Joel. I spotted the lock (typeof) issue, and that was an easy switch out (interesting article here about this: 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.Emend
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.Ringlet
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.Ringlet
MSDN has an article with a sample implementation of ISessionStateItem here: msdn.microsoft.com/en-us/library/…. I will mess with that, and report back. This rabbit hole is going deeper than I first thought!Emend
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: codeproject.com/KB/cs/safe_enumerable.aspx.Emend
James - obviously this is a fairly old topic, but I was wondering if you were able to share your ultimate solution? I've tried to follow along using the thread of comments above but so far have not been able to get a working solution. I'm fairly certain that there's nothing fundamental in our limited use of the session that would require locking.Schapira
James I agree with @bsiegel, if you could share the finished product that would help people out a lot.Benge
@Emend Any chance you can share the code for your solution?Budworth
Please see my implementation if somebody needs the code: https://mcmap.net/q/99717/-session-lock-causes-asp-net-websites-to-be-slowClue
One thing to keep in mind is that the usual Session is case insensitive while the hashtable would be case-sensitive. So this is not always a drop-in replacement for the default session.Aleta
I urge everyone NOT to use the code in the linked MS article since it's outdated. It loses session variable inside "AcquireRequestState" event sometimes etc. etc. I spent hours debugging and recommend everyone use the Nuget package by @Clue from this answer since it's way more reliable (and based on the same code). https://mcmap.net/q/99717/-session-lock-causes-asp-net-websites-to-be-slowFutile
R
210

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.

Ringlet answered 2/9, 2010 at 17:50 Comment(7)
Hi Joel Thanks 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.Emend
@Emend - 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.Ringlet
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.Ringlet
So develop a system that caters to the lowest common denominator programmers that can't figure out locking? Is the purpose to enable web farms that share a session store between IIS instances? Can you give an example of something that you would store in a session variable? I can't think of anything.Marentic
Yes, this is one of the purposes. Rethink the various scenarios when load balancing and redundancy is implemented in the infrastructure. When user works on the webpage, i.e. he is entering data in the form, for, let's say, 5 minutes, and something in the webfarm crashes - powersource of one node goes puff - the user should NOT notice that. He cannot be kicked out of the session just because his session was lost, just because his workerprocess does not exist anymore. This means that to handle perfect balancing/redundancy, the sessions must be externalized from worker nodes..Vellavelleity
Another useful level of opt-out is <pages enableSessionState="ReadOnly" /> in web.config and use @Page to enable write on specific pages only.Daube
+ blogs.msdn.microsoft.com/amitsh/2007/07/31/…Slipshod
E
86

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...

Emend answered 7/9, 2010 at 16:56 Comment(14)
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.Ringlet
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.Ringlet
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.Ringlet
Thanks again Joel. I spotted the lock (typeof) issue, and that was an easy switch out (interesting article here about this: 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.Emend
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.Ringlet
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.Ringlet
MSDN has an article with a sample implementation of ISessionStateItem here: msdn.microsoft.com/en-us/library/…. I will mess with that, and report back. This rabbit hole is going deeper than I first thought!Emend
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: codeproject.com/KB/cs/safe_enumerable.aspx.Emend
James - obviously this is a fairly old topic, but I was wondering if you were able to share your ultimate solution? I've tried to follow along using the thread of comments above but so far have not been able to get a working solution. I'm fairly certain that there's nothing fundamental in our limited use of the session that would require locking.Schapira
James I agree with @bsiegel, if you could share the finished product that would help people out a lot.Benge
@Emend Any chance you can share the code for your solution?Budworth
Please see my implementation if somebody needs the code: https://mcmap.net/q/99717/-session-lock-causes-asp-net-websites-to-be-slowClue
One thing to keep in mind is that the usual Session is case insensitive while the hashtable would be case-sensitive. So this is not always a drop-in replacement for the default session.Aleta
I urge everyone NOT to use the code in the linked MS article since it's outdated. It loses session variable inside "AcquireRequestState" event sometimes etc. etc. I spent hours debugging and recommend everyone use the Nuget package by @Clue from this answer since it's way more reliable (and based on the same code). https://mcmap.net/q/99717/-session-lock-causes-asp-net-websites-to-be-slowFutile
E
31

I started using the AngiesList.Redis.RedisSessionStateModule, which aside from using the (very fast) Redis server for storage (I'm using the windows port -- though there is also an MSOpenTech port), it does absolutely no locking on the session.

In my opinion, if your application is structured in a reasonable way, this is not a problem. If you actually need locked, consistent data as part of the session, you should specifically implement a lock/concurrency check on your own.

MS deciding that every ASP.NET session should be locked by default just to handle poor application design is a bad decision, in my opinion. Especially because it seems like most developers didn't/don't even realize sessions were locked, let alone that apps apparently need to be structured so you can do read-only session state as much as possible (opt-out, where possible).

Existence answered 2/3, 2012 at 20:25 Comment(2)
Your GitHub link seems to be 404-dead. libraries.io/github/angieslist/AL-Redis seems to be the new URL?Giddy
Looks like the author wanted to remove the library, even from the second link. I would be hesitant to use an abandoned library, but there's a fork here: github.com/PrintFleet/AL-Redis and an alternative library linked from here: https://mcmap.net/q/101778/-redis-backed-asp-net-sessionstate-provider-closedArmes
C
21

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:

Install-Package Heavysoft.LockFreeSessionState.HashTable

ScaleOut StateServer module:

Install-Package Heavysoft.LockFreeSessionState.Soss

Custom module:

Install-Package Heavysoft.LockFreeSessionState.Common

If you want to implement support of Memcached or Redis, install this package. Then inherit the LockFreeSessionStateModule class and implement abstract methods.

The code is not tested on production yet. Also need to improve error handling. Exceptions are not caught in current implementation.

Some lock-free session providers using Redis:

Clue answered 10/8, 2014 at 17:7 Comment(7)
It requires libraries from ScaleOut solution, which is not free?Smtih
Yes, I created implementation for SOSS only. You can use the mentioned Redis session providers, it's free.Clue
Maybe Hoàng Long missed the point that you have a choice between the in-memory HashTable implementation and ScaleOut StateServer.Yecies
Thanks for your contribution :) I'll be giving it a try to see how it acts upon the few use cases we have with SESSION involved.Phonotypy
Since a lot of people have mentioned getting a lock on a specific item in the session, it might be good to point out that the backing implementation needs to return a common reference to the session value across get calls in order to enable locking (and even that won't work with load-balanced servers). Depending on how you use the session state, there is potential for race conditions here. Also, it seems to me that there are locks in your implementation that don't actually do anything because they only wrap a single read or write call (correct me if I'm wrong here).Kile
A robust thread-safe solution would require the session provider itself supporting an interface for locking individual session items.Kile
The provider does not support locks across multiple web requests. It's called "lock-free", it means that you understand and accept that session consistency is not guaranteed. Locks are used to implement thread-safe data structures only. Use Redis, if you need an ability to lock individual session/cache items.Clue
L
19

If you are using the updated Microsoft.Web.RedisSessionStateProvider(starting from 3.0.2) you can add this to your web.config to allow concurrent sessions.

<appSettings>
    <add key="aspnet:AllowConcurrentRequestsPerSession" value="true"/>
</appSettings>

Source

Longitudinal answered 20/2, 2019 at 18:22 Comment(4)
Not sure why this was at 0. +1. Very useful.Muse
Does this work in classic mode app pool? github.com/Azure/aspnet-redis-providers/issues/123Summons
does this work with the default inProc or Session state service provider?Chaotic
Note that the poster references if you are using RedisSessionStateprovider, but it might also work with these newer AspNetSessionState Async providers (for SQL and Cosmos) because it's also in their documentation: github.com/aspnet/AspNetSessionState My guess is that it would work in classicmode if the SessionStateProvider already works in classic mode, likely the session state stuff happens inside of ASP.Net (not IIS). With InProc it may not work but would be less of an issue because it solves a resource contention issue that's a bigger deal with out of proc scenarios.Venal
M
11

Unless your application has specially needs, I think you have 2 approaches:

  1. Do not use session at all
  2. Use session as is and perform fine tuning as joel mentioned.

Session is not only thread-safe but also state-safe, in a way that you know that until the current request is completed, every session variable wont change from another active request. In order for this to happen you must ensure that session WILL BE LOCKED until the current request have completed.

You can create a session like behavior by many ways, but if it does not lock the current session, it wont be 'session'.

For the specific problems you mentioned I think you should check HttpContext.Current.Response.IsClientConnected. This can be useful to to prevent unnecessary executions and waits on the client, although it cannot solve this problem entirely, as this can be used only by a pooling way and not async.

Machinery answered 2/5, 2013 at 12:21 Comment(0)
M
8

For ASPNET MVC, we did the following:

  1. By default, set SessionStateBehavior.ReadOnly on all controller's action by overriding DefaultControllerFactory
  2. On controller actions that need writing to session state, mark with attribute to set it to SessionStateBehavior.Required

Create custom ControllerFactory and override GetControllerSessionBehavior.

    protected override SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)
    {
        var DefaultSessionStateBehaviour = SessionStateBehaviour.ReadOnly;

        if (controllerType == null)
            return DefaultSessionStateBehaviour;

        var isRequireSessionWrite =
            controllerType.GetCustomAttributes<AcquireSessionLock>(inherit: true).FirstOrDefault() != null;

        if (isRequireSessionWrite)
            return SessionStateBehavior.Required;

        var actionName = requestContext.RouteData.Values["action"].ToString();
        MethodInfo actionMethodInfo;

        try
        {
            actionMethodInfo = controllerType.GetMethod(actionName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
        }
        catch (AmbiguousMatchException)
        {
            var httpRequestTypeAttr = GetHttpRequestTypeAttr(requestContext.HttpContext.Request.HttpMethod);

            actionMethodInfo =
                controllerType.GetMethods().FirstOrDefault(
                    mi => mi.Name.Equals(actionName, StringComparison.CurrentCultureIgnoreCase) && mi.GetCustomAttributes(httpRequestTypeAttr, false).Length > 0);
        }

        if (actionMethodInfo == null)
            return DefaultSessionStateBehaviour;

        isRequireSessionWrite = actionMethodInfo.GetCustomAttributes<AcquireSessionLock>(inherit: false).FirstOrDefault() != null;

         return isRequireSessionWrite ? SessionStateBehavior.Required : DefaultSessionStateBehaviour;
    }

    private static Type GetHttpRequestTypeAttr(string httpMethod) 
    {
        switch (httpMethod)
        {
            case "GET":
                return typeof(HttpGetAttribute);
            case "POST":
                return typeof(HttpPostAttribute);
            case "PUT":
                return typeof(HttpPutAttribute);
            case "DELETE":
                return typeof(HttpDeleteAttribute);
            case "HEAD":
                return typeof(HttpHeadAttribute);
            case "PATCH":
                return typeof(HttpPatchAttribute);
            case "OPTIONS":
                return typeof(HttpOptionsAttribute);
        }

        throw new NotSupportedException("unable to determine http method");
    }

AcquireSessionLockAttribute

[AttributeUsage(AttributeTargets.Method)]
public sealed class AcquireSessionLock : Attribute
{ }

Hook up the created controller factory in global.asax.cs

ControllerBuilder.Current.SetControllerFactory(typeof(DefaultReadOnlySessionStateControllerFactory));

Now, we can have both read-only and read-write session state in a single Controller.

public class TestController : Controller 
{
    [AcquireSessionLock]
    public ActionResult WriteSession()
    {
        var timeNow = DateTimeOffset.UtcNow.ToString();
        Session["key"] = timeNow;
        return Json(timeNow, JsonRequestBehavior.AllowGet);
    }

    public ActionResult ReadSession()
    {
        var timeNow = Session["key"];
        return Json(timeNow ?? "empty", JsonRequestBehavior.AllowGet);
    }
}

Note: ASPNET session state can still be written to even in readonly mode and will not throw any form of exception (It just doesn't lock to guarantee consistency) so we have to be careful to mark AcquireSessionLock in controller's actions that require writing session state.

Marry answered 9/2, 2017 at 2:27 Comment(1)
M
7

Marking a controller's session state as readonly or disabled will solve the problem.

You can decorate a controller with the following attribute to mark it read-only:

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]

the System.Web.SessionState.SessionStateBehavior enum has the following values:

  • Default
  • Disabled
  • ReadOnly
  • Required
Maramarabel answered 5/7, 2017 at 22:2 Comment(0)
H
4

This answer about allowing concurrent request per session is great but it is missing some important details:

  1. The setting to allow concurrent requests per session is implemented in the newer ASP .NET Session state module which is of type Microsoft.AspNet.SessionState.SessionStateModuleAsync. This setting is supported for any provider who can work with this module.
  2. The older sessionstate module System.Web.SessionState.SessionStateModule does not support this.
  3. Make sure session state usage is thread safe or concurrency issue can occur in the session

Summary to enable this feature:

Allow concurrent request:

<appSettings>
    <add key="aspnet:AllowConcurrentRequestsPerSession" value="true"/>
</appSettings>

Make sure newer session state module is used:

<system.webServer>
  <modules>
    <!-- remove the existing Session state module -->
    <remove name="Session" />
    <add name="Session" preCondition="integratedMode" type="Microsoft.AspNet.SessionState.SessionStateModuleAsync, Microsoft.AspNet.SessionState.SessionStateModule, Version=1.1.0.0, Culture=neutral" />
  </modules>
</system.webServer>
Hindoo answered 15/10, 2021 at 20:16 Comment(1)
You need add this package Microsoft.AspNet.SessionState.SessionStateModule to your project besides the web.config change.Factotum
E
1

After struggling with all available options, I ended up writing a JWT token based SessionStore provider (the session travels inside a cookie, and no backend storage is needed).

http://www.drupalonwindows.com/en/content/token-sessionstate

Advantages:

  • Drop-in replacement, no changes to your code are needed
  • Scale better than any other centralized store, as no session storage backend is needed.
  • Faster than any other session storage, as no data needs to be retrieved from any session storage
  • Consumes no server resources for session storage.
  • Default non-blocking implementation: concurrent request won't block each other and hold a lock on the session
  • Horizontally scale your application: because the session data travels with the request itself you can have multiple web heads without worrying about session sharing.
Elledge answered 30/5, 2020 at 18:43 Comment(0)
G
0

Just to help anyone with this problem (locking requests when executing another one from the same session)...

Today I started to solve this issue and, after some hours of research, I solved it by removing the Session_Start method (even if empty) from the Global.asax file.

This works in all projects I've tested.

Gittern answered 30/9, 2016 at 19:19 Comment(2)
IDK what type of project this was on, but mine doesn't have a Session_Start method and still locksGoran
"I've discovered that this behavior only occurs when the debugger is attached (running with F5). If you run it without the debugger attached (Ctrl-F5) then it seems to be ok. So, maybe it's not a significant problem but it's still strange." src #4452286Grandchild
I
0

For Mono users out there that encountered this issue and found none of the solutions helpful, you are not doing anything wrong.
There is a bug in Mono (Issue #19618) that makes SessionStateBehavior useless on SessionStateModule, so it doesn't matter if you set SessionStateBehavior on Web.config/pages, Application_BeginRequest, or set an attribute on a Controller, or an Action. None will work. I tried.

However, the logic that prevents locking (calling GetItem instead of GetItemExclusive on SessionStateModule) is there with one limitation: The HttpHandler must implement the marker interface IReadOnlySessionState.

So instead of implementing my own SessionStateModule, I took a different (bit hacky) approach.


To your consideration:

// Custom handler that derives from MvcHandler which implements IReadOnlySessionState
public class MvcReadOnlyHandler : MvcHandler, IReadOnlySessionState
{
    public MvcReadOnlyHandler(RequestContext requestContext) : base(requestContext)
    {
    }
}
// Custom RouteHandler that derives from `MvcRouteHandler` which
// returns our very own `MvcReadOnlyHandler`
public class MvcConcurrentRouteHandler : MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new MvcReadOnlyHandler(requestContext);
    }
}
// On Global.asax.cs Application_Start, after all the Routes and Areas are registered
// change only the route handler to the new concurrent route handler
foreach (var routeBase in RouteTable.Routes)
{
    // Check if the route handler is of type MvcRouteHandler
    if (routeBase is Route { RouteHandler: MvcRouteHandler _ } route)
    {
         // Replace the route handler
         route.RouteHandler = new MvcConcurrentRouteHandler();
    }
}

Since now the router do implements IReadOnlySessionState there is no locking on the session id

Hopefully when the bug will be fixed my solution will be redundant, but untill then, I hope it will help someone.


Important note: This solution basically makes storing items on the Session unsafe, I don't use this feature so for me it works. You can still add items since ReadOnly does not prevent writing, it is just not locking.
If you want to guarantee safe writing, you can add another extension method MapRoute to RouteCollection to use the new router, in order to register routes that doesnt lock. Like that you can register your routes to new MvcConcurrentRouteHandler router or to the existing one for writing.

Instigate answered 22/5, 2022 at 7:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.