mirror of
https://github.com/moodle/moodle.git
synced 2025-03-14 12:40:01 +01:00
MDL-25500 lock: New locking framework.
This locking system is designed to be used everywhere requiring locking in Moodle. Each use of the locking system can be configured to use a different type of locking (or the same type with a different configuration). The first supported lock types are memcache, memcached, file (flock), db (specific handlers for pg, mysql and mariadb).
This commit is contained in:
parent
9788e26805
commit
9843e5ece5
@ -470,6 +470,33 @@ $CFG->admin = 'admin';
|
||||
// will be sent to supportemail.
|
||||
// $CFG->supportuserid = -20;
|
||||
//
|
||||
// Moodle 2.7 introduces a locking api for critical tasks (e.g. cron).
|
||||
// The default locking system to use is DB locking for MySQL and Postgres, and File
|
||||
// locking for Oracle and SQLServer. If $CFG->preventfilelocking is set, then the default
|
||||
// will always be DB locking. It can be manually set to one of the lock
|
||||
// factory classes listed below, or one of your own custom classes implementing the
|
||||
// \core\lock\lock_factory interface.
|
||||
//
|
||||
// $CFG->lock_factory = "auto";
|
||||
//
|
||||
// The list of available lock factories is:
|
||||
//
|
||||
// "\\core\\lock\\file_lock_factory" - File locking
|
||||
// Uses lock files stored by default in the dataroot. Whether this
|
||||
// works on clusters depends on the file system used for the dataroot.
|
||||
//
|
||||
// "\\core\\lock\\db_row_lock_factory" - DB locking based on table rows.
|
||||
//
|
||||
// "\\core\\lock\\postgres_lock_factory" - DB locking based on postgres advisory locks.
|
||||
//
|
||||
// "\\core\\lock\\mysql_lock_factory" - DB locking based on mysql lock functions.
|
||||
//
|
||||
// Settings used by the lock factories
|
||||
//
|
||||
// Location for lock files used by the File locking factory. This must exist
|
||||
// on a shared file system that supports locking.
|
||||
// $CFG->lock_file_root = $CFG->dataroot . '/lock';
|
||||
//
|
||||
//=========================================================================
|
||||
// 7. SETTINGS FOR DEVELOPMENT SERVERS - not intended for production use!!!
|
||||
//=========================================================================
|
||||
|
254
lib/classes/lock/db_record_lock_factory.php
Normal file
254
lib/classes/lock/db_record_lock_factory.php
Normal file
@ -0,0 +1,254 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This is a db record locking factory.
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\lock;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* This is a db record locking factory.
|
||||
*
|
||||
* This lock factory uses record locks relying on sql of the form "SET XXX where YYY" and checking if the
|
||||
* value was set. It supports timeouts, autorelease and can work on any DB. The downside - is this
|
||||
* will always be slower than some shared memory type locking function.
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class db_record_lock_factory implements lock_factory {
|
||||
|
||||
/** @var moodle_database $db Hold a reference to the global $DB */
|
||||
protected $db;
|
||||
|
||||
/** @var string $type Used to prefix lock keys */
|
||||
protected $type;
|
||||
|
||||
/** @var array $openlocks - List of held locks - used by auto-release */
|
||||
protected $openlocks = array();
|
||||
|
||||
/**
|
||||
* Is available.
|
||||
* @return boolean - True if this lock type is available in this environment.
|
||||
*/
|
||||
public function is_available() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Almighty constructor.
|
||||
* @param string $type - Used to prefix lock keys.
|
||||
*/
|
||||
public function __construct($type) {
|
||||
global $DB;
|
||||
|
||||
$this->type = $type;
|
||||
// Save a reference to the global $DB so it will not be released while we still have open locks.
|
||||
$this->db = $DB;
|
||||
|
||||
\core_shutdown_manager::register_function(array($this, 'auto_release'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about the blocking behaviour of the lock type on this platform.
|
||||
* @return boolean - True
|
||||
*/
|
||||
public function supports_timeout() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will this lock type will be automatically released when a process ends.
|
||||
*
|
||||
* @return boolean - True (shutdown handler)
|
||||
*/
|
||||
public function supports_auto_release() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple locks for the same resource can be held by a single process.
|
||||
* @return boolean - False - not process specific.
|
||||
*/
|
||||
public function supports_recursion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function generates a unique token for the lock to use.
|
||||
* It is important that this token is not soley based on time as this could lead
|
||||
* to duplicates in a clustered environment (especially on VMs due to poor time precision).
|
||||
*/
|
||||
protected function generate_unique_token() {
|
||||
$uuid = '';
|
||||
|
||||
if (function_exists("uuid_create")) {
|
||||
$context = null;
|
||||
uuid_create($context);
|
||||
|
||||
uuid_make($context, UUID_MAKE_V4);
|
||||
uuid_export($context, UUID_FMT_STR, $uuid);
|
||||
} else {
|
||||
// Fallback uuid generation based on:
|
||||
// "http://www.php.net/manual/en/function.uniqid.php#94959".
|
||||
$uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
|
||||
// 32 bits for "time_low".
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
|
||||
// 16 bits for "time_mid".
|
||||
mt_rand(0, 0xffff),
|
||||
|
||||
// 16 bits for "time_hi_and_version",
|
||||
// four most significant bits holds version number 4.
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
|
||||
// 16 bits, 8 bits for "clk_seq_hi_res",
|
||||
// 8 bits for "clk_seq_low",
|
||||
// two most significant bits holds zero and one for variant DCE1.1.
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
|
||||
// 48 bits for "node".
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
|
||||
}
|
||||
return trim($uuid);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create and get a lock
|
||||
* @param string $resource - The identifier for the lock. Should use frankenstyle prefix.
|
||||
* @param int $timeout - The number of seconds to wait for a lock before giving up.
|
||||
* @param int $maxlifetime - Unused by this lock type.
|
||||
* @return boolean - true if a lock was obtained.
|
||||
*/
|
||||
public function get_lock($resource, $timeout, $maxlifetime = 86400) {
|
||||
|
||||
$token = $this->generate_unique_token();
|
||||
$now = time();
|
||||
$giveuptime = $now + $timeout;
|
||||
$expires = $now + $maxlifetime;
|
||||
|
||||
if (!$this->db->record_exists('lock_db', array('resourcekey' => $resource))) {
|
||||
$record = new \stdClass();
|
||||
$record->resourcekey = $resource;
|
||||
$result = $this->db->insert_record('lock_db', $record);
|
||||
}
|
||||
|
||||
$params = array('expires' => $expires,
|
||||
'token' => $token,
|
||||
'resourcekey' => $resource,
|
||||
'now' => $now);
|
||||
$sql = 'UPDATE {lock_db}
|
||||
SET
|
||||
expires = :expires,
|
||||
owner = :token
|
||||
WHERE
|
||||
resourcekey = :resourcekey AND
|
||||
(owner IS NULL OR expires < :now)';
|
||||
|
||||
do {
|
||||
$now = time();
|
||||
$params['now'] = $now;
|
||||
$this->db->execute($sql, $params);
|
||||
|
||||
$countparams = array('owner' => $token, 'resourcekey' => $resource);
|
||||
$result = $this->db->count_records('lock_db', $countparams);
|
||||
$locked = $result === 1;
|
||||
if (!$locked) {
|
||||
usleep(rand(10000, 250000)); // Sleep between 10 and 250 milliseconds.
|
||||
}
|
||||
// Try until the giveup time.
|
||||
} while (!$locked && $now < $giveuptime);
|
||||
|
||||
if ($locked) {
|
||||
$this->openlocks[$token] = 1;
|
||||
return new lock($token, $this);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a lock that was previously obtained with @lock.
|
||||
* @param lock $lock - a lock obtained from this factory.
|
||||
* @return boolean - true if the lock is no longer held (including if it was never held).
|
||||
*/
|
||||
public function release_lock(lock $lock) {
|
||||
$params = array('noexpires' => null,
|
||||
'token' => $lock->get_key(),
|
||||
'noowner' => null);
|
||||
|
||||
$sql = 'UPDATE {lock_db}
|
||||
SET
|
||||
expires = :noexpires,
|
||||
owner = :noowner
|
||||
WHERE
|
||||
owner = :token';
|
||||
$result = $this->db->execute($sql, $params);
|
||||
if ($result) {
|
||||
unset($this->openlocks[$lock->get_key()]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend a lock that was previously obtained with @lock.
|
||||
* @param lock $lock - a lock obtained from this factory.
|
||||
* @param int $maxlifetime - the new lifetime for the lock (in seconds).
|
||||
* @return boolean - true if the lock was extended.
|
||||
*/
|
||||
public function extend_lock(lock $lock, $maxlifetime = 86400) {
|
||||
$now = time();
|
||||
$expires = $now + $maxlifetime;
|
||||
$params = array('expires' => $expires,
|
||||
'token' => $lock->get_key());
|
||||
|
||||
$sql = 'UPDATE {lock_db}
|
||||
SET
|
||||
expires = :expires,
|
||||
WHERE
|
||||
owner = :token';
|
||||
|
||||
$this->db->execute($sql, $params);
|
||||
$countparams = array('owner' => $lock->get_key());
|
||||
$result = $this->count_records('lock_db', $countparams);
|
||||
|
||||
return $result === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto release any open locks on shutdown.
|
||||
* This is required, because we may be using persistent DB connections.
|
||||
*/
|
||||
public function auto_release() {
|
||||
// Called from the shutdown handler. Must release all open locks.
|
||||
foreach ($this->openlocks as $key => $unused) {
|
||||
$lock = new lock($key, $this);
|
||||
$this->release_lock($lock);
|
||||
}
|
||||
}
|
||||
}
|
196
lib/classes/lock/file_lock_factory.php
Normal file
196
lib/classes/lock/file_lock_factory.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Flock based file locking factory.
|
||||
*
|
||||
* The file lock factory returns file locks locked with the flock function. Works OK, except on some
|
||||
* NFS, exotic shared storage and exotic server OSes (like windows). On windows, a second attempt to get a
|
||||
* lock will block indefinitely instead of timing out.
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\lock;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Flock based file locking factory.
|
||||
*
|
||||
* The file lock factory returns file locks locked with the flock function. Works OK, except on some
|
||||
* NFS, exotic shared storage and exotic server OSes (like windows). On windows, a second attempt to get a
|
||||
* lock will block indefinitely instead of timing out.
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class file_lock_factory implements lock_factory {
|
||||
|
||||
/** @var string $type - The type of lock, e.g. cache, cron, session. */
|
||||
protected $type;
|
||||
|
||||
/** @var string $lockdirectory - Full system path to the directory used to store file locks. */
|
||||
protected $lockdirectory;
|
||||
|
||||
/** @var boolean $verbose - If true, debugging info about the owner of the lock will be written to the lock file. */
|
||||
protected $verbose;
|
||||
|
||||
/**
|
||||
* Create this lock factory.
|
||||
*
|
||||
* @param string $type - The type, e.g. cron, cache, session
|
||||
*/
|
||||
public function __construct($type) {
|
||||
global $CFG;
|
||||
|
||||
$this->type = $type;
|
||||
if (!isset($CFG->file_lock_root)) {
|
||||
$this->lockdirectory = $CFG->dataroot . '/lock';
|
||||
} else {
|
||||
$this->lockdirectory = $CFG->file_lock_root;
|
||||
}
|
||||
$this->verbose = false;
|
||||
if ($CFG->debugdeveloper) {
|
||||
$this->verbose = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about the blocking behaviour of the lock type on this platform.
|
||||
* @return boolean - False if attempting to get a lock will block indefinitely.
|
||||
*/
|
||||
public function supports_timeout() {
|
||||
global $CFG;
|
||||
|
||||
return $CFG->ostype !== 'WINDOWS';
|
||||
}
|
||||
|
||||
/**
|
||||
* This lock type will be automatically released when a process ends.
|
||||
* @return boolean - True
|
||||
*/
|
||||
public function supports_auto_release() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is available.
|
||||
* @return boolean - True if this lock type is available in this environment.
|
||||
*/
|
||||
public function is_available() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple locks for the same resource cannot be held from a single process.
|
||||
* @return boolean - False
|
||||
*/
|
||||
public function supports_recursion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get some info that might be useful for debugging.
|
||||
* @return boolean - string
|
||||
*/
|
||||
protected function get_debug_info() {
|
||||
return 'host:' . php_uname('n') . ', pid:' . getmypid() . ', time:' . time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a lock within the specified timeout or return false.
|
||||
* @param string $resource - The identifier for the lock. Should use frankenstyle prefix.
|
||||
* @param int $timeout - The number of seconds to wait for a lock before giving up.
|
||||
* @param int $maxlifetime - Unused by this lock type.
|
||||
* @return boolean - true if a lock was obtained.
|
||||
*/
|
||||
public function get_lock($resource, $timeout, $maxlifetime = 86400) {
|
||||
global $CFG;
|
||||
|
||||
$giveuptime = time() + $timeout;
|
||||
|
||||
$hash = md5($this->type . '_' . $resource);
|
||||
$lockdir = $this->lockdirectory . '/' . substr($hash, 0, 2);
|
||||
|
||||
if (!check_dir_exists($lockdir, true, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lockfilename = $lockdir . '/' . $hash;
|
||||
|
||||
$filehandle = fopen($lockfilename, "wb");
|
||||
|
||||
// Could not open the lock file.
|
||||
if (!$filehandle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
do {
|
||||
// Will block on windows. So sad.
|
||||
$wouldblock = false;
|
||||
$locked = flock($filehandle, LOCK_EX | LOCK_NB, $wouldblock);
|
||||
if (!$locked && $wouldblock) {
|
||||
usleep(rand(10000, 250000)); // Sleep between 10 and 250 milliseconds.
|
||||
}
|
||||
// Try until the giveup time.
|
||||
} while (!$locked && $wouldblock && time() < $giveuptime);
|
||||
|
||||
if (!$locked) {
|
||||
fclose($filehandle);
|
||||
return false;
|
||||
}
|
||||
if ($this->verbose) {
|
||||
fwrite($filehandle, $this->get_debug_info());
|
||||
}
|
||||
return new lock($filehandle, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a lock that was previously obtained with @lock.
|
||||
* @param lock $lock - A lock obtained from this factory.
|
||||
* @return boolean - true if the lock is no longer held (including if it was never held).
|
||||
*/
|
||||
public function release_lock(lock $lock) {
|
||||
$handle = $lock->get_key();
|
||||
|
||||
if (!$handle) {
|
||||
// We didn't have a lock.
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend a lock that was previously obtained with @lock.
|
||||
* @param lock $lock - not used
|
||||
* @param int $maxlifetime - not used
|
||||
* @return boolean - true if the lock was extended.
|
||||
*/
|
||||
public function extend_lock(lock $lock, $maxlifetime = 86400) {
|
||||
// Not supported by this factory.
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
112
lib/classes/lock/lock.php
Normal file
112
lib/classes/lock/lock.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Class representing a lock
|
||||
*
|
||||
* The methods available for a specific lock type are only known by it's factory.
|
||||
*
|
||||
* @package core
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\lock;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class representing a lock
|
||||
*
|
||||
* The methods available for a specific lock type are only known by it's factory.
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class lock {
|
||||
|
||||
/** @var string|int $key A uniq key representing a held lock */
|
||||
protected $key = '';
|
||||
|
||||
/** @var lock_factory $factory The factory that generated this lock */
|
||||
protected $factory;
|
||||
|
||||
/** @var bool $released Has this lock been released? If a lock falls out of scope without being released - show a warning. */
|
||||
protected $released;
|
||||
|
||||
/**
|
||||
* Construct a lock containing the unique key required to release it.
|
||||
* @param string $key - The lock key.
|
||||
* @param lock_factory $factory - The factory that generated this lock.
|
||||
*/
|
||||
public function __construct($key, $factory) {
|
||||
$this->factory = $factory;
|
||||
$this->key = $key;
|
||||
$this->released = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the unique key representing this lock.
|
||||
* @return string|int lock key.
|
||||
*/
|
||||
public function get_key() {
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the lifetime of this lock. Not supported by all factories.
|
||||
* @param int $maxlifetime - the new lifetime for the lock (in seconds).
|
||||
* @return bool
|
||||
*/
|
||||
public function extend($maxlifetime = 86400) {
|
||||
if ($this->factory) {
|
||||
return $this->factory->extend_lock($this, $maxlifetime);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release this lock
|
||||
* @return bool
|
||||
*/
|
||||
public function release() {
|
||||
$this->released = true;
|
||||
if (empty($this->factory)) {
|
||||
return false;
|
||||
}
|
||||
$result = $this->factory->release_lock($this);
|
||||
// Release any held references to the factory.
|
||||
unset($this->factory);
|
||||
$this->factory = null;
|
||||
$this->key = '';
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print debugging if this lock falls out of scope before being released.
|
||||
*/
|
||||
public function __destruct() {
|
||||
if (!$this->released && defined('PHPUNIT_TEST')) {
|
||||
$this->release();
|
||||
throw new \coding_exception('\core\lock\lock(' . $this->key . ') has fallen out of scope ' .
|
||||
'without being released.' . "\n" .
|
||||
'Locks must ALWAYS be released by calling $mylock->release().');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
79
lib/classes/lock/lock_config.php
Normal file
79
lib/classes/lock/lock_config.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Lock configuration class, used to get an instance of the currently configured lock factory.
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\lock;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Lock configuration class, used to get an instance of the currently configured lock factory.
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class lock_config {
|
||||
|
||||
/**
|
||||
* Get an instance of the currently configured locking subclass.
|
||||
*
|
||||
* @param string $type - Unique namespace for the locks generated by this factory. e.g. core_cron
|
||||
* @return \core\lock\lock_factory
|
||||
*/
|
||||
public static function get_lock_factory($type) {
|
||||
global $CFG, $DB;
|
||||
$lockfactory = null;
|
||||
|
||||
if (isset($CFG->lock_factory) && $CFG->lock_factory != 'auto') {
|
||||
if (!class_exists($CFG->lock_factory)) {
|
||||
// In this case I guess it is not safe to continue. Different cluster nodes could end up using different locking
|
||||
// types because of an installation error.
|
||||
throw new \coding_exception('Lock factory set in $CFG does not exist: ' . $CFG->lock_factory);
|
||||
}
|
||||
$lockfactoryclass = $CFG->lock_factory;
|
||||
$lockfactory = new $lockfactoryclass($type);
|
||||
} else {
|
||||
$dbtype = clean_param($DB->get_dbfamily(), PARAM_ALPHA);
|
||||
|
||||
// DB Specific lock factory is prefered - should support auto-release.
|
||||
$lockfactoryclass = "\\core\\lock\\${dbtype}_lock_factory";
|
||||
if (!class_exists($lockfactoryclass)) {
|
||||
if (empty($CFG->preventfilelocking)) {
|
||||
// File locking is second option - if $CFG->preventfilelocking allows it.
|
||||
$lockfactoryclass = '\core\lock\file_lock_factory';
|
||||
} else {
|
||||
// Final fallback - DB row locking. Does not support auto-release - so on failures
|
||||
// we will have to wait for a timeout.
|
||||
$lockfactoryclass = '\core\lock\db_record_lock_factory';
|
||||
}
|
||||
}
|
||||
$lockfactory = new $lockfactoryclass($type);
|
||||
}
|
||||
|
||||
return $lockfactory;
|
||||
}
|
||||
|
||||
}
|
106
lib/classes/lock/lock_factory.php
Normal file
106
lib/classes/lock/lock_factory.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Defines abstract factory class for generating locks.
|
||||
*
|
||||
* @package core
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\lock;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Defines abstract factory class for generating locks.
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
interface lock_factory {
|
||||
|
||||
/**
|
||||
* Define the constructor signature required by the lock_config class.
|
||||
*
|
||||
* @param string $type - The type this lock is used for (e.g. cron, cache)
|
||||
*/
|
||||
public function __construct($type);
|
||||
|
||||
/**
|
||||
* Return information about the blocking behaviour of the locks on this platform.
|
||||
*
|
||||
* @return boolean - False if attempting to get a lock will block indefinitely.
|
||||
*/
|
||||
public function supports_timeout();
|
||||
|
||||
/**
|
||||
* Will this lock be automatically released when the process ends.
|
||||
* This should never be relied upon in code - but is useful in the case of
|
||||
* fatal errors. If a lock type does not support this auto release,
|
||||
* the max lock time parameter must be obeyed to eventually clean up a lock.
|
||||
*
|
||||
* @return boolean - True if this lock type will be automatically released when the current process ends.
|
||||
*/
|
||||
public function supports_auto_release();
|
||||
|
||||
/**
|
||||
* supports_recursion
|
||||
*
|
||||
* @return boolean - True if attempting to get 2 locks on the same resource will "stack"
|
||||
*/
|
||||
public function supports_recursion();
|
||||
|
||||
/**
|
||||
* Is available.
|
||||
*
|
||||
* @return boolean - True if this lock type is available in this environment.
|
||||
*/
|
||||
public function is_available();
|
||||
|
||||
/**
|
||||
* Get a lock within the specified timeout or return false.
|
||||
*
|
||||
* @param string $resource - The identifier for the lock. Should use frankenstyle prefix.
|
||||
* @param int $timeout - The number of seconds to wait for a lock before giving up.
|
||||
* Not all lock types will support this.
|
||||
* @param int $maxlifetime - The number of seconds to wait before reclaiming a stale lock.
|
||||
* Not all lock types will use this - e.g. if they support auto releasing
|
||||
* a lock when a process ends.
|
||||
* @return \core\lock\lock|boolean - An instance of \core\lock\lock if the lock was obtained, or false.
|
||||
*/
|
||||
public function get_lock($resource, $timeout, $maxlifetime = 86400);
|
||||
|
||||
/**
|
||||
* Release a lock that was previously obtained with @lock.
|
||||
*
|
||||
* @param lock $lock - The lock to release.
|
||||
* @return boolean - True if the lock is no longer held (including if it was never held).
|
||||
*/
|
||||
public function release_lock(lock $lock);
|
||||
|
||||
/**
|
||||
* Extend the timeout on a held lock.
|
||||
*
|
||||
* @param lock $lock - lock obtained from this factory
|
||||
* @param int $maxlifetime - new max time to hold the lock
|
||||
* @return boolean - True if the lock was extended.
|
||||
*/
|
||||
public function extend_lock(lock $lock, $maxlifetime = 86400);
|
||||
}
|
157
lib/classes/lock/mysql_lock_factory.php
Normal file
157
lib/classes/lock/mysql_lock_factory.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* MySQL GET_LOCK locking factory.
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\lock;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* MySQL GET_LOCK locking factory.
|
||||
*
|
||||
* Use MySQL GET_LOCK functions to support locking. Supports auto-release and timeouts and should be fairly quick.
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class mysql_lock_factory implements lock_factory {
|
||||
|
||||
/** @var moodle_database $db Hold a reference to the global $DB */
|
||||
protected $db;
|
||||
|
||||
/** @var string $type Used to prefix lock keys */
|
||||
protected $type;
|
||||
|
||||
/** @var array $openlocks - List of held locks - used by auto-release */
|
||||
protected $openlocks = array();
|
||||
|
||||
/**
|
||||
* Is available.
|
||||
* @return boolean - True if this lock type is available in this environment.
|
||||
*/
|
||||
public function is_available() {
|
||||
return $this->db->get_dbfamily() === 'mysql';
|
||||
}
|
||||
|
||||
/**
|
||||
* Almighty constructor.
|
||||
* @param string $type - Used to prefix lock keys.
|
||||
*/
|
||||
public function __construct($type) {
|
||||
global $DB;
|
||||
|
||||
$this->type = $type;
|
||||
// Save a reference to the global $DB so it will not be released while we still have open locks.
|
||||
$this->db = $DB;
|
||||
|
||||
\core_shutdown_manager::register_function(array($this, 'auto_release'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about the blocking behaviour of the lock type on this platform.
|
||||
* @return boolean - Defer to the DB driver.
|
||||
*/
|
||||
public function supports_timeout() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will this lock type will be automatically released when a process ends.
|
||||
*
|
||||
* @return boolean - Defer to the DB driver.
|
||||
*/
|
||||
public function supports_auto_release() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple locks for the same resource can be held by a single process.
|
||||
* @return boolean - Defer to the DB driver.
|
||||
*/
|
||||
public function supports_recursion() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and get a lock
|
||||
* @param string $resource - The identifier for the lock. Should use frankenstyle prefix.
|
||||
* @param int $timeout - The number of seconds to wait for a lock before giving up.
|
||||
* @param int $maxlifetime - Unused by this lock type.
|
||||
* @return boolean - true if a lock was obtained.
|
||||
*/
|
||||
public function get_lock($resource, $timeout, $maxlifetime = 86400) {
|
||||
$params = array(
|
||||
'key' => $resource,
|
||||
'timeout' => $timeout
|
||||
);
|
||||
$result = $this->db->get_record_sql('SELECT GET_LOCK(:key, :timeout) AS locked',
|
||||
$params);
|
||||
$locked = (bool)($result->locked);
|
||||
|
||||
if ($locked) {
|
||||
$this->openlocks[$resource] = 1;
|
||||
return new lock($resource, $this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a lock that was previously obtained with @lock.
|
||||
* @param lock $lock - a lock obtained from this factory.
|
||||
* @return boolean - true if the lock is no longer held (including if it was never held).
|
||||
*/
|
||||
public function release_lock(lock $lock) {
|
||||
$result = $this->db->get_record_sql('SELECT RELEASE_LOCK(:key) AS unlocked', array('key' => $lock->get_key()));
|
||||
$result = (bool)$result->unlocked;
|
||||
if ($result) {
|
||||
unset($this->openlocks[$lock->get_key()]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend a lock that was previously obtained with @lock.
|
||||
* @param lock $lock - a lock obtained from this factory.
|
||||
* @param int $maxlifetime - the new lifetime for the lock (in seconds).
|
||||
* @return boolean - true if the lock was extended.
|
||||
*/
|
||||
public function extend_lock(lock $lock, $maxlifetime = 86400) {
|
||||
// Not supported by this factory.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto release any open locks on shutdown.
|
||||
* This is required, because we may be using persistent DB connections.
|
||||
*/
|
||||
public function auto_release() {
|
||||
// Called from the shutdown handler. Must release all open locks.
|
||||
foreach ($this->openlocks as $key => $unused) {
|
||||
$lock = new lock($key, $this);
|
||||
$this->release_lock($lock);
|
||||
}
|
||||
}
|
||||
}
|
244
lib/classes/lock/postgres_lock_factory.php
Normal file
244
lib/classes/lock/postgres_lock_factory.php
Normal file
@ -0,0 +1,244 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Postgres advisory locking factory.
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\lock;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Postgres advisory locking factory.
|
||||
*
|
||||
* Postgres locking implementation using advisory locks. Some important points. Postgres has
|
||||
* 2 different forms of lock functions, some accepting a single int, and some accepting 2 ints. This implementation
|
||||
* uses the 2 int version so that it uses a separate namespace from the session locking. The second note,
|
||||
* is because postgres uses integer keys for locks, we first need to map strings to a unique integer. This is
|
||||
* done by storing the strings in the lock_db table and using the auto-id returned. There is a static cache for
|
||||
* id's in this function.
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright Damyon Wiese 2013
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class postgres_lock_factory implements lock_factory {
|
||||
|
||||
/** @var int $dblockid - used as a namespace for these types of locks (separate from session locks) */
|
||||
protected $dblockid = -1;
|
||||
|
||||
/** @var array $lockidcache - static cache for string -> int conversions required for pg advisory locks. */
|
||||
protected static $lockidcache = array();
|
||||
|
||||
/** @var moodle_database $db Hold a reference to the global $DB */
|
||||
protected $db;
|
||||
|
||||
/** @var string $type Used to prefix lock keys */
|
||||
protected $type;
|
||||
|
||||
/** @var array $openlocks - List of held locks - used by auto-release */
|
||||
protected $openlocks = array();
|
||||
|
||||
/**
|
||||
* Calculate a unique instance id based on the database name and prefix.
|
||||
* @return int.
|
||||
*/
|
||||
protected function get_unique_db_instance_id() {
|
||||
global $CFG;
|
||||
|
||||
$strkey = $CFG->dbname . ':' . $CFG->prefix;
|
||||
$intkey = crc32($strkey);
|
||||
// Normalize between 64 bit unsigned int and 32 bit signed ints. Php could return either from crc32.
|
||||
if (PHP_INT_SIZE == 8) {
|
||||
if ($intkey > 0x7FFFFFFF) {
|
||||
$intkey -= 0x100000000;
|
||||
}
|
||||
}
|
||||
|
||||
return $intkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Almighty constructor.
|
||||
* @param string $type - Used to prefix lock keys.
|
||||
*/
|
||||
public function __construct($type) {
|
||||
global $DB;
|
||||
|
||||
$this->type = $type;
|
||||
$this->dblockid = $this->get_unique_db_instance_id();
|
||||
// Save a reference to the global $DB so it will not be released while we still have open locks.
|
||||
$this->db = $DB;
|
||||
|
||||
\core_shutdown_manager::register_function(array($this, 'auto_release'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is available.
|
||||
* @return boolean - True if this lock type is available in this environment.
|
||||
*/
|
||||
public function is_available() {
|
||||
return $this->db->get_dbfamily() === 'postgres';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about the blocking behaviour of the lock type on this platform.
|
||||
* @return boolean - Defer to the DB driver.
|
||||
*/
|
||||
public function supports_timeout() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will this lock type will be automatically released when a process ends.
|
||||
*
|
||||
* @return boolean - Via shutdown handler.
|
||||
*/
|
||||
public function supports_auto_release() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple locks for the same resource can be held by a single process.
|
||||
* @return boolean - Defer to the DB driver.
|
||||
*/
|
||||
public function supports_recursion() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function generates the unique index for a specific lock key.
|
||||
* Once an index is assigned to a key, it never changes - so this is
|
||||
* statically cached.
|
||||
*
|
||||
* @param string $key
|
||||
* @return int
|
||||
*/
|
||||
protected function get_index_from_key($key) {
|
||||
global $DB;
|
||||
|
||||
if (isset(self::$lockidcache[$key])) {
|
||||
return self::$lockidcache[$key];
|
||||
}
|
||||
|
||||
$index = 0;
|
||||
$record = $this->db->get_record('lock_db', array('resourcekey' => $key));
|
||||
if ($record) {
|
||||
$index = $record->id;
|
||||
}
|
||||
|
||||
if (!$index) {
|
||||
$record = new \stdClass();
|
||||
$record->resourcekey = $key;
|
||||
try {
|
||||
$index = $this->db->insert_record('lock_db', $record);
|
||||
} catch (dml_exception $de) {
|
||||
// Race condition - never mind - now the value is guaranteed to exist.
|
||||
$record = $this->db->get_record('lock_db', array('resourcekey' => $key));
|
||||
if ($record) {
|
||||
$index = $record->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$index) {
|
||||
throw new moodle_exception('Could not generate unique index for key');
|
||||
}
|
||||
|
||||
self::$lockidcache[$key] = $index;
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and get a lock
|
||||
* @param string $resource - The identifier for the lock. Should use frankenstyle prefix.
|
||||
* @param int $timeout - The number of seconds to wait for a lock before giving up.
|
||||
* @param int $maxlifetime - Unused by this lock type.
|
||||
* @return boolean - true if a lock was obtained.
|
||||
*/
|
||||
public function get_lock($resource, $timeout, $maxlifetime = 86400) {
|
||||
$giveuptime = time() + $timeout;
|
||||
|
||||
$token = $this->get_index_from_key($resource);
|
||||
|
||||
$params = array('locktype' => $this->dblockid,
|
||||
'token' => $token);
|
||||
|
||||
$locked = false;
|
||||
|
||||
do {
|
||||
$result = $this->db->get_record_sql('SELECT pg_try_advisory_lock(:locktype, :token) AS locked', $params);
|
||||
$locked = $result->locked === 't';
|
||||
if (!$locked) {
|
||||
usleep(rand(10000, 250000)); // Sleep between 10 and 250 milliseconds.
|
||||
}
|
||||
// Try until the giveup time.
|
||||
} while (!$locked && time() < $giveuptime);
|
||||
|
||||
if ($locked) {
|
||||
$this->openlocks[$token] = 1;
|
||||
return new lock($token, $this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a lock that was previously obtained with @lock.
|
||||
* @param lock $lock - a lock obtained from this factory.
|
||||
* @return boolean - true if the lock is no longer held (including if it was never held).
|
||||
*/
|
||||
public function release_lock(lock $lock) {
|
||||
$params = array('locktype' => $this->dblockid,
|
||||
'token' => $lock->get_key());
|
||||
$result = $this->db->get_record_sql('SELECT pg_advisory_unlock(:locktype, :token) AS unlocked', $params);
|
||||
$result = $result->unlocked === 't';
|
||||
if ($result) {
|
||||
unset($this->openlocks[$lock->get_key()]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend a lock that was previously obtained with @lock.
|
||||
* @param lock $lock - a lock obtained from this factory.
|
||||
* @param int $maxlifetime - the new lifetime for the lock (in seconds).
|
||||
* @return boolean - true if the lock was extended.
|
||||
*/
|
||||
public function extend_lock(lock $lock, $maxlifetime = 86400) {
|
||||
// Not supported by this factory.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto release any open locks on shutdown.
|
||||
* This is required, because we may be using persistent DB connections.
|
||||
*/
|
||||
public function auto_release() {
|
||||
// Called from the shutdown handler. Must release all open locks.
|
||||
foreach ($this->openlocks as $key => $unused) {
|
||||
$lock = new lock($key, $this);
|
||||
$this->release_lock($lock);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="lib/db" VERSION="20140112" COMMENT="XMLDB file for core Moodle tables"
|
||||
<XMLDB PATH="lib/db" VERSION="20140115" COMMENT="XMLDB file for core Moodle tables"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@ -3066,5 +3066,21 @@
|
||||
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="lock_db" COMMENT="Stores active and inactive lock types for db locking method.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="resourcekey" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="String identifying the resource to be locked. Should use frankenstyle format."/>
|
||||
<FIELD NAME="expires" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Expiry time for an active lock."/>
|
||||
<FIELD NAME="owner" TYPE="char" LENGTH="36" NOTNULL="false" SEQUENCE="false" COMMENT="uuid indicating the owner of the lock."/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="resourcekey_uniq" UNIQUE="true" FIELDS="resourcekey" COMMENT="Unique index for resourcekey"/>
|
||||
<INDEX NAME="expires_idx" UNIQUE="false" FIELDS="expires" COMMENT="Index on expires column"/>
|
||||
<INDEX NAME="owner_idx" UNIQUE="false" FIELDS="owner" COMMENT="Index on owner"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
|
@ -2927,5 +2927,32 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2014011701.00);
|
||||
}
|
||||
|
||||
if ($oldversion < 2014012400.00) {
|
||||
// Define table lock_db to be created.
|
||||
$table = new xmldb_table('lock_db');
|
||||
|
||||
// Adding fields to table lock_db.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('resourcekey', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('expires', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
|
||||
$table->add_field('owner', XMLDB_TYPE_CHAR, '36', null, null, null, null);
|
||||
|
||||
// Adding keys to table lock_db.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
|
||||
// Adding indexes to table lock_db.
|
||||
$table->add_index('resourcekey_uniq', XMLDB_INDEX_UNIQUE, array('resourcekey'));
|
||||
$table->add_index('expires_idx', XMLDB_INDEX_NOTUNIQUE, array('expires'));
|
||||
$table->add_index('owner_idx', XMLDB_INDEX_NOTUNIQUE, array('owner'));
|
||||
|
||||
// Conditionally launch create table for lock_db.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2014012400.00);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1255,7 +1255,7 @@ function disable_output_buffering() {
|
||||
*/
|
||||
function redirect_if_major_upgrade_required() {
|
||||
global $CFG;
|
||||
$lastmajordbchanges = 2013100400.02;
|
||||
$lastmajordbchanges = 2014012400.00;
|
||||
if (empty($CFG->version) or (float)$CFG->version < $lastmajordbchanges or
|
||||
during_initial_install() or !empty($CFG->adminsetuppending)) {
|
||||
try {
|
||||
|
76
lib/tests/lock_config_test.php
Normal file
76
lib/tests/lock_config_test.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* lock unit tests
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright 2013 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for our locking configuration.
|
||||
*
|
||||
* @package core
|
||||
* @category lock
|
||||
* @copyright 2013 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class lock_config_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Tests the static parse charset method
|
||||
* @return void
|
||||
*/
|
||||
public function test_lock_config() {
|
||||
global $CFG;
|
||||
$original = null;
|
||||
if (isset($CFG->lock_factory)) {
|
||||
$original = $CFG->lock_factory;
|
||||
}
|
||||
|
||||
// Test no configuration.
|
||||
unset($CFG->lock_factory);
|
||||
|
||||
$factory = \core\lock\lock_config::get_lock_factory('cache');
|
||||
|
||||
$this->assertNotEmpty($factory, 'Get a default factory with no configuration');
|
||||
|
||||
$CFG->lock_factory = '\core\lock\file_lock_factory';
|
||||
|
||||
$factory = \core\lock\lock_config::get_lock_factory('cache');
|
||||
$this->assertTrue($factory instanceof \core\lock\file_lock_factory,
|
||||
'Get a default factory with a set configuration');
|
||||
|
||||
$CFG->lock_factory = '\core\lock\db_record_lock_factory';
|
||||
|
||||
$factory = \core\lock\lock_config::get_lock_factory('cache');
|
||||
$this->assertTrue($factory instanceof \core\lock\db_record_lock_factory,
|
||||
'Get a default factory with a changed configuration');
|
||||
|
||||
if ($original) {
|
||||
$CFG->lock_factory = $original;
|
||||
} else {
|
||||
unset($CFG->lock_factory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
111
lib/tests/lock_test.php
Normal file
111
lib/tests/lock_test.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* lock unit tests
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2013 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for our locking implementations.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2013 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class lock_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Some lock types will store data in the database.
|
||||
*/
|
||||
protected function setUp() {
|
||||
$this->resetAfterTest(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a suite of tests on a lock factory.
|
||||
* @param \core\lock\lock_factory $lockfactory - A lock factory to test
|
||||
*/
|
||||
protected function run_on_lock_factory(\core\lock\lock_factory $lockfactory) {
|
||||
|
||||
if ($lockfactory->is_available()) {
|
||||
// This should work.
|
||||
$lock1 = $lockfactory->get_lock('abc', 2);
|
||||
$this->assertNotEmpty($lock1, 'Get a lock');
|
||||
|
||||
if ($lockfactory->supports_timeout()) {
|
||||
if ($lockfactory->supports_recursion()) {
|
||||
$lock2 = $lockfactory->get_lock('abc', 2);
|
||||
$this->assertNotEmpty($lock2, 'Get a stacked lock');
|
||||
$this->assertTrue($lock2->release(), 'Release a stacked lock');
|
||||
} else {
|
||||
// This should timeout.
|
||||
$lock2 = $lockfactory->get_lock('abc', 2);
|
||||
$this->assertFalse($lock2, 'Cannot get a stacked lock');
|
||||
}
|
||||
}
|
||||
// Release the lock.
|
||||
$this->assertTrue($lock1->release(), 'Release a lock');
|
||||
// Get it again.
|
||||
$lock3 = $lockfactory->get_lock('abc', 2);
|
||||
|
||||
$this->assertNotEmpty($lock3, 'Get a lock again');
|
||||
// Release the lock again.
|
||||
$this->assertTrue($lock3->release(), 'Release a lock again');
|
||||
// Release the lock again (shouldn't hurt).
|
||||
$this->assertFalse($lock3->release(), 'Release a lock that is not held');
|
||||
if (!$lockfactory->supports_auto_release()) {
|
||||
// Test that a lock can be claimed after the timeout period.
|
||||
$lock4 = $lockfactory->get_lock('abc', 2, 2);
|
||||
$this->assertNotEmpty($lock4, 'Get a lock');
|
||||
sleep(3);
|
||||
|
||||
$lock5 = $lockfactory->get_lock('abc', 2, 2);
|
||||
$this->assertNotEmpty($lock5, 'Get another lock after a timeout');
|
||||
$this->assertTrue($lock5->release(), 'Release the lock');
|
||||
$this->assertTrue($lock4->release(), 'Release the lock');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the testable lock factories.
|
||||
* @return void
|
||||
*/
|
||||
public function test_locks() {
|
||||
// Run the suite on the current configured default (may be non-core).
|
||||
$defaultfactory = \core\lock\lock_config::get_lock_factory('default');
|
||||
$this->run_on_lock_factory($defaultfactory);
|
||||
|
||||
// Manually create the core no-configuration factories.
|
||||
$dblockfactory = new \core\lock\db_record_lock_factory('test');
|
||||
$this->run_on_lock_factory($dblockfactory);
|
||||
|
||||
$filelockfactory = new \core\lock\file_lock_factory('test');
|
||||
$this->run_on_lock_factory($filelockfactory);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ JavaSript:
|
||||
* The findChildNodes global function has been deprecated. Y.all should
|
||||
be used instead.
|
||||
|
||||
* New locking api and admin settings to configure the system locking type.
|
||||
|
||||
=== 2.6 ===
|
||||
|
||||
* Use new methods from core_component class instead of get_core_subsystems(), get_plugin_types(),
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2014012300.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2014012400.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user