I have this problem in my site that haunts me for months. I keep getting around it, but now I seem not to be able to find such a way, so I figured why not tackle the root problem?
My root problem is that sometimes (not always) session data disappears for some of my users. I know most of them for sure have cookies enabled. I thought maybe it's a session expiry thing, but I don't think that's it. I set the expiration for very long, plus the amount of users that their session gets erased is just too damn high...
I'm using drupal 6.
Any suggestions?
Few things, that you might want to check
- How are your session garbage collector configured? Please verify PHP settings: session.gc_maxlifetime, session.gc_divisor, session.session.gc_probability. It's only a guess, but maybe GC is deleting your data.
- Does data disappears from sessions table? If you use default session handler for Drupal, data of $_SESSION table is stored at session column of sessions table at serialized form.
- If data perishes from database, and if you are able to repeat problem on your development server, you can also enable queries logging for your database (at mysql server configuration, see http://dev.mysql.com/doc/refman/5.1/en/query-log.html for more details). With devel module enabled, you can decorate (devel module settings) your queries with comment including info about function executing query. This may help you to find problem. DONT TRY ON production environment
- Sometimes problem can be caused by one of enabled modules, which cripples your $_SESSION table. Try to find all $_SESSION occurences at your enabled modules code, especialy at write context. Some regular expression will help at this task.
Add ob_start()
and session_start();
at the top of your script.
I think your problem is with session save path
Please create a folder of name (chmod 777 MUST) in your root directory i.e. where the .php is placed and place this code
if(!is_writable(session_save_path())){
session_save_path(dirname(__FILE__)."/<xyz>");
}
Before starting the session.
And one more thing this happens when your session setting is on using files instead of memory.
I had a similar problem once, the reason being the famous www.
The site was enabled to be accessed through both www.example.com and example.com, which worked fine until the user hit a link which was hardcoded with the oposit domainname. Then he switched domain/subdomain and apache/php created a new session for the user on the "new" domain. It was in particular annoying when people were returning from payment.
Solution in our case was to always force users to use only example.com and either disable or redirect all requests to www.example.com directly to example.com. This way you never get sessions on both.
I've heard about a session_regenerate_id() problem of Drupal with some specific PHP version. Could you try to disable this feature and test it?
I'm guessing a bit here, bit I think this might be your problem/solution:
Short answer: try setting session.cookie_lifetime in sites/default/settings.php to 0.
Long answer: the session depends on the session data on the server and the PHP session cookie on the client. If you set the session cookie lifetime too low (Drupal default is 23 days, so normally not a problem), the session might effectively be 'destroyed' on the client side (by the browser deleting the cookie, because it expired).
I had run into this problem at one time because I changed the Drupal session cookie lifetime from 23 days to something lower that seemed more 'appropriate' in an attempt to limit login duration for security reasons. But lowering the session cookie lifetime is not the solution. The best way to do this is to limit the session duration on the server side. You can do this by setting session.gc_maxlifetime (also in Drupal settings) to something like 7200 (for 2 hours). You then only need to make sure that session garbage collection is run often enough, so that sessions reliably end after 2 hours.
Then, to avoid problems with cookies being destroyed client-side, you can set session.cookie_lifetime to 0. This will make sure that, as long as the user is on the website, he will only be logged out if he stays inactive for longer than session.gc_maxlifetime. And on top of that it will also LOG THE USER OUT when he CLOSES his BROWSER. Which is not done by Drupal by default!!
ESSENTIAL UPDATE: Why is it a good idea to set cookie lifetime to 0? Because otherwise THE SERVER sets the DATE/TIME at which the client needs tot EXPIRE the cookie.
So what, you might say? Well, consider this situation: if the CLIENT HAS IT'S CLOCK SET WRONG (yes, it does happen, even when most computers update their time via NTP, some don't)!! It might expire the cookie much sooner then you think, EVEN UPON ARRIVAL.
This was the exact problem I had to debug once...
So try cookie lifetime 0 at least ONCE if you run into strange session problems :)
I am following up on this post because it appears none of the answers solved the problem. I had a similar problem in D7 (and mod_fcgi) and therefore found this thread, the effect is not restricted to Drupal 6, the temporary storage service in Drupal 8 might alleviate this.
In brief, I would consider a race condition as the root cause. Websites built using Drupal and other frameworks involving Ajax can become complex beasts, with concurrent access to the session variables. I would first look for long-running requests using e.g. firefox-developer edition, a traffic analyzer, etc. Such could be introduced for example in blocks or other structure on the site, any of which might access $_SESSION. Subsequently, turn off as many blocks and menus as possible, until the problem disappears.
In addition, you could add some debug output to includes/session.inc
in functions drupal_session_start()
and drupal_session_commit()
. The output should contain the process ids of server processes. Look for commits that occur long after the main content is displayed.
Hypothesis on what might be going on
A long-running request overwrites session variables with an old state. This is partly due to how session variables are managed in Drupal. By default, session data is stored in the session column of the sessions table as a single serialized string representation of the data. In principle, reading and writing session variables should be an atomic action, but that is not easy to enforce. The code below is from session.inc and handles the writing of the session data. It uses a timestamp to and an is_changed flag to delay writing efficiently. One could also say, that sessions are not thread-safe. Also, it uses SQL MERGE statements, but this is not able to merge serialized variables; in effect, the latest written state is the winning state.
// For performance reasons, do not update the sessions table, unless
// $_SESSION has changed or more than 180 has passed since the last update.
if ($is_changed || !isset($user->timestamp) || REQUEST_TIME - $user->timestamp > variable_get('session_write_interval', 180)) {
// Either ssid or sid or both will be added from $key below.
$fields = array(
'uid' => $user->uid,
'cache' => isset($user->cache) ? $user->cache : 0,
'hostname' => ip_address(),
'session' => $value,
'timestamp' => REQUEST_TIME,
);
...
db_merge('sessions')
->key($key)
->fields($fields)
->execute();
Imagine the following scenario:
Main site content is a module that displays a button and a counter. The counter is stored in $_SESSION['counter'] and incremented by clicking a button. The button has an Ajax callback that does effectively the following
function counter_increment(){
if (!drupal_session_started()) {
// Must initialize sessions for anonymous users.
drupal_session_start();
}
$_SESSION['counter'] = (isset($_SESSION['counter'])) ? $_SESSION['counter']++ : 0;
drupal_session_commit();
...
}
In a block in the site footer, there is a block loading session data that stalls for whatever reason.
function do_nothing(){
if (!drupal_session_started()) {
// Must initialize sessions for anonymous users.
drupal_session_start();
}
$otherdata = $_SESSION['otherdata'];
// do something that takes a long time and eventually is finished or fails, like starting a site search:
wait(200); // might work, request is now older than write delay (default 180)
$_SESSION['otherdata'] = $otherdata; // session is updated now, too, possibly not necessary
return TRUE;
}
So once the content is loaded, the footer block calls do_nothing which than hangs for >180 seconds. In the meantime, the user increments the counter using the callback, but once do_nothing finishes, the whole session content will be replaced with the session content of the do_nothing request, thereby losing $_SESSION['counter'];