25

Is there any way to disable the session locking in PHP while using the default session handler?

[EDIT:] Or is there at least a way to restart a session after calling session_write_close()? session_start() does not work if any output is already sent to the browser.

hakre
  • 193,403
  • 52
  • 435
  • 836
Taloncor
  • 371
  • 1
  • 4
  • 7
  • 1
    Interesting article explaining the background of session locks: https://ma.ttias.be/php-session-locking-prevent-sessions-blocking-in-requests/ – stollr Jan 24 '20 at 11:57

7 Answers7

24

You don't want to disable it... If you do, you'll potentially run into all sorts of weird issues where you login on one window, be logged out on another and then wind up in an inconsistent state... The locking is there for a reason...

Instead, close the session very early if you know you are not going to write to it in that request. Once you start it, you'll be able to read from it for the whole request (unless you re-start it, or do some other special things) even after you call session_write_close. So what you would do, is check the request to see if it's a write request, and if it's not, just close it right after opening it. Note that this may have some adverse affects if you later try writing to the session (For Captcha or CSRF protection or something else), so use with caution...

But instead of trying to get around it, I would put my effort into either shortening the request lengths (to reduce lock contention), or making cookieless requests for those requests that don't need the session at all...

ircmaxell
  • 163,128
  • 34
  • 264
  • 314
  • No really an answer to the question, but very helpful in other ways. Thank you. I invoke long running from my scripts and missed the obvious solution to call session_write_close beforehand and session_start afterwards. – Taloncor Jul 30 '10 at 13:13
  • You need to call `session_write_close` AFTER `session_start`. Otherwise it's not going to do anything. I'm not sure if it's possible to re-start the session later, but you can try that... – ircmaxell Jul 30 '10 at 13:23
  • What doesn't work? What do you mean restart a session? More details are needed... – ircmaxell Jul 30 '10 at 14:28
  • 1
    So, the user hits 3 pages simultaneously, and one of those happens to be the logout page. 1 page loads, 1 page logs out, and the 3rd page says they need to login. **Is this really a big problem?** That's the developer's decision. Some of us are left with legacy code and what we need to fix is to allow concurrent connections in the existing code...if you're storing stuff in the session that a race condition would break...you probably need to stop it. By removing locking, I got a 2 minute report down to 30 seconds. A cookie-less request would be better, but that requires more refactoring. – Kevin Nelson Mar 20 '15 at 17:23
  • _"Is this really a big problem?"_ OK, I hope in the last 7 years you have had more than enough lost input (form data) to have the answer now. ;) – Sz. Dec 13 '22 at 14:07
  • @KevinNelson is correct. PHP sessions behavior are nonsense. The fact that it locks everything is unacceptable and the fact there's NO simple setting to make it lock-free is a mistake. – Martin Zvarík Mar 30 '23 at 16:30
20

I had a report page that took over 2 minutes (over 80 AJAX requests). I got it down to under 30 seconds by removing session-locking. Yes, heaven forbid you remove file locking, because then you have race conditions. And if you don't understand race-conditions and you don't know what the effect would be on your sessions...then do NOT remove file-locking. However, if you, knowing what data that is in your sessions, and knowing what race-conditions do, feel that there is no data that could be adversely affected by race conditions which would create a bug...you know your environment better than anyone else does, so go for it.

MySQL, REDIS, and Memcache

Also, note that if you switch to MySQL for session management, there's a 99% chance, IMHO, that you do NOT lock the row from the time you read to the time you write. So, using MySQL you still have the same race conditions (or locking problems if you decide to lock the row).

Based on the information I could find, people who use PHP Redis are using a non-locking application that is prone to race conditions...as per the following thread...and they cite speed as one of the reasons they like this "feature":

https://github.com/phpredis/phpredis/issues/37

Memcached didn't support session locking until version 3.0.4...so it was also--initially--prone to race conditions.

Clearly, with the success of these options, race conditions are not the biggest problem that programmers face.

Ultimately the Issue Is

ALL concurrent requests will ALWAYS be subject to race-conditions UNLESS you do file locking, at which point they are not concurrent requests anymore.

The important thing about Sessions and Locking vs. Concurrency and race conditions is to know your application, know if a race condition could break your application...and devise a solution that fits YOUR application. If all you do is store the userId in the session and read it on all subsequent requests, you probably don't need to worry about race conditions. If you store an elaborate history of data that will break if things happen out of order or data might be lost, then lock the file between reading and writing and try to do your write as quickly after the read as possible to limit the amount of time the file is locked.

The Best Option

A session-less API will, however, be much better for concurrent requests. However, if you don't have time to refactor to such an API...then read-on.

A Stop-Gap Solution to keep using PHP session files and stop locking

To continue using PHP sessions the default way, to stop locking, and basically have a very quick solution to a complex problem, you can implement the PHP website's example implementation of a SessionHandler.

I have the below code running in a production environment for a site with tens of thousands of connections per minute, and I haven't had any problems with race conditions yet, but I also don't store data that a race-condition could break. This code, as I said, got one report from over 2 minutes down to under 30 seconds...and it took a couple of minutes to implement. No MySQL schema to create, no Memcache or Redis to install.

This is, to a letter, the example implementation provided on PHP's documentation (http://php.net/manual/en/class.sessionhandlerinterface.php), and it doesn't lock the session file when it reads it.

