2012-04-23 56 views
2

我寫了一個簡單的session_set_save_handler使用MySQL innoDB表作爲存儲後端。 MySQL是5.5。PHP MySQL session_set_save_handler競爭條件

代碼看起來像這樣(非常簡化,但希望能夠說明代碼流),我使用RedBean ORM與數據庫進行交互,但是在顯示正在寫入,讀取或刪除的內容時,命令應該清楚:

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->_database->exec('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE'); 
     $this->_database->begin(); //Begin Transaction 
     return true; 
    } 

    public function close(){ 
     $this->_database->commit(); //Commit the transaction 
     return true; 
    } 

    public function read($id){ 

     $this->_database->exec('SELECT * FROM session WHERE identifier = ? LOCK IN SHARE MODE', array($id)); 
     $session = $this->_database->findOne('session', 'identifier = ?', array($id)); 
     return $session->data; 
    } 

    public function write($id, $sessionData){ 

     $this->_database->exec('SELECT * FROM session WHERE identifier = ? FOR UPDATE', array($id)); 
     $session = $this->_database->findOne('session', 'identifier = ?', array($id)); 

     //We need to create a new session 
     if ($session == NULL){ 
      $session = $this->_database->dispense('session'); 
     } 

     $this->_database->store($session); 
     return TRUE; 
    } 

    public function destroy($id){ 
     $this->_database->exec('SELECT * FROM session WHERE identifier = ? FOR UPDATE', array($id)); 
     $session = $this->_database->findOne('session', 'identifier = ?', array($id)); 

     if ($session != NULL){ 
      $this->_database->trash($session); 
     } 
     return TRUE; 
    } 

    public function garbageCollection($maxlifetime){ 
     $old = ... //Calculate the time for expired sessions 

     $this->_database->exec("DELETE from session WHERE last_activity < ?", array($old)); 
     return TRUE; 
    } 
} 

的問題是,即使在發生鎖定,如果我發送多個請求的應用程序(例如,使用AJAX),比賽條件仍然存在,並存儲在會話中的數據丟失。我已將其固定爲session_regenerate_id(TRUE),這會刪除以前的會話以解決問題的原因。但是,即使使用行鎖定,問題仍然存在。

我看過這個page,以及來自該作者的一些code,但是它使用了實現表鎖定的MyISAM表。

任何人都可以闡明爲什麼鎖定沒有改變,也許提供一些解決方案來解決這個問題?

+0

'session_regenerate_id'總是有問題,並且容易受到客戶端上的競爭條件的影響。你確定它不是固有的cookie更新計時問題,而不是數據庫級別的問題嗎? – deceze 2012-04-23 02:14:33

+0

實際上我不確定,現在你提到它。有沒有辦法使用dbug並檢查cookie更新時機問題? – F21 2012-04-23 02:16:00

+0

這很棘手,很有趣。 ;)使用大量的日誌記錄和網絡流量自省來查看誰在什麼時候發送什麼。 – deceze 2012-04-23 02:18:30

回答

1

我認爲你最好的選擇是隻在你知道請求是一個完整頁面請求而不是AJAX請求時重新生成會話ID。

if (empty($_SERVER['HTTP_X_REQUESTED_WITH'])) { 
    session_regenerate_id(TRUE); 
} 

上面假定您正在使用的JavaScript庫設置標題。