title: PHP MySQL session_set_save_handler race condition
body: <p>I have written a simple <a href="http://php.net/manual/en/function.session-set-save-handler.php" rel="nofollow">session_set_save_handler</a> using a MySQL innoDB table as the storage backend. MySQL is 5.5.</p>
  
  <p>Code looks like so (very simplified, but hopefully illustrates the flow of code), I am using <a href="http://www.redbeanphp.com/" rel="nofollow">RedBean ORM</a> for interacting with the database, but the commands should be clear in showing what was being written, read or deleted.:</p>
  
  <pre><code>class MySession{
  
      private $_database;  
  
      public function  __construct($database){
         //database object is injected using dependency injection then assigned to private var
  
         //Hook up handlers
         session_set_save_handler(
          array($this, "open"),
          array($this, "close"),
          array($this, "read"),
          array($this, "write"),
          array($this, "destroy"),
          array($this, "garbageCollection"));
         }
  
         //Register the shutdown function
         register_shutdown_function('session_write_close'); 
  
         //start
         session_start();
  
         //Regenerate session id
         //(NOTE: in production code, the id is regenerated every 10 minutes,
         //but regenerating allows us to trigger the race condition for demonstration.
         session_regenerate_id(TRUE);
  
      public function open($savePath, $sessionName){
          $this-&gt;_database-&gt;exec('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');
          $this-&gt;_database-&gt;begin(); //Begin Transaction
          return true;
      }   
  
      public function close(){
          $this-&gt;_database-&gt;commit(); //Commit the transaction
          return true;
      }
  
      public function read($id){
  
          $this-&gt;_database-&gt;exec('SELECT * FROM session WHERE identifier = ? LOCK IN SHARE MODE', array($id));
          $session = $this-&gt;_database-&gt;findOne('session', 'identifier = ?', array($id));
          return $session-&gt;data;
      }
  
      public function write($id, $sessionData){
  
          $this-&gt;_database-&gt;exec('SELECT * FROM session WHERE identifier = ? FOR UPDATE', array($id));
          $session = $this-&gt;_database-&gt;findOne('session', 'identifier = ?', array($id));
  
          //We need to create a new session
          if ($session == NULL){
              $session = $this-&gt;_database-&gt;dispense('session');
          }
  
          $this-&gt;_database-&gt;store($session);
          return TRUE;
      }
  
      public function destroy($id){
          $this-&gt;_database-&gt;exec('SELECT * FROM session WHERE identifier = ? FOR UPDATE', array($id));
          $session = $this-&gt;_database-&gt;findOne('session', 'identifier = ?', array($id));
  
          if ($session != NULL){
              $this-&gt;_database-&gt;trash($session);
          }
          return TRUE;
      }
  
      public function garbageCollection($maxlifetime){
          $old = ... //Calculate the time for expired sessions
  
          $this-&gt;_database-&gt;exec("DELETE from session WHERE last_activity &lt; ?", array($old));
          return TRUE;
      }
  }
  </code></pre>
  
  <p>The problem is that even with the locking in place, if I send multiple requests to the application (for example, using AJAX), the race condition still occurs and the data stored in the session is lost. I have pinned it down to <code>session_regenerate_id(TRUE)</code>, which deletes the previous session to being the cause of the issue. However, even with the row locking, the issue still occurs.</p>
  
  <p>I have looked at this <a href="http://thwartedefforts.org/2006/11/11/race-conditions-with-ajax-and-php-sessions/" rel="nofollow">page</a>, as well as some <a href="http://race.phux.net/code/session_mysqlsesslock.phps" rel="nofollow">code</a> from that author, but that uses MyISAM tables which implements table locking.</p>
  
  <p>Can anyone shed some light as to why the locking is not making a difference and perhaps offer some solutions to this problem?</p>