webdeveloper has asked for the wisdom of the Perl Monks concerning the following question:

I'm using CGI::Session (with the 'File' driver) within an AJAX-y, access controlled website. The user's session includes a flag indicating whether they are logged in, and other data relating to the state of the application.

As the user browses the site, it's quite common for any given AJAX request to take a while to complete (let's say, up to 10 seconds), whilst the user views the usual 'spinner' icon. The following scenario is therefore not unlikely:

Each of the requests above update completely different params in the user's session. Let's say the AJAX request calculates a total, and adds that to the session as 'total=1234' when done. The logout request sets the separate param 'logged_in=0'. There is therefore no conflict between the params being set, but the (surprising) problem I am experiencing is as follows:

The session data written by the logout request (setting the "logged_in" flag to false) will get completely overwritten a few seconds later when the previous request finishes running. This means that the user finds themselves viewing the logout page, but is still 'really' logged in.

Looking through the CGI::Session code, it's obvious why this is happening: each session write (via $session->flush) overwrites the session as a whole with whatever data exists within that request's session object, rather than only updating the data changed by that specific request.

Assuming each request is free to update session data at any point, this means that the state of the session following two concurrent requests is likely to be determined by the one that finishes running last - and NOT the one issued last. Any requests issued in the meantime but completing earlier are from a session perspective erased from history.

I'd be interested to know if any Monks have encountered a similar situation, and would welcome any suggestions for how to best address the problem case stated above? Is there a way to persuade CGI::Session to behave differently?

Thanks all!

Update: Removed reference to $session->write.

Replies are listed 'Best First'.
Re: Concurrent requests on the same CGI::Session
by locked_user sundialsvc4 (Abbot) on Jan 17, 2011 at 13:55 UTC

    I would consider submitting a patch.   The scenario that you describe is unfortunately a pretty-common “race condition.”

    Otherwise, build a descendant class... and please then consider contributing it to CPAN, where I am sure it will be very well received.

    Many CGI session handlers are not-very-well architected (IMHO...) for AJAX needs.   (And I do mean that in a very constructive and positive way.)   Even though the design principles needed for suitable session-stores are straightforward enough, I am really not aware of any packages that do AJAX well... for the very reasons you describe.   (And if this thread could now expand to include a list of a few that notably are, that would be a Very Good Thing.™)

      Many CGI session handlers are not-very-well architected (IMHO...) for AJAX needs. (And I do mean that in a very constructive and positive way.)

      For that to be constructive it has to be more specific

        Sure... no problem.

        The problem is twofold:   (a) that Session handlers normally replace the entire set of variables; and (b) that there is no “change counter.”   (Usually, database transactions are used, but that is not sufficient.)

        Therefore, when two or more simultaneous requests start, both of them get the same set of initial-data, and the last one to complete overwrites the entire set of changes previously posted by all of the requests preceding it (in completion-time).   Precisely as the OP observed.

        A more-sophisticated approach is needed, such as... partitioning the data, providing change-counters that are incremented with each update (of a given partition) and so on.   I am very interested to find session-support modules that can do these things automagically.   (Although such a thing can be written... “laziness is a virtue among programmers, you know.”)

      Thanks - it's very useful at least to hear that this is a 'known issue'.

      "Even though the design principles needed for (AJAX) suitable session-stores are straightforward enough..."

      I just wanted to ask if you had any specific references or resources in mind that might list these, as it would be good to be aware of all the potential issues surrounding this topic when considering an alternative way to approach it. Thanks!

Re: Concurrent requests on the same CGI::Session
by Anonyrnous Monk (Hermit) on Jan 17, 2011 at 11:25 UTC

    You'd probably have to modify CGI::Session's write()1 method to first read in any existing session data from the file (if exists), then merge it with the new session data, before writing it out again (this assumes that there are no merge conflicts, i.e. only independent parts of the data have been modifed in the meantime).  There would still be a race condition left, in theory, but that might be tolerable in practice...

    Otherwise, modify CGI::Session to lock the session file upon start of a request, and unlock when the request has finished. This would of course mean that other requests would have to wait for the first one to finish...  Also, locking often causes its very own type of issues, for example, if - for some reason - the unlocking doesn't happen as expected (process dies, or some such), and you have to wait up until some timeout period is over, etc. .

    1 P.S.: What version of CGI::Session do you have?  I just checked the current source, and it doesn't seem to have a write() method, but rather calls $serializer->freeze(...) from within its flush() method (and the default serializer is implemented via Data::Dumper).

      Thanks! I suspected that modifying the module might be suggested as the best approach, but wanted to check that I wasn't missing something that would enable me to achieve a 'merge' via the existing CGI::Session interface.

      I did think about the locking approach, but I'm not sure that this fits with user expectations within the context of a web application (ie. that a logout request should be pretty much instant).

      Thanks again for your feedback - much appreciated. (You are correct about $session->write - original node updated)

      .
Re: Concurrent requests on the same CGI::Session
by wfsp (Abbot) on Jan 17, 2011 at 17:00 UTC
    Would giving the login flag its own cookie help?

      Yes it does - and in fact that's the solution I implemented as a temporary workaround ahead of seeking wisdom.

      Although this solves the specific case above, in the general case it clearly doesn't scale (ie. it's not desirable to have one cookie/session per param). However, it's definitely good to have a working fallback position for my specific problem case!