Permalink
Browse files

Merge branch 'w16_MDL-30242_m21_session' of git://github.com/skodak/m…

…oodle into MOODLE_21_STABLE
  • Loading branch information...
2 parents 993debd + dfd6a60 commit 721c418985b58b30a6b539a2e50f3c248267a1c2 @stronk7 stronk7 committed Apr 17, 2012
View
@@ -433,6 +433,7 @@
$string['sectionnotexist'] = 'This section does not exist';
$string['sendmessage'] = 'Send message';
$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['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.';
@@ -2204,9 +2204,10 @@ public function session_lock_supported() {
/**
* 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) {
+ public function get_session_lock($rowid, $timeout) {
$this->used_for_db_sessions = true;
}
@@ -1227,18 +1227,46 @@ public function session_lock_supported() {
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()) {
return;
}
- parent::get_session_lock($rowid);
+ parent::get_session_lock($rowid, $timeout);
+
+ $timeoutmilli = $timeout * 1000;
$fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
- $sql = "sp_getapplock '$fullname', 'Exclusive', 'Session', 120000";
+ // 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
+ DECLARE @result INT
+ EXECUTE @result = sp_getapplock @Resource='$fullname',
+ @LockMode='Exclusive',
+ @LockOwner='Session',
+ @LockTimeout='$timeoutmilli'
+ SELECT @result
+ 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);
}
@@ -1203,10 +1203,17 @@ public function session_lock_supported() {
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;
- $sql = "SELECT GET_LOCK('$fullname',120)";
+ $sql = "SELECT GET_LOCK('$fullname', $timeout)";
$this->query_start($sql, null, SQL_QUERY_AUX);
$result = $this->mysqli->query($sql);
$this->query_end($result);
@@ -1218,8 +1225,7 @@ public function get_session_lock($rowid) {
if (reset($arr) == 1) {
return;
} else {
- // try again!
- $this->get_session_lock($rowid);
+ throw new dml_sessionwait_exception();
}
}
}
@@ -1607,19 +1607,28 @@ public function session_lock_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()) {
return;
}
- parent::get_session_lock($rowid);
+ 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' => 120);
+ $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);
}
@@ -1153,17 +1153,54 @@ public function session_lock_supported() {
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
// 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);
+ 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);
@@ -4147,6 +4147,50 @@ function test_concurent_transactions() {
$DB2->dispose();
}
+ public function test_session_locks() {
+ $DB = $this->tdb;
+ $dbman = $DB->get_manager();
+
+ // Open second connection
+ $cfg = $DB->export_dbconfig();
+ if (!isset($cfg->dboptions)) {
+ $cfg->dboptions = array();
+ }
+ $DB2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
+ $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
+
+ // Testing that acquiring a lock efectively locks
+ // Get a session lock on connection1
+ $rowid = rand(100, 200);
+ $timeout = 1;
+ $DB->get_session_lock($rowid, $timeout);
+
+ // Try to get the same session lock on connection2
+ try {
+ $DB2->get_session_lock($rowid, $timeout);
+ $DB2->release_session_lock($rowid); // Should not be excuted, but here for safety
+ $this->fail('An Exception is missing, expected due to session lock acquired.');
+ } catch (exception $e) {
+ $this->assertTrue($e instanceof dml_sessionwait_exception);
+ $DB->release_session_lock($rowid); // Release lock on connection1
+ }
+
+ // Testing that releasing a lock efectively frees
+ // Get a session lock on connection1
+ $rowid = rand(100, 200);
+ $timeout = 1;
+ $DB->get_session_lock($rowid, $timeout);
+ // Release the lock on connection1
+ $DB->release_session_lock($rowid);
+
+ // Get the just released lock on connection2
+ $DB2->get_session_lock($rowid, $timeout);
+ // Release the lock on connection2
+ $DB2->release_session_lock($rowid);
+
+ $DB2->dispose();
+ }
+
public function test_bound_param_types() {
$DB = $this->tdb;
$dbman = $DB->get_manager();
@@ -1286,17 +1286,44 @@ public function session_lock_supported() {
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()) {
return;
}
- parent::get_session_lock($rowid);
+ parent::get_session_lock($rowid, $timeout);
+
+ $timeoutmilli = $timeout * 1000;
$fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
- $sql = "sp_getapplock '$fullname', 'Exclusive', 'Session', 120000";
+ // 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
+ DECLARE @result INT
+ EXECUTE @result = sp_getapplock @Resource='$fullname',
+ @LockMode='Exclusive',
+ @LockOwner='Session',
+ @LockTimeout='$timeoutmilli'
+ SELECT @result
+ 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);
}
View
@@ -79,6 +79,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.
*/
class dml_read_exception extends dml_exception {
Oops, something went wrong.

0 comments on commit 721c418

Please sign in to comment.