NOTE As Anther pointed out in this comments, this will not work in a distributed system unless you save the file to a single server.

<?php
class MySessionHandler implements SessionHandlerInterface
{
    private $savePath;

    public function open($savePath, $sessionName)
    {
        $this->savePath = $savePath;
        if (!is_dir($this->savePath)) {
            mkdir($this->savePath, 0777);
        }

        return true;
    }

    public function close()
    {
        return true;
    }

    public function read($id)
    {
        return (string)@file_get_contents("$this->savePath/sess_$id");
    }

    public function write($id, $data)
    {
        return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true;
    }

    public function destroy($id)
    {
        $file = "$this->savePath/sess_$id";
        if (file_exists($file)) {
            unlink($file);
        }

        return true;
    }

    public function gc($maxlifetime)
    {
        foreach (glob("$this->savePath/sess_*") as $file) {
            if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
                unlink($file);
            }
        }

        return true;
    }
}

In PHP 5.4+, using it is as simple as setting the handler before you start your session:

$handler = new MySessionHandler();
session_set_save_handler($handler, true);
session_start();

For lower versions of PHP, you can still do it via function calls...see the PHP docs.

Kevin Nelson
  • 7,613
  • 4
  • 31
  • 42
  • 1
    This code is blatantly susceptible to race condition errors (which is why session locking exists). Use a data store appropriate for the use case (i.e. not files. redis, even mysql is likely more suitable) and things are more robust. – AD7six Mar 20 '15 at 16:17
  • 5
    @AD7six, I agree. It is blatantly prone to race conditions, which is why I spelled that out when describing it. **So is a MySQL session if you don't lock the row for the entire request**. When you have 6 concurrent AJAX connections for intensive reporting, locking just isn't an option...and the session, in our case, doesn't have anything that a race condition would hurt. Whether or not a race condition is problematic is for the developer to decide based on what they hold in that session. This does answer the OP's question, however, which was how to not lock. It's a valid answer. – Kevin Nelson Mar 20 '15 at 16:50
  • @AD7six, redis doesn't lock by default and is prone to race conditions...but as some commenters point out here, their primary concern is speed not file-locking: https://github.com/phpredis/phpredis/issues/37 – Kevin Nelson Mar 20 '15 at 18:01
  • The file writing solution would only work if your code is not distributed across multiple boxes. – Anther Feb 10 '21 at 15:34
  • @Anther, thanks for the feedback. I added a note mentioning that above the code. I don't imagine that anyone using a distributed system would be using sessions like this anyway, but probably worth noting. – Kevin Nelson Feb 11 '21 at 15:48
  • Locking also protects the session files against premature accidental removal by housekeeping background (e.g. cleanup) processes. I'd wager that judicious use of and early closing of sessions plus the cost of some request serialization with rare heavy peak loads, with locking, is a *much* broader, *much* simpler and *much* safer use case than the opposite, trying to rely solely on the perfect understanding of an (often pretty complex) parallel system. For that 1% perf. critical apps, do go ahead lockless, but as a general advice, that would be *very* shaky. – Sz. Dec 13 '22 at 14:18
  • 2
    I'm really curious what y'all are storing in sessions that you're worried about not locking. If you need locks on data, seems like you need to store whatever this mystery data is in a transactional DB, not in a session identifier. IMHO, the session should only be used to store the User Id of an authenticated user. At that point, the worst that can happen is that they get a couple milliseconds where they could potentially still open a new page after they logout because they will still be logged in before the logout script deletes the session. – Kevin Nelson Dec 27 '22 at 22:22
  • This is a solution if you have a lot of 3party code that calls session_start(). Then unfortunately you can't properly close sessions your self. This solves this issue. – Gellweiler Feb 14 '23 at 18:40
2

This is a rather old question which I came across while researching session handlers, however the answer is yes it is possible - but not using the default handler (without deep hacking into the filesystem to disable locking).

I have come across the same issue. The important thing to note is that you really must know exactly the consequences of disabling locking!

My solution is part of a larger extension to the session handling mechanism - the Stackable Session Handler which includes storage compatible with the default handler and (optionally) non-blocking session reads and writes.

symcbean
  • 47,736
  • 6
  • 59
  • 94
1

You can restart the session by recalling session_start() after session_write_close(). However this will cause multiple SIDS. I resolve this by removing the multiple SIDS from the header before the output is flushed.

See this example: https://gist.github.com/CMCDragonkai/6912726#file-nativesession-php-L254

CMCDragonkai
  • 6,222
  • 12
  • 56
  • 98
0

There is no way of disable locks from php sessions. It's a real nighmare use case of locking. Only way to get rid of sessions and/or php. As a temporary solution you may want to use riak session handler: https://github.com/zacharyfox/riak-php-sessions it's lock free, and it's working.

Last versions of memcached session handler also introduces locking for some insane reasons and no way to disable it.

d0rc
  • 115
  • 7
0

If PHP is not handling requests asynchronously even after calling session_write_close, it may just be xdebug. I don't know if that is your issue, but I keep getting tripped up by this and driving myself crazy over it so I thought I'd post it if anyone else is having the same issue :)

Meir
  • 171
  • 1
  • 7
0

Other than using session_write_close() ? None that I know of.

Mark Baker
  • 209,507
  • 32
  • 346
  • 385