Framework: Laravel 5.0 - Asyncronous AJAX Requests cause Session Variable Changes to be Overwritten

Created on 21 Feb 2015  ·  47Comments  ·  Source: laravel/framework

If a number of AJAX requests are fired which get processed simultaneously by the webserver, the session files do not lock causing session variables to be overwritten.

On rare occasions, the session file can become corrupted if one copy reads in a half written out file. This condition, however, is difficult to reproduces.

Practically, you probably shouldn't be firing a number of AJAX requests simultaneously, but it is always possible that a user quickly clicks on several buttons that result in requests and the server happens to process those requests simultaneously which would be a reasonable design and result in session problems.

I've put together I very simple demo of this using 26 variables (a-z) which are each incremented by a separate AJAX request. The final result isn't that all 26 variables have the value of 1 (which it should be).

https://github.com/wiseloren/laravel-race

I think this is referenced in #4441 #4576 #4400 I think in these issues the problem is in file corruption due to reads and writes happening at the same time to the session file, which as I mentioned above, I can't reliably reproduce (though I do think it happened at least once during my testing because half my variables disappeared).

Though it is outdated now #6848 seems to have been intended to fix this. If someone can verify this, I'm willing to work on updating the pull request and try to get the style in line which seems to have been the only previous complaint. I'm not sure how one would write a unit test for this since unit tests are executed synchronously and this only happens in async execution.

Most helpful comment

When making simultaneous XHR requests, session data may be lost due to concurrent requests updating the session at the same time. This is a known issue. One solution may be reducing the number of requests, or use *_session locking *_. This allows to lock the session file when used, hence forcing all requests to be executed one after the other. This may slow down your app, but eliminate the potential errors.

Laravel does not include session locking by default. Someone made a Laravel package to add the feature. https://github.com/rairlie/laravel-locking-session

It seems to have worked for me.

All 47 comments

This is known limitation of the file based session driver.

Does it not apply to any of the drivers? IE: Is there a driver I can use that won't have this problem?
Does this mean you have no interest in fixing it? As mentioned in my post, I'm willing to help code a fix, but I'm not going to waste my time on something you have no interest in having.

