Codeigniter 3 upgrade session lock causing issues - php

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.

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!

Related

Is there some kind of performance (or other) punishment for using session_start() but not really using the session super-array?

I've always wondered why PHP makes you manually session_start() in order to gain access to the immensely useful $_SESSION "super-array".
It strikes me that this might be causing a lot of stress on the server, but not really make a difference in practice unless you have an extreme amount of users.
I don't really see why it would cause such a strain, though, if you don't use that array/mechanism. And if you do, you always want session_start() to have been called... It would really be nice to finally get this straightened out.
The manual doesn't offer any explanation: https://www.php.net/session_start
It's not really a question of how much overhead it causes, as far as why sessions aren't started by default. There are tons of possible PHP applications that have nothing to do with session variables (including any CLI use!), and therefore, on principle it shouldn't be automatically started. Establishing an idle database connection ("immensely useful!") also doesn't create silly overhead. It's still not done by default. Resources should be available, but uninstantiated.
The main performance impact caused by starting a session, with or without ever using it, basically involves (quoting from the manual on session_start()):
PHP will call the open and read session save handlers. ... The read callback will retrieve any existing session data (stored in a special serialized format) and will be unserialized and used to automatically populate the $_SESSION superglobal when the read callback returns the saved session data back to PHP session handling.
This typically means disk access to look up and read the serialized session data. Even if it's empty or non-existent. (It will not be created until $_SESSION variables are used; but you won't know if it's there without trying!) Also: your session ID is typically stored in a cookie. Want session? Make a cookie, take a cookie, pass a cookie, read a cookie, etc. pass/read on each page load. Unnecessary baking and trading, and we'd rather avoid redundant HTTP traffic.
Aside that, there's a strange and wonderful thing called session locking that can make you scractczch your head a lot and wonder why you can't load long-running scripts on your site in two tabs simultaneously, even when you've double-damned-configured your Apache, MySQL and the rest to handle concurrent connections and/or space alien armadas on steroids. Without session locking, you could. (Alas, debugging long-running scripts with sessions on!)
Significantly, this will haunt you with concurrent AJAX requests to PHP scripts with sessions; they'll be sequentially processed instead. There are ways to overcome session locking delays, but the default behavior blocks parallel execution, and requests are queued, and it's rather annoying but a necessary evil to prevent race conditions and session corruption (read more).
So much for the obvious performance/quirks side. In terms of customizing how things work, there are functions that may be called before session_start(), such as session_name() (for named sessions). The session_start function itself (as of PHP 7) takes an optional array of session parameters which you couldn't use, were the session started by default.
If you look at the link above, you'll notice that there is in fact a session auto-start option in php.ini session configuration:
session.auto_start boolean
session.auto_start specifies whether the session module starts a session automatically on request startup. Defaults to 0 (disabled).
There are some related cautions in PHP Intro to Sessions on the auto_start option:
Caution If you turn on session.auto_start then the only way to put objects into your sessions is to load its class definition using auto_prepend_file in which you load the class definition else you will have to serialize() your object and unserialize() it afterwards.
If you are certain that you always want to use sessions, the simplest move would be to create a bootup file that you require at the beginning of files that use sessions; add the path to the file into your include path; and then simply <?php require 'sessions.php' before your main code begins.
The session bootup file could also have some of your own session-handling functions, etc. relevant standard material. This route would give you more freedom than the auto-start option, plus a way to implement other options and functionality across all your session-using code. You shouldn't rely on the auto-start in any case, in case you ever want to create code that can be easily deployed into environments with default PHP configuration!

Handling session_regenerate_id() in ajax requests

