/** * Read session handler. * * {@see http://php.net/manual/en/function.session-set-save-handler.php} * * @param string $sid * @return string */ public function handler_read($sid) { try { if (!($record = $this->database->get_record('sessions', array('sid' => $sid), 'id'))) { // Let's cheat and skip locking if this is the first access, // do not create the record here, let the manager do it after session init. $this->failed = false; $this->recordid = null; $this->lasthash = sha1(''); return ''; } if ($this->recordid and $this->recordid != $record->id) { error_log('Second session read with different record id detected, cannot read session'); $this->failed = true; $this->recordid = null; return ''; } if (!$this->recordid) { // Lock session if exists and not already locked. $this->database->get_session_lock($record->id, $this->acquiretimeout); $this->recordid = $record->id; } } catch (\dml_sessionwait_exception $ex) { // This is a fatal error, better inform users. // It should not happen very often - all pages that need long time to execute // should close session immediately after access control checks. error_log('Cannot obtain session lock for sid: ' . $sid); $this->failed = true; throw $ex; } catch (\Exception $ex) { // Do not rethrow exceptions here, this should not happen. error_log('Unknown exception when starting database session : ' . $sid . ' - ' . $ex->getMessage()); $this->failed = true; $this->recordid = null; return ''; } // Finally read the full session data because we know we have the lock now. if (!($record = $this->database->get_record('sessions', array('id' => $record->id), 'id, sessdata'))) { // Ignore - something else just deleted the session record. $this->failed = true; $this->recordid = null; return ''; } $this->failed = false; if (is_null($record->sessdata)) { $data = ''; $this->lasthash = sha1(''); } else { $data = base64_decode($record->sessdata); $this->lasthash = sha1($record->sessdata); } return $data; }
/** * Obtain session lock * @param int $rowid id of the row with session record * @param int $timeout max allowed time to wait for the lock in seconds * @return void */ public function get_session_lock($rowid, $timeout) { parent::get_session_lock($rowid, $timeout); $fullname = $this->dbname . '-' . $this->prefix . '-session-' . $rowid; $sql = "SELECT GET_LOCK('{$fullname}', {$timeout})"; $this->query_start($sql, null, SQL_QUERY_AUX); $result = $this->mysqli->query($sql); $this->query_end($result); if ($result) { $arr = $result->fetch_assoc(); $result->close(); if (reset($arr) == 1) { return; } else { throw new dml_sessionwait_exception(); } } }
/** * Obtain session lock * @param int $rowid id of the row with session record * @param int $timeout max allowed time to wait for the lock in seconds * @return bool success */ public function get_session_lock($rowid, $timeout) { if (!$this->session_lock_supported()) { return; } parent::get_session_lock($rowid, $timeout); $timeoutmilli = $timeout * 1000; $fullname = $this->dbname . '-' . $this->prefix . '-session-' . $rowid; // There is one bug in PHP/freetds (both reproducible with mssql_query() // and its mssql_init()/mssql_bind()/mssql_execute() alternative) for // stored procedures, causing scalar results of the execution // to be cast to boolean (true/fals). Here there is one // workaround that forces the return of one recordset resource. // $sql = "sp_getapplock '$fullname', 'Exclusive', 'Session', $timeoutmilli"; $sql = "BEGIN\n DECLARE @result INT\n EXECUTE @result = sp_getapplock @Resource='{$fullname}',\n @LockMode='Exclusive',\n @LockOwner='Session',\n @LockTimeout='{$timeoutmilli}'\n SELECT @result\n END"; $this->query_start($sql, null, SQL_QUERY_AUX); $result = mssql_query($sql, $this->mssql); $this->query_end($result); if ($result) { $row = mssql_fetch_row($result); if ($row[0] < 0) { throw new dml_sessionwait_exception(); } } $this->free_result($result); }
/** * Write session handler. * * {@see http://php.net/manual/en/function.session-set-save-handler.php} * * NOTE: Do not write to output or throw any exceptions! * Hopefully the next page is going to display nice error or it recovers... * * @param string $sid * @param string $session_data * @return bool success */ public function handler_write($sid, $session_data) { global $USER; // TODO: MDL-20625 we need to rollback all active transactions and log error if any open needed if ($this->failed) { // do not write anything back - we failed to start the session properly return false; } $userid = 0; if (!empty($USER->realuser)) { $userid = $USER->realuser; } else { if (!empty($USER->id)) { $userid = $USER->id; } } if (isset($this->record->id)) { $data = base64_encode($session_data); // There might be some binary mess :-( // Skip db update if nothing changed, // do not update the timemodified each second. $hash = sha1($data); if ($this->lasthash === $hash and $this->record->userid == $userid and time() - $this->record->timemodified < 20 and $this->record->lastip == getremoteaddr()) { // No need to update anything! return true; } $this->record->sessdata = $data; $this->record->userid = $userid; $this->record->timemodified = time(); $this->record->lastip = getremoteaddr(); try { $this->database->update_record_raw('sessions', $this->record); $this->lasthash = $hash; } catch (dml_exception $ex) { if ($this->database->get_dbfamily() === 'mysql') { try { $this->database->set_field('sessions', 'state', 9, array('id' => $this->record->id)); } catch (Exception $ignored) { } error_log('Can not write database session - please verify max_allowed_packet is at least 4M!'); } else { error_log('Can not write database session'); } return false; } catch (Exception $ex) { error_log('Can not write database session'); return false; } } else { // fresh new session try { $record = new stdClass(); $record->state = 0; $record->sid = $sid; $record->sessdata = base64_encode($session_data); // there might be some binary mess :-( $record->userid = $userid; $record->timecreated = $record->timemodified = time(); $record->firstip = $record->lastip = getremoteaddr(); $record->id = $this->database->insert_record_raw('sessions', $record); $this->record = $this->database->get_record('sessions', array('id' => $record->id)); $this->lasthash = sha1($record->sessdata); $this->database->get_session_lock($this->record->id, SESSION_ACQUIRE_LOCK_TIMEOUT); } catch (Exception $ex) { // this should not happen error_log('Can not write new database session or acquire session lock'); $this->failed = true; return false; } } return true; }
public function get_session_lock($rowid) { parent::get_session_lock($rowid); $fullname = $this->dbname . '-' . $this->prefix . '-session-' . $rowid; $sql = "SELECT GET_LOCK('{$fullname}',120)"; $this->query_start($sql, null, SQL_QUERY_AUX); $result = $this->mysqli->query($sql); $this->query_end($result); if ($result) { $arr = $result->fetch_assoc(); $result->close(); if (reset($arr) == 1) { return; } else { // try again! $this->get_session_lock($rowid); } } }
/** * Obtain session lock * @param int $rowid id of the row with session record * @param int $timeout max allowed time to wait for the lock in seconds * @return bool success */ public function get_session_lock($rowid, $timeout) { if (!$this->session_lock_supported()) { return; } parent::get_session_lock($rowid, $timeout); $fullname = $this->dbname . '-' . $this->prefix . '-session-' . $rowid; $sql = 'SELECT MOODLE_LOCKS.GET_LOCK(:lockname, :locktimeout) FROM DUAL'; $params = array('lockname' => $fullname, 'locktimeout' => $timeout); $this->query_start($sql, $params, SQL_QUERY_AUX); $stmt = $this->parse_query($sql); $this->bind_params($stmt, $params); $result = oci_execute($stmt, $this->commit_status); if ($result === false) { // Any failure in get_lock() raises error, causing return of bool false throw new dml_sessionwait_exception(); } $this->query_end($result, $stmt); oci_free_statement($stmt); }
/** * Obtain session lock * @param int $rowid id of the row with session record * @param int $timeout max allowed time to wait for the lock in seconds * @return bool success */ public function get_session_lock($rowid, $timeout) { // NOTE: there is a potential locking problem for database running // multiple instances of moodle, we could try to use pg_advisory_lock(int, int), // luckily there is not a big chance that they would collide if (!$this->session_lock_supported()) { return; } parent::get_session_lock($rowid, $timeout); $timeoutmilli = $timeout * 1000; $sql = "SET statement_timeout TO {$timeoutmilli}"; $this->query_start($sql, null, SQL_QUERY_AUX); $result = pg_query($this->pgsql, $sql); $this->query_end($result); if ($result) { pg_free_result($result); } $sql = "SELECT pg_advisory_lock({$rowid})"; $this->query_start($sql, null, SQL_QUERY_AUX); $start = time(); $result = pg_query($this->pgsql, $sql); $end = time(); try { $this->query_end($result); } catch (dml_exception $ex) { if ($end - $start >= $timeout) { throw new dml_sessionwait_exception(); } else { throw $ex; } } if ($result) { pg_free_result($result); } $sql = "SET statement_timeout TO DEFAULT"; $this->query_start($sql, null, SQL_QUERY_AUX); $result = pg_query($this->pgsql, $sql); $this->query_end($result); if ($result) { pg_free_result($result); } }
/** * Obtain session lock * @param int $rowid id of the row with session record * @param int $timeout max allowed time to wait for the lock in seconds * @return void */ public function get_session_lock($rowid, $timeout) { if (!$this->session_lock_supported()) { return; } parent::get_session_lock($rowid, $timeout); $timeoutmilli = $timeout * 1000; $fullname = $this->dbname . '-' . $this->prefix . '-session-' . $rowid; // While this may work using proper {call sp_...} calls + binding + // executing + consuming recordsets, the solution used for the mssql // driver is working perfectly, so 100% mimic-ing that code. // $sql = "sp_getapplock '$fullname', 'Exclusive', 'Session', $timeoutmilli"; $sql = "BEGIN\n DECLARE @result INT\n EXECUTE @result = sp_getapplock @Resource='{$fullname}',\n @LockMode='Exclusive',\n @LockOwner='Session',\n @LockTimeout='{$timeoutmilli}'\n SELECT @result\n END"; $this->query_start($sql, null, SQL_QUERY_AUX); $result = sqlsrv_query($this->sqlsrv, $sql); $this->query_end($result); if ($result) { $row = sqlsrv_fetch_array($result); if ($row[0] < 0) { throw new dml_sessionwait_exception(); } } $this->free_result($result); }
public function get_session_lock($rowid) { if (!$this->session_lock_supported()) { return; } parent::get_session_lock($rowid); $fullname = $this->dbname . '-' . $this->prefix . '-session-' . $rowid; $sql = 'SELECT MOODLE_LOCKS.GET_LOCK(:lockname, :locktimeout) FROM DUAL'; $params = array('lockname' => $fullname, 'locktimeout' => 120); $this->query_start($sql, $params, SQL_QUERY_AUX); $stmt = $this->parse_query($sql); $this->bind_params($stmt, $params); $result = oci_execute($stmt, $this->commit_status); $this->query_end($result, $stmt); oci_free_statement($stmt); }
public function get_session_lock($rowid) { if (!$this->session_lock_supported()) { return; } parent::get_session_lock($rowid); $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid; $sql = "sp_getapplock '$fullname', 'Exclusive', 'Session', 120000"; $this->query_start($sql, null, SQL_QUERY_AUX); $result = mssql_query($sql, $this->mssql); $this->query_end($result); $this->free_result($result); }
public function get_session_lock($rowid) { // NOTE: there is a potential locking problem for database running // multiple instances of moodle, we could try to use pg_advisory_lock(int, int), // luckily there is not a big chance that they would collide if (!$this->session_lock_supported()) { return; } parent::get_session_lock($rowid); $sql = "SELECT pg_advisory_lock({$rowid})"; $this->query_start($sql, null, SQL_QUERY_AUX); $result = pg_query($this->pgsql, $sql); $this->query_end($result); if ($result) { pg_free_result($result); } }