Looking at the session documentation (http://laravel.com/docs/5.0/session#session-drivers). I see that you have 5 great drivers which minus the arrays appear to be presented as equally good and useful. Does that mean that they all have this problem? I know cookies would since that can't lock; I think I've seen where your database implementation does in one of the other tickets; we've established files do; I guess that only leaves memcached/redis.

In other tickets, it's also been established that PHP native sessions and Symphony both implement locking. I'm confused why you don't want to.

@GrahamCampbell do you have a fix for this?

I have long POST request and ServerSentEvents request immediately after POST. When i used "file" session driver i got worked out SSE request after POST. When i used "redis" session driver i got SSE request worked out before long POST request.

I'm getting this problem with all tested drivers (native, file, memcached and redis) in Laravel 4.2.

Imagine a browser making multiple (ajax) requests with the same session id:
Request 1: Enters, loads session, does some heavy database processing.
Request 2: Enters, loads session, changes a session var, stores the session, returns a result.
Request 1: Finishes processing, stores previously loaded session, returns result.
Session changes in request 2 are effectively overwritten by request 1.

laravel5.1 & redis or file
一个ajax分页,快速地请求各个页面,一会儿,出现401, session 失效
A ajax page, quickly request each page, for a while, 401, session failure

i am having this issue with laravel 5.1 and redis. any solution?

ping @GrahamCampbell

ping @taylorotwell

Can't we use the native php session as 'file'? That implements locking, right?
Symfony does that already, so can't we use that? https://github.com/symfony/http-foundation/blob/3.0/Session/Storage/Handler/NativeFileSessionHandler.php

Maybe any having these troubles can try with the Symfony drivers? https://github.com/laravel/framework/blob/5.2/src/Illuminate/Session/SessionManager.php

use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
protected function createNativeDriver()
{
    $path = $this->app['config']['session.files'];
    return $this->buildSession(new NativeFileSessionHandler($path));
}

same here

same here ...

having the same issue here

When making simultaneous XHR requests, session data may be lost due to concurrent requests updating the session at the same time. This is a known issue. One solution may be reducing the number of requests, or use *_session locking *_. This allows to lock the session file when used, hence forcing all requests to be executed one after the other. This may slow down your app, but eliminate the potential errors.

Laravel does not include session locking by default. Someone made a Laravel package to add the feature. https://github.com/rairlie/laravel-locking-session

It seems to have worked for me.

Your solution worked for me @cumuluswebdesign, merci beaucoup!!!

@cumuluswebdesign that library worked here too, thank you so much!

Does anyone know of a quick way to replicate this issue on a local environment?

My original issue included a laravel project which shows the issue and should run fine locally (provided you are running a multi-threaded webserver which most are though local installs are occasionally set to be single threaded):
https://github.com/loren138/laravel-race

@loren138 thanks for that, but I need to replicate the issue in our code so I can prove that there is an issue as mentioned by a client and that we managed to resolve it.

Ah, basically you just need to make a bunch of nearly simultaneous AJAX requests. Alternatively, if you add a sleep(1) or sleep(2) in your problematic functions, then you should be able to click the buttons on the browser side fast enough to create the bug.

Is this a problem for memcache or redis drivers? I believe they don't have the ability to fail like the file driver?

@loren138 I've just tried wrapping my AJAX calls in a foreach loop but I still cannot replicate the issue. I have even tried putting in sleep(1) on the page that the AJAX is calling. Any ideas?

@rreynier The problem probably does exist for memcache and redis, just not to the full extent of file because they probably cannot be corrupted. The problem still exists that without locking a later session can overwrite the earlier or just be wrong.

Consider 3 incoming requests 1 second apart which each take 1.1 seconds to run and increment a session varaible.

Request A reads a value of 0 at time 0 and saves the value 1 at time 1.1
Request B reads a value of 0 at time 1 and saves the value 1 at time 2.1
Request C reads a value of 1 at time 2 and saves the value 2 at time 3.1

As you can see your incremented variable is all messed up and isn't really counting because it has out of order read/write.

@OAFCROB You might want to try a random sleep (ie sleep(rand(1,3));) to try to make sure the requests overlap. You could also look at your network panel (in developer tools of Chrome or Firefox) and make sure the requests are overlapping as that is what causes this bug.

Without entirely blocking the session this “problem” will always exist for Memcached / Redis. If you are having the issue perhaps consider incrementing the value in a more atomic fashion like using a Redis variable instead of placing the variable in the session. It sounds like the session is being mis-used in this case for something that is best suited for another tool.

We use the session to store a last active timestamp, which is then checked against a before filter. However, on one clients server this session is being wiped for some reason they believe it's related to this but we believe it's their server setup as we don't get on our servers for other clients.

Also we are currently still on Laraval 4.1.

@OAFCROB are you using the file driver or one of the others? With the file driver, it at least previously did not lock the file for reads/writes so a simultaneous read/write could occur and corrupt the session file thus killing the session. That bug only existed in the file driver though so we switched to using the DB driver for our apps. (I haven't checked to see if that is fixed in 5.1 or not.)

@taylorotwell The actual use case that I had a problem with was session variables. We store variables in the session for search/sort order of our reports. These variables are only stored per session (rather than in persistent storage) and are set with separate AJAX calls. The issues cropped up when we created a button that would restore certain saved searches (ie sort by price and search for "widgets") because sometimes only one of the two would get saved in the session. This was caused by the bug mentioned above. Presumably we could have combined those two calls into one request to prevent the bug, but a person could also type a search and immediately clicked out of that box (causing a save) onto a sort (causing another save) and create the same bug (which would have been much harder to debug since it would have been less obvious to reproduce) so we implemented a bunch of javascript locking on those saves client side to make sure it sent all the variable saves synchronously rather than asynchronously.

@loren138 we're using file, but I'm convinced the issue is to do with the fact the servers are being load balanced compared to our servers where we don't. It appears that the user is being logged out at some point meaning some information is lost resulting in a redirect to the login page.

If I can replicate this session issue I could implement https://github.com/rairlie/laravel-locking-session but at the moment I would be implementing it with no real idea if that's going to resolve the issue.

@OAFCROB Are you sharing the session files between the load balanced servers on some central file share or are your load balanced sessions "sticky" (ie a person always goes to server 2 where their session file is)? For the session file to corrupt, you have to have requests hit at the exact right moment such that a request opens the session file while another request is saving it (very hard to reproduce, but perhaps more likely in a file share senario since it would take longer to read/save the file?). I think that package would fix your problem (while also potentially slowing down requests). If you can, I think using one of the other session drivers (ie database) would also solve your problem.

@loren138 yes we're aware of that, but the client are hosting the app themselves and will not give us anymore information other than there is load balancing. I guess we could try the package or changing to the database method and if the issue continues then it will be down to their server setup and not the code.

@loren138 I am only concerned around corruption. Not having session locks means you have to build your app in such a way that session holds as little state as possible. I actually just removed session locking all together from an old CodeIgniter project and it seems to behave just fine. I don't store anything time sensitive in my session (in fact my goal is to only store user authentication state in session).

As long as I am not getting failure/corruption when I retrieve session data, I am happy (I use memcached or Redis).

To @taylorotwell point, I agree that if you are concerned with race conditions, you may want to look at other solutions since sessions may not be the right fit.

@OAFCROB We've been experiencing the issues on Laravel 5.2 when using http://flowjs.github.io/ng-flow/. Uploads (concurrent xhr requests) more or less always resulted in session overwrites. We've been testing this with file and sql sessions and could replicate for both.

@taylorotwell for sure, it's already on the agenda ....

If it's usefull...
I was experiencing these issues when a I was using php server to serve laravel for others computers on my network for testing porpouses. (php artisan serve --host=x.x.x.x --port=80)
So, I started a apache server and everything worked fine.

I found this, laravel-locking-session.
I tested it and it's working (only file and cookie).

Try out https://github.com/AltThree/Locker - Has solved this issue for me.

Having the same issue using database driver, while testing the app on my local machine.

Some of the sessions in my db

"5ORX5CCWkonxEFmgZezWU7YfQT9Yhxuep6gutQZc" "1" "::1" "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36" "payload" "1484775623" "IPXfl1YQSytubaWpdvFu41DqBmoNwaTaswaUzgYm" "1" "::1" "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36" "payload" "1484774846" "mIAFH0fIMcVko2af3nnWnAFp5IAIzJMdGdPVx8XE" "1" "::1" "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36" "payload" "1484775437" "MTKKuPVxs59QYdtwOEIVTzTzL0P65fl8Cs3QyGEa" "1" "::1" "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36" "payload" "1484773710"

And also I'm trying to implement multi auth in the application, how's multi auth possible if only user_id is saved without the model in a session.

I recently had this issue and the problem may have been a double-bind of the web middleware on a route. You guys should check that. I failed to notice that web was already applied in RouteServiceProvider. After removing 'middleware' => 'web' on my problem routes, this issue went away completely.

@defaye Your solution will surely work but with out authentication.

I also had similar problem with concurrent api calls using laravel authentication. I had to try rairlie/laravel-locking-session and hope this will work.

I hope this would be fixed in the future.. more power to artisans cheers !!

ilovelaravel

@micoboyet what do you mean without authentication?

Doubling the web middleware causes tons of problems with this on nearly every request (and has nothing to do with authentication since you are still using it once). This thread, however, is an issue with race conditions which occur on only occasional requests particularly if a several requests are sent in a short timeframe.

@loren138 if I understand you correctly, you're partly answering @micoboyet for me - yes - I implied that after removing the accidental double-bind, my session becomes reliable again and authentication can work. If however you are then going on to dismiss what I've contributed then I am thoroughly confused, as there will be plenty of others who will come here having unwittingly done the same thing as me. And that can happen when you have a mammoth routes file with various groups and child routes set up. Debugging an issue like that can become inherently time-consuming and this kind of warning message serves as a good clue for anyone else in the same boat.

@defaye it's fair that others may have made that mistake. (I've done it.) However, it sounded to me like your answer was saying all or most of us had made the same mistake as you (user error) so I was making it clear that there is a Laravel issue (documented as the first post here) that can cause this as well as a user error.

In summary, I know of two potential solutions to this problem. One the user has accidentally called the session middleware twice (either by calling the web group twice or calling it once and session once or some other way). If not that, then it is likely from Laravel not locking sessions in any way for which Laravel-locking-sessions is a work around. Alternatively, you can store your session data somewhere else or slow down your web calls so they don't overlap.

I'm still facing this same issue with laravel 5.6. None of the solutions here solved it for me. I don't know if i'm the one not implementing them right.

I use laravel for my back-end, but i did not embed my web files into laravel. It's an external API and so i use the api.php and not web.php to communicate with my laravel.

When i try to perform a multiple file upload of 6 images with filePond JS plugin, which i set to instantly upload, it only inserts 2 or 3 images into my database, but all images uploaded get into my folder directory.

When i tried setting instantUpload to false and tried sending them one at a time with my plugin, they all get inserted into my database and into my folder directory as well.

IT was until i came across this page before i decoded the problem.
Any solution? I don't seem to know how to implement any of the solutions above and their documentations are not very elaborate too me.

<input type="file" class="filepond" id="file" name="filepond" multiple data-max-file-size="3MB" data-max-files="6" required>

const inputElement = document.querySelector('#file');

const pond = FilePond.create( inputElement, {
        maxFiles: 6
});
 FilePond.setOptions({
        instantUpload: true,
        allowFileTypeValidation: true,
        acceptedFileTypes: ['image/*'],
        server: {
        url: 'http://localhost/laravel/public/api',
        process: 'url'
            }
});

Same here in Laravel 5.7

When making simultaneous XHR requests, session data may be lost due to concurrent requests updating the session at the same time. This is a known issue. One solution may be reducing the number of requests, or use *_session locking *_. This allows to lock the session file when used, hence forcing all requests to be executed one after the other. This may slow down your app, but eliminate the potential errors.

Laravel does not include session locking by default. Someone made a Laravel package to add the feature. https://github.com/rairlie/laravel-locking-session

It seems to have worked for me.

Thanks. Works for me.

I think this problem can be reduced if one would not write to the session storage that often.

I am using the database driver and every request updates the database. Although the session did not change.

For example, if I have an ajax request that takes 1 minute. And I login while the request is running. When the ajax is finished, it will save the session I had before the login.

One could fix that for the database session driver changing the following method:
https://github.com/laravel/framework/blob/1bbe5528568555d597582fdbec73e31f8a818dbc/src/Illuminate/Session/DatabaseSessionHandler.php#L163-L166

to:

    protected function performUpdate($sessionId, $payload)
    {
        if ($payload['payload'] === $oldPayload['payload']) {
             unset($payload['payload']);
        }
        return $this->getQuery()->where('id', $sessionId)->update($payload);
    }

I did a gist with an alternative session driver that does less writing to the db. (The other stuff in it is to send less cookies.)

For anyone following this issue, I've summarize what I believe is the reason session overwrites happen somewhat frequently, and offered some possible solutions: https://github.com/laravel/framework/issues/30996

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  ·  3Comments

lzp819739483 picture lzp819739483  ·  3Comments

RomainSauvaire picture RomainSauvaire  ·  3Comments

fideloper picture fideloper  ·  3Comments

felixsanz picture felixsanz  ·  3Comments