I want to secure a little bit my application, especially I want to improve the way how sessions are handled. So, at this moment I know several facts:
session_regenerate_id(false) does not destroy old session
session_regenerate_id(true) destroys old session. With ordinary page reloads there is nothing wrong with using session_regenerate_id(true).
However when making dozens concurrent AJAX requests there may be a
problem which results in an error message object destruction failed.
So, there is nothing left to do, then to use
session_regenerate_id(false) in AJAX request.
But what is needed, is to somehow mark previous outdated sessions, which become outdated as a result of invoking session_regenerate_id(false), as "zombie" sessions that will somehow be destroyed and not litter the sessions folder.
I need some practical advice on how to implement this.
All session cleanup, including those with regenerated IDs, is handled by PHP's session garbage collector. There is nothing special needed when calling session_regenerate_id(false) to remove old sessions from storage.
PHP's settings for session.gc_probability, session.gc_divisor, and session.gc_maxlifetime apply.
You can also run your own session storage cleanup based on last access time.

Pages hang when another page from same site is loading

Seeing some strange behavior in my application. Any time I have a long running script waiting for a response in the browser, any other page in the application will spin/hang after a click (even for very simple//static pages) until the original page finishes loading. This is a PHP5.3 based application using native PHP sessions on Apache 2.2.x.
This happens in multiple browsers in all our dev, qa, and production instances. I'm not sure where to start looking. Any advice?
Get all the data you need from the $_SESSION, and call session_write_close() before you are going to do something that takes a long time. Due to a PHP script still being active with that session, it locks the session (after all, it may write some data to that session that the next request needs).
If you need to write something to the session after you've done your long job, you can just call session_start() once again (provided you've not generated output yet), write to it, and after that the script may end, or maybe you repeat this cycle a few times.
Also, don't call session_start() if you don't need it, I see you mention 'static' pages. If a page does not need session data, avoid the overhead & locks it creates.

PHP Session not written after Output(echo or print_r on external ajax Call)

