Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node/Express: concurrency issues when using session to store state

So, I've searched quite a bit for this and found several somewhat similar questions, none of them really addressing the problem though so I thought this deserved a question of its own.

I have an express application with a bunch of routes that modify the session to keep state. Thing is, if there are multiple parallel requests, from time to time the session will be overwritten due to race conditions between requests.

So typically

...
app.use(express.static('/public'));
app.use(session(...));
app.route('methodA').get(function(req, res, next) {
    doSomethingSlow().then(function() {
        req.session.a = 'foo';
        res.send(...);
    }
});
app.route('methodB').get(function(req, res, next) {
    doSomethingElseSlow().then(function() {
        req.session.b = 'bar';
        res.send(...);
    }
});

Basically the problem is straightforward and is e.g. described in this answer. Express stores the session in res.end(), but while a methodA request is being handled, a methodB request could have modified the session in the meantime so that when methodA stores the session it will overwrite any changes made by methodB. So even though node is single-threaded and all requests are served by the same thread we can end up with concurrency issues as soon as any method is doing something asynchronous thereby letting other requests be handled simultaneously.

However, I am struggling to decide how I should proceed to solve this problem. All answers I have found only list ways of minimizing the probability of this happening, e.g. by making sure static content does not store the session by registering the serve-static MW before the session MW. But this only helps to some extent; if there are in fact API methods that are supposed to be called in parallel some real concurrency approach to session updates is needed (IMO, when it comes to concurrency issues, every "solution" that strives to minimize the probability of problems occuring rather than addressing the actual problem is bound to go wrong).

Basically these are the alternatives I am exploring so far:

  1. Prevent parallel requests within the same session completely by modifying my client to make sure it calls all API methods serially.

    This may be possible but will have quite some impact on my architecture and may impact performance. It also avoids the problem rather than solves it and if I make something wrong in my client or the API is used by another client I might still run into this randomly, so it doesn't feel very robust.

  2. Make sure each session write is preceded by a session reload and make the entire reload-modify-write operation atomic.

    I am not sure how to achieve this. Even if I modify res.end() to reload the session just before modifying and storing it, because reading and writing the session is async I/O it seems possible that this could happen:

    • request A reloads the session
    • request A modifies session.A = 'foo'
    • request B reloads the session (and will not see session.A)
    • request A stores the session
    • request B modifies session.B = 'bar'
    • request B stores the session, overwriting previous store so that session.A is missing

So in essence I would need to make each reload-modify-store atomic, which basically means blocking the thread? Feels wrong, and I have no idea how to do it.

  1. Stop using the session in this way altogether and pass necessary state as params to each request or by some other means.

    This also avoids the problem rather than addressing it. It would also have huge impact on my project. But sure, it might be the best way.

  2. ???

Anyone got any ideas how to address this in a robust way? Thanks!

like image 742
JHH Avatar asked Feb 19 '15 09:02

JHH


People also ask

Is Express session safe?

If you run with https and your physical computer is secure from outsiders, then your express session cookie is protected from outsiders when stored locally and is protected (by https) when in transport to the server.

How does express session store user data?

We can use the express-session package to keep session cookie data on the server-side. There're many options like the content of various cookie attributes and the time to expiry. Other settings like the ID, whether to save cookie only in HTTPS and so on can be set. The cookies will be stored in a session store.


1 Answers

A possible solution to this is to create a simple middleware for express that will maintain a list of all current requests and queue any requests coming from the same session. It won't call next() until the previous requests for that session-id have been processed first.

Every time a request comes in it can store it in an object with the key being the session-id cookie name and the value an array of current requests for the session-id.

{ 'session-id1': [... queued requests ...], 'session-id2': ... }

When a request is completed it will remove it's self from the array and trigger the next request in the array to be processed.

You can also add a flag to each request in the header to allow for concurrent running for requests that don't need to be queued (they don't write to the session for example) thus improving performance when queueing is not needed.

You should be able to implement this without having to change your application. You could also change the opt-out option in the header to a opt-in option instead meaning it will just process all request concurrently like it would normally unless specified otherwise.

like image 90
Zaptree Avatar answered Oct 24 '22 04:10

Zaptree