Skip to content

Commit

Permalink
MDL-30026 improve session lock acquire timeouts and other minor cleanup
Browse files Browse the repository at this point in the history
This is partially based on original patch by Tony Levi.
  • Loading branch information
skodak committed Apr 12, 2012
1 parent 8e35d0a commit a6ecee4
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 61 deletions.
1 change: 1 addition & 0 deletions lang/en/error.php
Expand Up @@ -433,6 +433,7 @@
$string['sectionnotexist'] = 'This section does not exist'; $string['sectionnotexist'] = 'This section does not exist';
$string['sendmessage'] = 'Send message'; $string['sendmessage'] = 'Send message';
$string['servicedonotexist'] = 'The service does not exist'; $string['servicedonotexist'] = 'The service does not exist';
$string['sessionwaiterr'] = 'Timed out while waiting for session lock.<br />Wait for your current requests to finish and try again later.';
$string['sessioncookiesdisable'] = 'Incorrect use of require_key_login() - session cookies must be disabled!'; $string['sessioncookiesdisable'] = 'Incorrect use of require_key_login() - session cookies must be disabled!';
$string['sessiondiskfull'] = 'The session partition is full. It is not possible to login at this time.<br /><br />Please notify server administrator.'; $string['sessiondiskfull'] = 'The session partition is full. It is not possible to login at this time.<br /><br />Please notify server administrator.';
$string['sessionerroruser'] = 'Your session has timed out. Please login again.'; $string['sessionerroruser'] = 'Your session has timed out. Please login again.';
Expand Down
3 changes: 2 additions & 1 deletion lib/dml/moodle_database.php
Expand Up @@ -2204,9 +2204,10 @@ public function session_lock_supported() {
/** /**
* Obtain session lock * Obtain session lock
* @param int $rowid id of the row with session record * @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 * @return bool success
*/ */
public function get_session_lock($rowid) { public function get_session_lock($rowid, $timeout) {
$this->used_for_db_sessions = true; $this->used_for_db_sessions = true;
} }


Expand Down
21 changes: 18 additions & 3 deletions lib/dml/mssql_native_moodle_database.php
Expand Up @@ -1227,18 +1227,33 @@ public function session_lock_supported() {
return true; return true;
} }


public function 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()) { if (!$this->session_lock_supported()) {
return; return;
} }
parent::get_session_lock($rowid); parent::get_session_lock($rowid, $timeout);

$timeoutmilli = $timeout * 1000;


$fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid; $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
$sql = "sp_getapplock '$fullname', 'Exclusive', 'Session', 120000"; $sql = "sp_getapplock '$fullname', 'Exclusive', 'Session', $timeoutmilli";
$this->query_start($sql, null, SQL_QUERY_AUX); $this->query_start($sql, null, SQL_QUERY_AUX);
$result = mssql_query($sql, $this->mssql); $result = mssql_query($sql, $this->mssql);
$this->query_end($result); $this->query_end($result);


if ($result) {
$row = mssql_fetch_row($result);
if ($row[0] < 0) {
throw new dml_sessionwait_exception();
}
}

$this->free_result($result); $this->free_result($result);
} }


Expand Down
16 changes: 11 additions & 5 deletions lib/dml/mysqli_native_moodle_database.php
Expand Up @@ -1203,10 +1203,17 @@ public function session_lock_supported() {
return true; return true;
} }


