php loses part of session randomly
Question
I am developing for Joomla! 2.5.6 and this code used to work fine with Joomla 1.5
Page 1
JFactory::getSession()->clear('domain_name', 'dominiForm');
Page 2
$session = JFactory::getSession();
$session->set('domain_name', $domain_name, 'dominiForm');
Page 3
$session = JFactory::getSession();
$domain_name = $session->get('domain_name', null, 'dominiForm');
The problem is page 3 sometimes returns null, sometimes it returns the saved value. It works ok on my development machine, but not on production server, I don't know what to do.
Here is some of the phpinfo() on the server
PHP Version 5.3.3-7+squeeze14
session
Session Support enabled
Registered save handlers files user sqlite
Registered serializer handlers php php_binary wddx
Directive Local Value Master Value
session.auto_start Off Off
session.bug_compat_42 Off Off
session.bug_compat_warn Off Off
session.cache_expire 180 180
session.cache_limiter none nocache
session.cookie_domain no value no value
session.cookie_httponly Off Off
session.cookie_lifetime 0 0
session.cookie_path / /
session.cookie_secure Off Off
session.entropy_file no value no value
session.entropy_length 0 0
session.gc_divisor 1000 1000
session.gc_maxlifetime 2700 1440
session.gc_probability 0 0
session.hash_bits_per_character 5 5
session.hash_function 0 0
session.name a6252c638b628a21b4b4b1cf3338a103 PHPSESSID
session.referer_check no value no value
session.save_handler user files
session.save_path /var/lib/php5 /var/lib/php5
session.serialize_handler php php
session.use_cookies On On
session.use_only_cookies On On
session.use_trans_sid 0 0
Solution
You are using a custom session.save_handler on your production server. Probably you don't on your development machine.
Please be aware that Joomla does get the session locking wrong - there is none. Essentially this means that you are the victim of a race condition.
If you take a look at the documentaion for session_set_save_handler(), you will see that there are callbacks for open, close, read, write, destroy and gc (garbage collection).
open
should "initialize" stuff, but most importanty should aquire a write lock to the ressource used for storage.
read
does the usual reading, write
does the writing.
close
should release the write lock.
If a session save handler does not aquire the lock, multiple parallel requests with the same session id can overwrite each other!
There is a simple test you should execute on your server to see if you have this problem:
<?php
// initialize alternate session save handler here.
//include_once "session-handler.php";
if (isset($_GET['subrequest'])) {
$starttime = time();
$subrequest = intval($_GET['subrequest']);
session_start(); // should wait until lock is released
echo "<html><pre>";
echo "Request started on ". date("Y-m-d H:i:s", $starttime)."\n";
echo "Session locked for this request on ". date("Y-m-d H:i:s"). "\n";
echo "Executing subrequest ". $subrequest."\n";
$_SESSION["subrequest"][] = 'collected subrequest #'.$subrequest;
echo "All subrequests collected:\n";
var_dump($_SESSION["subrequest"]);
echo "\nWaiting 1 second\n";
sleep(1);
echo "Releasing session lock on ". date("Y-m-d H:i:s"). "\n";
echo "</pre></html>";
exit();
}
session_start();
$_SESSION['subrequest'] = array('master request');
?>
<html>
<iframe src="?subrequest=1" width="90%" height="100"></iframe>
<hr>
<iframe src="?subrequest=2" width="90%" height="100"></iframe>
<hr>
<iframe src="?subrequest=3" width="90%" height="100"></iframe>
</html>
This PHP file will initialize the session and emit three iframes on screen that almost instantly make three requests to the server again.
If the session is proberly locked, then each of the iframes will be filled serially one after another the following seconds. Additionally, in order of appearance, the control output of $_SESSION['subrequest']
should include ALL subrequests on the one that returned last.
If the session is NOT locked properly, all three iframes will fill almost instantly one second after the main page was loaded, and the will all report only their own subrequest together with the master request in the debug output.
If I use this implementation for filesystem storage I got from the php.net documentation (Example #2) page, proper saving to the session fails!
<?php
class FileSessionHandler
{
private $savePath;
function open($savePath, $sessionName)
{
$this->savePath = $savePath;
if (!is_dir($this->savePath)) {
mkdir($this->savePath, 0777);
}
return true;
}
function close()
{
return true;
}
function read($id)
{
return (string)@file_get_contents("$this->savePath/sess_$id");
}
function write($id, $data)
{
return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true;
}
function destroy($id)
{
$file = "$this->savePath/sess_$id";
if (file_exists($file)) {
unlink($file);
}
return true;
}
function gc($maxlifetime)
{
foreach (glob("$this->savePath/sess_*") as $file) {
if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
unlink($file);
}
}
return true;
}
}
$handler = new FileSessionHandler();
session_set_save_handler(
array($handler, 'open'),
array($handler, 'close'),
array($handler, 'read'),
array($handler, 'write'),
array($handler, 'destroy'),
array($handler, 'gc')
);
// the following prevents unexpected effects when using objects as save handlers
register_shutdown_function('session_write_close');
If I take a look at some Joomla session classes, I'd predict that storing to APC, databases and XCache will fail this test, because they use custom functions without implementing open
or close
properly.
I am not fluent with Joomla, so you will have to implement the Joomla way of using sessions into this test script yourself.
One final note: If locking is not possible on a single dataset in the database (e.g. you are using MyISAM tables), then effectively you cannot use this table for storing session data. Aquiring a lock on the table will stop ALL other USERS sessions.
OTHER TIPS
Do you use a caching mechanism on your production maschine, like varnish? Because caching is always a mess when dealing with sessions and joomla, got a lot of problems related to caching myself when I used caches (that's why i currently don't use one, just APC without user cache).
I think you can use this script In the first or second page use following script
$session =& JFactory::getSession();
$session->set('name', "value");
*name is your session variable and value is your value
then in the 3rd page use this
$session =& JFactory::getSession();
echo $session->get('name');
*name is your session variable