Description:
The GetItem function (get a read only session) doesn't check if the session is currently locked before incrementing the Lock ID and returning the session object.
The Microsoft documentation for GetItem indicates the function should check the current lock status and set the locked parameter to true if the session is currently locked. The ASP.NET session state code will then retry at 0.5 second intervals until the lock is freed.
This causes a problem if a read-only session page calls GetItem in between a read/write session page calling GetItemExclusive and SetAndReleaseItemExclusive as it increments the Lock ID preventing SetAndReleaseItemExclusive from releasing the lock. Any calls to GetItemExclusive in the next 110 seconds will be returned as locked causing the application to appear to freeze.
How to repeat:
Two ASP.NET pages are needed to demonstrate this problem.
1. write.aspx - A page with writeable sessions i.e. EnableSessionState="True" that takes a while to ruturn, for debugging a 10 second sleep in Page_Load is ideal.
2. read.aspx - A page with a read-only session i.e. EnableSessionState="ReadOnly". This page can return instantly.
To demonstrate the problem:
1. Request write.aspx
2. Request read.aspx before write.aspx has returned (e.g. in another tab)
3. Once both requests above have returned request write.aspx again.
Request 3 will not return until System.Web.Configuration.ExecutionTimeout seconds (110 by default) after request 1 began as it has to wait for the lock to be freed.
Suggested fix:
Follow the Microsoft documentation and make calls to GetItem return locked=true if the session is currently locked in the database.
Here's a diff between SessionProvider.cs in MySQL Connector/NET 6.4.4 and a fixed version based on that release.
*** SessionProvider.cs 2012-01-11 16:49:49.000000000 +0000
--- SessionProvider.fixed.cs 2012-01-11 16:56:47.000000000 +0000
***************
*** 525,529 ****
// Retrieve the current session item information.
cmd = new MySqlCommand(
"SELECT NOW(), Expires , SessionItems, LockId, Flags, Timeout, " +
! " LockDate " +
" FROM my_aspnet_sessions" +
--- 525,530 ----
// Retrieve the current session item information.
+ // Add the Locked column here so read-only sessions can check it
cmd = new MySqlCommand(
"SELECT NOW(), Expires , SessionItems, LockId, Flags, Timeout, " +
! " LockDate, Locked " +
" FROM my_aspnet_sessions" +
***************
*** 563,564 ****
--- 564,570 ----
lockAge = now.Subtract(lockDate);
+
+ // If it's a read-only session set locked to the current lock
+ // status (writable sessions have already done this)
+ if (!lockRecord)
+ locked = reader.GetBoolean(7);
}