I´m having some serious trouble debugging this particular problem and I hope someone has a clue what i´m doing wrong.
I have a Custom CMS system working that uses Paragraphs as building blocks that get updated using Ajax(prototypejs) calls and functions that parse the HTML chunks in given order, clean them up and save this data in associative arrays in a Session variable.
Users log in, Session is created and I can check for this session without problem in every page I need it. The system works directly on the definitive websites, so the user can see his updates on realtime and browse the site as a normal user would do, but editing.
So, nothing new here. But here is the weird thing.
Enduser site on edit mode(admin user logged in): path "/"
After the logged status is verified, a function processes the editable content and saves an associative array to session, it also starts some javascript objects for editing every paragraph. Data is actually saved, I can use an external script to check if it´s there after this php script ends.If I load a new page(new content), Session gets updated with new data)
Admin User modifies a paragraph using an Inplaceeditor and this HTML chunk is send via Ajax to a php script that starts the named session, reads the present session data, checks if a paragraph should be modified, appended or deleted and reassigns values to existing array keys in $_SESSION. if i make a var_dump() o print_r to $_SESSION after assigning new data is there.After that the script echoes the processed html, and ajax updates the original paragraph on the calling page.
This script is in /admin/cms/...etc, that means at least 4 directories inside the root of the site.
When the script ends, I check using the same session dump script to see if data was really written/commited, but no, $_SESSION has only the original data from the calling page.
Same ID, same session name, same session_start() but no data gets written.
This whole operation is very quick, so I though it could be a speed problem, scripts ends before session_write_close can make his work.
But if I add a new key to $_SESSION array and put some data there, data gets updated and written. If i don´t output anything on this script and just process data and set session variables it also get´s updated and written.
It´s like some members of $_SESSION array are getting blocked to update.
What i did to track this error and what i´m sure i´m not doing wrong.
1.- register_globals are off of course
2.- session_name() and session_start() are always present and in the given
order. I used to have multiple
session_start() -close on a same page
to use several named sessions, but to
refine the problem this is not longer
so.
3.- I use session_write_close() after session data is processed. Also
tried without, letting php decide
when to commit data, but no luck.
4.- I`m using only cookies for SID.
5.- sessions are stored on /tmp, i can see the data getting updated.
I also tried using a custom save
handler on DB, but same problem,
"_write" got only called when no output as present.
I searched php.net, stackoverflow, google, etc for this subject. I never ask without investigation, this is my first time in many years...but it´s just so unlogical it must be something tiny a haven´t thought of.
The most weird thing is that when I just process data without output $_SESSION gets updated ok. But if i modify this script afterwards by adding the output and try again, instead of just having the new(last) value present I get the original value back, the one created by the calling page at first place, sometimes after playing around a few times! PHP can´t cache values between scripts or?I dont have globlals on.
I´m really clueless. This system worked flawless on PHP4.3, since i´m using 5.3.3 for two moths my users where caliming data where getting mixed up, so i checked and yes, there are serious problems. Today I updated to (5.3.6) and I can´t get this session values commited.
Script code called via Ajax:
<?
session_cache_limiter('nocache');
session_name("CMS_ses");
session_start();
include('../htmLawed/htmLawed.php');
include("utils_cms.php");
include("../../../php/utils_array.php");
$value=$_POST['value'];
$editorId=$_POST['editorId'];
$clase=$_POST['clase'];
$editorId=str_replace("pre","",$editorId);
$value=html_entity_decode(stripslashes($value),ENT_QUOTES);
if (strlen(trim($value))==0)
{
die();
}
$value="<div id=\"$editorId\" class=\"$clase\">$value</div>";
$newXHTML=$value;
$retorno=CMS_nuevoBloque($newXHTML,$editorId);
$_SESSION['data']['CMSeditores']=$retorno[1];
$_SESSION['data']['CMScont']=$retorno[2];
session_write_close();
print_r($retorno[0]); //Offending part...without everything works
?>
really nothing strange here....main page code is even simpler, no strange php directives, etc.
Here is the header of the caller page
include 'php/db.php';
$len=$_GET['len'];
$sec=$_GET['sec'];
$cont=$_GET['cont'];
$admfin=$_GET['admfin'];
$fecha=$_GET['fecha'];
$token=$_GET['token'];
$cur=$_GET['cur'];
$PHP_SELF=$_SERVER['PHP_SELF'];
session_cache_limiter('nocache');
session_name("CMS_ses");
session_start();
$passvar='';
unset($adm);
if ((!empty($_SESSION['cms_logged'])) and (!isset($admfin)) )
{
$nivelpermisos=$_SESSION['cms_logged_group'];
$useractual=$_SESSION['cms_logged'];
$adm=1;
}
elseif (empty($_SESSION['cms_logged']))
{
unset($useractual);
}
//.........rest of the code
UPDATE: I did late night tests and found someting i don´t understand.HElP please:
It has not only to do with Sessions but also with Mysql Querys. Same code, but instead of trying to write to $_SESSION array i made a simple update to a Innodb table using the session_id. When i Output some code, the update does get executed,(i can output the query string and no mysql_error() or notice) problems, but checking the database the row doesn´t get updated. Letting the output out if the script and Query does get commited. Only common thing is sessions are started and output is made.
I restarted Apache, etc(who knows) but no luck. Then i made something really stupid, because this is a server side thing. I changed my browser to Firefox(using safari) and everything works! Ok, recheck, back to safari, nothing works. Both running side by side, same issue. PHP is server side, how can different browsers handle code different, can a browser say to apache rollback, request not handled or call the same script twice without notice(checked safaris developer console and the script is called only once) ? Can safari resubmit data silently because it "thinks" ajax failed? I checked headers using firebug and Safaris developer tools , nothing strange but whenever i make a Ajax call with safari, the caller page reloads data(Aka conection to server...).
I really don´t understand nothing.
I had a similar problem to this (if I have understood correctly). I needed to force session data to be written (for a custom session driver) after scripts have finished running. A shutdown function can be registered which should run after scripts have finished.
Maybe this will solve (or help you to solve) your problem.
http://php.net/manual/en/function.register-shutdown-function.php
Thank's for your help. I was doing everything in the right order and still session data was not being written. Session names where necesary because sometimes we test many sites on the same domain using the same custom CMS. So, finally, after making lots of test and no luck, i found that register globals was active on this server(we never use it, code was written having this option off in mind of course), but it messes with sessions!. Switching this off made a huge change. No more problemas. I also made a custom session handler in DB, so i could track the problems in an more centralized way.
Conclussion: Never use register globals + named sessions, an complex data in sessions.
Anyway, i will give this issue more time and more tests. Ajax calls are also sometimes too fast, i had to put a sleep command so writing the session data was really done.Thanks
I am not sure but few suggestion i think may be helpful.
delete session cookies before refreshing the page for testing purposes :)
Ensure that you're not assigning any arrays with a key containing the pipe character (|). This will prevent the session data from being serialized and saved.
Do session_regenerate_id(true); many cases session_write_close doesn't seem to matter with out session_regenerate_id. or just do session_start() after session_write_close() if you are relying on SID ; and in your case i think this is what is causing problem to you as you are ending the current session every time and not re starting it for the next page. hope u get my point. Further more To Make sure data is actually flushed out to the browser use ob_end_flush();
i could not understand the connection between
$_SESSION['data']['CMSeditores']=$retorno[1];
$_SESSION['data']['CMScont']=$retorno[2];
and
$nivelpermisos=$_SESSION['cms_logged_group'];
$useractual=$_SESSION['cms_logged'];
i think you need to paste some more code where the data part is causing problem instead of admin login part.
i hope this helps you.:)
Is there any reason you're establishing the session name twice? I've had issues in the past where I would establish the session without a name, then another piece of script (not mine) was naming the session. Even at the end of the script I was able to print out the session variable, but once I went to a new page my session had been forgotten. It wasn't until I copied the name included in the 2nd script into my session call that it was solved.
Check that there's no other session names being used; also, maybe try only naming the session once, at the first call to the session?
Question: Are you calling session_start() first thing... before ANY output to the browser and before any variables are assigned?
Sounds silly but give it a try.
Also, why are you using session names? Really not necessary unless you have a lot of session variables with the same name serving different purposes and if thats the case then you need to fix that first!
I had a similar problem but it was having with ie few years back. IE manipulates the header on its own way and that causes strange php bugs that you can find in php.net archives.
#Diego Pino Navarro, please see this help page and find Safari and it's issues with php.
I also found "Safari "forget" http-authentication's logon-information".

Session_set_save_handler close/gc

I need to perform an action after a session times out. However I have no clue how to trigger that action without an incoming request. An event handler/a listener/a timer would suit perfect but since PHP does not support such a thing it is really difficult to accomplish what I want.
After lot of searching I bumped into session_set_save_handler but I still cannot fully understand how this method works. If I write code that triggers the action inside the close method when it will be executed?
I also need to keep the current session solution as it is and I wonder what the ramifications of using session_set_save_handler in conjunction with that will be? (My current solution sets a session cookie, assigns a name to the session and starts the session, when the user logs out or a request is done after the session timeout (that value is set in a configuration file) the session is completely destroyed)
Regards!
This is somewhat difficult to do in php, AFAIK.
But you can try by making a passive session handler:
Store session ids associated with a timestamp.
Each time the user associated with certain id makes a request, refresh it's timestamp.
You can detect defunct sessions by comparing the system's current, and each session id's timestamps. The ones that differ above a given treshold (say 30min), are assumed to have passed away. Then you can execute your own save handlers for these session ids.
This won't work if the session end handler needs to be executed inmediately, as this process is executed each time when a request arrives (from any user), so it will depend directly on the website's traffic flow.
But you can also solve it by setting cron jobs each 15min or so. Depending on how expensive your save handlers are, seems an acceptable periodicity.
PHP needs to run in some way for PHP code to execute. Either through a user request or a cronjob.
A Session is saved to the locale storage when a PHP request finishes executing or when session_write_close() is called
This session_set_save_handler() allows you to write your own save handler.

Categories