public function get_session_lock($rowid) { /**
parent::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) {
parent::get_session_lock($rowid, $timeout);

$fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid; $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
$sql = "SELECT GET_LOCK('$fullname',120)"; $sql = "SELECT GET_LOCK('$fullname', $timeout)";
$this->query_start($sql, null, SQL_QUERY_AUX); $this->query_start($sql, null, SQL_QUERY_AUX);
$result = $this->mysqli->query($sql); $result = $this->mysqli->query($sql);
$this->query_end($result); $this->query_end($result);
Expand All @@ -1218,8 +1225,7 @@ public function get_session_lock($rowid) {
if (reset($arr) == 1) { if (reset($arr) == 1) {
return; return;
} else { } else {
// try again! throw new dml_sessionwait_exception();
$this->get_session_lock($rowid);
} }
} }
} }
Expand Down
19 changes: 16 additions & 3 deletions lib/dml/oci_native_moodle_database.php
Expand Up @@ -1607,21 +1607,34 @@ public function session_lock_supported() {
return $this->dblocks_supported; return $this->dblocks_supported;
} }


public function 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()) { if (!$this->session_lock_supported()) {
return; return;
} }
parent::get_session_lock($rowid); parent::get_session_lock($rowid, $timeout);


$fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid; $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
$sql = 'SELECT MOODLE_LOCKS.GET_LOCK(:lockname, :locktimeout) FROM DUAL'; $sql = 'SELECT MOODLE_LOCKS.GET_LOCK(:lockname, :locktimeout) FROM DUAL';
$params = array('lockname' => $fullname , 'locktimeout' => 120); $params = array('lockname' => $fullname , 'locktimeout' => $timeout);
$this->query_start($sql, $params, SQL_QUERY_AUX); $this->query_start($sql, $params, SQL_QUERY_AUX);
$stmt = $this->parse_query($sql); $stmt = $this->parse_query($sql);
$this->bind_params($stmt, $params); $this->bind_params($stmt, $params);
$start = time();
$result = oci_execute($stmt, $this->commit_status); $result = oci_execute($stmt, $this->commit_status);
$end = time();
$this->query_end($result, $stmt); $this->query_end($result, $stmt);
oci_free_statement($stmt); oci_free_statement($stmt);

if ($end - $start >= $timeout) {
//TODO: there has to be a better way to find out if lock obtained
throw new dml_sessionwait_exception();
}
} }


public function release_session_lock($rowid) { public function release_session_lock($rowid) {
Expand Down
41 changes: 39 additions & 2 deletions lib/dml/pgsql_native_moodle_database.php
Expand Up @@ -1153,17 +1153,54 @@ public function session_lock_supported() {
return true; return true;
} }


public function 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) {
// NOTE: there is a potential locking problem for database running // NOTE: there is a potential locking problem for database running
// multiple instances of moodle, we could try to use pg_advisory_lock(int, int), // 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 // luckily there is not a big chance that they would collide
if (!$this->session_lock_supported()) { if (!$this->session_lock_supported()) {
return; return;
} }


parent::get_session_lock($rowid); 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)"; $sql = "SELECT pg_advisory_lock($rowid)";
$this->query_start($sql, null, SQL_QUERY_AUX); $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); $result = pg_query($this->pgsql, $sql);
$this->query_end($result); $this->query_end($result);


Expand Down
22 changes: 19 additions & 3 deletions lib/dml/sqlsrv_native_moodle_database.php
Expand Up @@ -1286,17 +1286,33 @@ public function session_lock_supported() {
return true; return true;
} }


public function 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()) { if (!$this->session_lock_supported()) {
return; return;
} }
parent::get_session_lock($rowid); parent::get_session_lock($rowid, $timeout);

$timeoutmilli = $timeout * 1000;


$fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid; $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
$sql = "sp_getapplock '$fullname', 'Exclusive', 'Session', 120000"; $sql = "sp_getapplock '$fullname', 'Exclusive', 'Session', $timeoutmilli";
$this->query_start($sql, null, SQL_QUERY_AUX); $this->query_start($sql, null, SQL_QUERY_AUX);
$result = sqlsrv_query($this->sqlsrv, $sql); $result = sqlsrv_query($this->sqlsrv, $sql);
$this->query_end($result); $this->query_end($result);

if ($result) {
$row = sqlsrv_fetch_array($result);
if ($row[0] < 0) {
throw new dml_sessionwait_exception();
}
}

$this->free_result($result); $this->free_result($result);
} }


Expand Down
12 changes: 12 additions & 0 deletions lib/dmllib.php
Expand Up @@ -78,6 +78,18 @@ function __construct($error) {
} }
} }


/**
* DML db session wait exception - triggered when session lock request times out.
*/
class dml_sessionwait_exception extends dml_exception {
/**
* Constructor
*/
function __construct() {
parent::__construct('sessionwaiterr');
}
}

/** /**
* DML read exception - triggered by SQL syntax errors, missing tables, etc. * DML read exception - triggered by SQL syntax errors, missing tables, etc.
*/ */
Expand Down

0 comments on commit a6ecee4

Please sign in to comment.