That is to say, if you have two requests being handled at the same time, from the same browser instance, and both use session_start(), then one blocks the other until the session is closed either explicitly (session_write_close()) or implicitly request terminates.
This is of course completely intentional and correct behaviour, and is done to ensure concurrent access to $_SESSION is safe, "thread safe" if you like.
9/10 you won't even notice this, users don't load multiple pages simulateously in general (frames used to be the time you would see it most, but frames have fallen out of favor in the last, err decade :-)).
But the world is changing, the advent of AJAX and other acronyms of merit have caused the times when you do need to be making multiple simultaneous requests to PHP much more common, and in this case having a blocking $_SESSION can really bite you in performance stakes, particularly, if like many, you just do session_start() at the beginning of each request (in your initial "set stuff up" include generally) so you can use $_SESSION freely from then on - even if that particular request isn't going to need it (you don't know in advance).
Improving the session handling.
There are certainly ways in which you could improve the session handling:
- You could write your own session handler object to store the session data in a database, with fancy locking commands, and take great care not to generate race conditions etc so that you can interleave your async requests to the maximum possible performance. To me, to just give a small site a bit of a speed boost, that's quite a bit of work. It also means that if you are incorporating third party software, you may need to faff about quite a bit to get it to work with your non-standard session handling.
- You could alter your code to carefully only session_start() just before you need it, and preferably session_write_close() as soon as possible when you are finished with the $_SESSION super global. But that's really awkward and rather unpleasant to maintain.
- Hijack the $_SESSION superglobal so that it does session_start() at the last possible moment that it is required. This way the only thing you need to change is basically to replace your call to session_start(), you can continue to use $_SESSION as you wish but the session will not be started, and most importantly it won't lock, until you first read or write to $_SESSION. Sure, it will remain locked until the request ends, but that's not so important because if the request doesn't use $_SESSION, it won't lock it.
Quite simply, we take advantage of the fact that even if session_start() has not been called, we can assign an object to $_SESSION and it will be superglobal like normal, and that when you do session_start() your object automagically gets nuked and replaced with the real $_SESSION superglobal.
The object we assign is a very simple implementation of the ArrayAccess interface, on any get, set, exists or unset it simply does a session_start() and re-sends the request. Once that first session_start() is done, our object is nuked so any further requests will go direct to $_SESSION.
Where's the code?
Here you go, it's only brand new and so there are probably ways in which it could be improved, the most important I think would be to send the session caching headers first up, because now session_start() may well not happen until after output has started, depending on your use of $_SESSION.
class gogoSession implements arrayAccess
function __construct($ForceNewSession = FALSE)
// Because session_start() could conceivably happen after output has
// started, we set the session cookie now if it wasn't sent to us.
// Note that we have to generate our own session_id.
if($ForceNewSession || !strlen(@$_COOKIE[session_name()]))
setcookie(session_name(), session_id(), 0, '/');
function offsetSet($Key, $Value)
return $_SESSION[$Key] = $Value;
// And this is how you use it, instead of the FIRST session_start (you don't want to be doing this multiple times)
$_SESSION = new gogoSession();
Other related posts:
Xero vs. Quickbooks, from a Quickbooks User
Vodafone Website Failure Fails
CSS namespacing, somebody tell me what I'm doing wrong.
Comment by hellonearthisman, on 18-Jun-2009 06:16
Comment by Janis, on 8-Sep-2009 22:50
Hello! Thanks for the article that at least confirmed why I had a problem with my script.
However, about your solution. It doesn't help in a situation when both (or all if more) requests actually do need to get the session values, does it?
Like, I make a request that reads from the session thus locking it up. And if I then make a second request, it will still be paused where it tries to get a session value?
Comment by korexus, on 13-Jan-2011 09:51
Thanks for this. It put me onto a similar idea which may be useful to other people.
I had an application in which two ajax requests needed to access the server, one took a long time to execute, the other was quick but both needed to access $_SESSION and there was no way to force the quick one to go first.
My solution was to put in individual calls to session_start() immediately before accessing $_SESSION and session_write_close() immediately afterwards. This does introduce some overhead especially for the slow script as it keeps opening and closing the session file, but means that the session is locked for a minimum amount of time. You can also save some work by taking a copy of the $_SESSION array at the start, that way you only need to call session_start() when writing.
N.B. In my case the two processes only write to session variables which are exclusive to them, so this process is safe. Be careful if you are sharing values between scripts as you could get inconsistent session variables with this method!
Comment by Michael Mior, on 29-Jul-2011 08:41
Cool idea! Seems it's still very relevant even a couple years later, since I've run into a similar problem. I think I'll have to try this out :)