Session reliability in PHP: An edge case for experts

At LeaseWeb we develop a lot of web applications in PHP. Every now and then we run into a very uncommon problem. These problems intrigue us, as they represent the level of knowledge we seek to overcome these challenges. Below, one such problem is described. It requires a high level of system engineering skills to solve. I hope this has made you curious enough to read on!

So, if you think you are a PHP expert we have a challenge/puzzle for you. The question is why this code:

<?php
$file = '/tmp/session_issue.log';
$url = 'http://session_issue.dev';
session_start();
$session_id = session_id();
if (!isset($_GET['counter']))
{ session_regenerate_id();
  //uncomment next 2 lines to get reliable behavior
  //session_write_close();
  //session_start();
  $session_id = session_id();
}
$counter = isset($_GET['counter'])?$_GET['counter']+0:0;
$ok = $_SESSION['counter']==$counter;
system(sprintf("echo %s %s %s >> %s",$session_id,$counter,$ok?'Y':'N',$file));
$_SESSION['counter'] = ++$counter;
//uncomment next line to get reliable behavior
//session_write_close();
if ($counter>=5) echo '<a href="test.php">test.php</a>';
else header(sprintf('Location: %s/test.php?counter=%s',$url,$counter));
//comment next line to get reliable behavior
flush();
//comment next line to get reliable behavior
usleep(100000);

When you run on PHP 5.3.10 on Ubuntu 12.04 with Apache 2.2.22 in the latest Firefox or Chrome (does not happen when using curl with cookie-jar), gives the unexpected output (in “/tmp/session_issue.log”):

8kms6f035tr287baj3oas9mav1 0 N
8kms6f035tr287baj3oas9mav1 1 N
8kms6f035tr287baj3oas9mav1 2 N
8kms6f035tr287baj3oas9mav1 3 Y
8kms6f035tr287baj3oas9mav1 4 Y
3n2l353k5ibg24pmhl7r3plfv5 0 N
3n2l353k5ibg24pmhl7r3plfv5 1 N
3n2l353k5ibg24pmhl7r3plfv5 2 Y
3n2l353k5ibg24pmhl7r3plfv5 3 Y
3n2l353k5ibg24pmhl7r3plfv5 4 N
j64517pjm7b7jhq4sup1e18me5 0 N
j64517pjm7b7jhq4sup1e18me5 1 N
j64517pjm7b7jhq4sup1e18me5 2 Y
j64517pjm7b7jhq4sup1e18me5 3 N
j64517pjm7b7jhq4sup1e18me5 4 Y
uvtppn3964tima8hnbegkai633 0 N
uvtppn3964tima8hnbegkai633 1 N
uvtppn3964tima8hnbegkai633 2 N
uvtppn3964tima8hnbegkai633 3 Y
uvtppn3964tima8hnbegkai633 4 Y

Note that one would expect only a “N” on the “0” lines and not on any other. This behavior means that some writes are being lost. Any ideas on why? Also why changing the commented lines makes a difference?

Unexpected timing

session_issue

This image shows the timing of the script in the browser. The page loads (and redirects) should take 100ms per load because of the “usleep”, so the timings below 100ms are unexpected.

Theory 1: Caused by concurrency and late session writing

One of the theories could be that, when redirecting, the browser may request the next page (using pipelining) before writing data to disk and thus losing the write.

Some reading on this:

  1. Save session value – then header redirect
  2. “It is a good idea to call session_write_close() before proceeding to a redirection”
  3. PHP Session Locks – How to Prevent Blocking Requests
  4. Concurrent Ajax Requests and PHP
  5. PHP Sessions in Memcache – Locking Issues

AFCAQTWCA

I know that “Any Fool Can Ask Questions The Wise Can’t Answer”, but please share your thoughts on this subject using the comments.

Share

4 thoughts on “Session reliability in PHP: An edge case for experts”

  1. For line 9-10: I guess session_start will set a lock on the session entry (file, memcache entry or …) when the session exists. The first time there is no session so session_write_close (before session_start) is required to create the session entry and make it possible for session_start to set a lock.

  2. @Mark: Yes, well thought, that could possibly be the problem there. Can you provide some proof of that?

  3. Have you tried giving the session a name & then explicitly calling that session name in your code? This solution often helps with session reliability issues, and is also required when running 2 or more sessions concurrently. In other words, it’s always in good practice to give your sessions a name. 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *