Search code examples
phpajaxcodeignitersession

Codeigniter 3 upgrade session lock causing issues


We've recently upgraded an old Codeigniter app from 2.1.0 to 3.1.9, and everything has gone smoothly. Except, that the new session locking is causing issues and I'm wondering the proper way to fix it.

The app uses AJAX heavily, however most of the AJAX calls don't write to the session and don't seem to break it.

Here is an example of the issue: there is a GUI with checkboxes, and when the input is changed (a checkbox is checked or unchecked) an AJAX call was made. On the other end of that AJAX call which boxes were checked were written to session so that they would be remembered from visit to visit. However, if you checked/unchecked multiple boxes causing multiple AJAX calls to go out, you would end up getting logged out. Similar behavior has been discovered around the app, all where session writes are happening.

I've tried implementing session_write_close() as suggested by the Codeigniter documentation but that only half worked in some spots, and caused more issues in area where there were no issues before. The app has a few endpoints that do all the work and all work flows share, so fixing the endpoint where the session writes are happening with session_write_close() breaks other script calls when they continue to need the session.

The short term solution I've come up with is to debounce the AJAX calls (which helps but doesn't solve the problem by itself) and to disable inputs until the AJAX call has finished.

Is there a better long term solution? Ultimately this app is being phased out, so spending a long time rewriting it isn't feasible.


Solution

  • The only long-term solution is to properly use session_write_close().

    As you undoubtedly understand, session data is locked so only one script at any time can write to the session's persistent datastore. Session locking prevents hard to troubleshoot concurrency bugs and is more secure.

    Without seeing your implementation it's really hard, er... impossible to offer any precise advice. Here are some things to consider that might help sort out the mess.

    Either do ALL or NONE of the session writes in the AJAX response functions. (By "AJAX response function" I mean the PHP controller/method value of the AJAX url.)

    With the ALL approach call session_write_close() in the "main" script before making any AJAX requests. Keep in mind that $_SESSION is not affected by session_write_close(). All $_SESSION items in the main script will remain accessible so you can reliably read the values. However, changes made to $_SESSION will not be written because, as far as PHP is concerned, the session is closed. But that's only true for the script that calls session_write_close().

    With the NONE approach you may still need to read session data. In that case it would be wise to have the AJAX response functions call session_write_close as soon as possible to minimize the time concurrent requests are blocked. The call is more important for functions that require significant time to execute. If the script execution time is short then the explicit call to session_write_close() is not needed. If at all possible, i.e. no need to read session data, then not loading the session class might result in cleaner code. It would definitely eliminate any chance of concurrent request blocking.

    Don't try to test session behavior by using multiple tabs to the same app on the same browser.

    Consider using $config['sess_time_to_update'] = 0; and then explicitly call $this->sess_regenerate((bool) config_item('sess_regenerate_destroy')); when and where it makes sense that the session id needs to be changed, i.e. right after login; right after a redirect to a "sensitive" page; etc.

    What follows next is offered with a large amount of trepidation. I've tested this using the "files" driver, but not extensively. So, buyer beware.

    I found that it is possible to "re-start" a session by calling the PHP function session_start() after session_write_close() has been used. CodeIgniter will open and read the session datastore and rebuild the $_SESSION superglobal. It's now possible to change session data and it will be written when script execution ends - or with another call to session_write_close().

    This makes sense because session_write_close() does not "do" anything to the CodeIgniter session object. The class is still instantiated and configured. CodeIgniter's custom SessionHandlerInterface is used to open, read, and write session data after session_start() is called.

    Maybe this apparent functionality can be used to solve your problems. In case I wasn't clear earlier - use at your own risk!