MDL-25290 cache: Added cache locking plugin and converted locking implementations to that

This commit is contained in:
Sam Hemelryk
2012-10-08 09:53:51 +13:00
parent 6fec18203b
commit 34c84c723a
20 changed files with 413 additions and 255 deletions
+66
View File
@@ -65,6 +65,12 @@ class cache_config {
*/
protected $configdefinitionmappings = array();
/**
* An array of configured cache lock instances.
* @var array
*/
protected $configlocks = array();
/**
* Please use cache_config::instance to get an instance of the cache config that is ready to be used.
*/
@@ -114,8 +120,32 @@ class cache_config {
$this->configstores = array();
$this->configdefinitions = array();
$this->configlocks = array();
$this->configmodemappings = array();
$this->configdefinitionmappings = array();
$this->configlockmappings = array();
// Filter the lock instances
$defaultlock = null;
foreach ($configuration['locks'] as $conf) {
if (!is_array($conf)) {
// Something is very wrong here.
continue;
}
if (!array_key_exists('name', $conf)) {
// Not a valid definition configuration
continue;
}
$name = $conf['name'];
if (array_key_exists($name, $this->configlocks)) {
debugging('Duplicate cache lock detected. This should never happen.', DEBUG_DEVELOPER);
continue;
}
if ($defaultlock === null || !empty($this->configlocks['default'])) {
$defaultlock = $name;
}
$this->configlocks[$name] = $conf;
}
// Filter the stores
$availableplugins = cache_helper::early_get_cache_plugins();
@@ -151,6 +181,10 @@ class cache_config {
}
$store['class'] = $class;
$store['default'] = !empty($store['default']);
if (!array_key_exists('lock', $store) || !array_key_exists($this->configlocks, $store['lock'])) {
$store['lock'] = $defaultlock;
}
$this->configstores[$store['name']] = $store;
}
@@ -256,6 +290,9 @@ class cache_config {
if (!array_key_exists('definitionmappings', $configuration) || !is_array($configuration['definitionmappings'])) {
$configuration['definitionmappings'] = array();
}
if (!array_key_exists('locks', $configuration) || !is_array($configuration['locks'])) {
$configuration['locks'] = array();
}
return $configuration;
}
@@ -394,4 +431,33 @@ class cache_config {
public function get_definition_mappings() {
return $this->configdefinitionmappings;
}
/**
* Returns an array of the configured locks.
* @return array
*/
public function get_locks() {
return $this->configlocks;
}
/**
* Returns the lock store configuration to use with a given store.
* @param string $storename
* @return array
* @throws cache_exception
*/
public function get_lock_for_store($storename) {
if (array_key_exists($storename, $this->configstores)) {
if (array_key_exists($this->configstores[$storename]['lock'], $this->configlocks)) {
$lock = $this->configstores[$storename]['lock'];
return $this->configlocks[$lock];
}
}
foreach ($this->configlocks as $lockconf) {
if (!empty($lockconf['default'])) {
return $lockconf;
}
}
throw new cache_exception('ex_nodefaultlock');
}
}
+8
View File
@@ -266,4 +266,12 @@ class cachestore_dummy implements cache_store {
$cache->initialise($definition);
return $cache;;
}
/**
* Returns the name of this instance.
* @return string
*/
public function my_name() {
return $this->name;
}
}
+33 -1
View File
@@ -76,6 +76,12 @@ class cache_factory {
*/
protected $definitions = array();
/**
* An array of lock plugins.
* @var array
*/
protected $lockplugins = null;
/**
* Returns an instance of the cache_factor method.
*
@@ -106,6 +112,7 @@ class cache_factory {
$factory->stores = array();
$factory->configs = array();
$factory->definitions = array();
$factory->lockplugins = null; // MUST be null in order to force its regeneration.
}
/**
@@ -165,7 +172,7 @@ class cache_factory {
}
/**
* Common protected method to create a cache instance given a definition.
* Common public method to create a cache instance given a definition.
*
* This is used by the static make methods.
*
@@ -304,4 +311,29 @@ class cache_factory {
$store->initialise($definition);
return $store;
}
/**
* Returns a lock instance ready for use.
*
* @param array $config
* @return cache_lock_interface
*/
public function create_lock_instance(array $config) {
if (!array_key_exists('name', $config) || !array_key_exists('type', $config)) {
throw new coding_exception('Invalid cache lock instance provided');
}
$name = $config['name'];
$type = $config['type'];
unset($config['name']);
unset($config['type']);
if ($this->lockplugins === null) {
$this->lockplugins = get_plugin_list_with_class('cachelock', '', 'lib.php');
}
if (!array_key_exists($type, $this->lockplugins)) {
throw new coding_exception('Invalid cache lock type.');
}
$class = $this->lockplugins[$type];
return new $class($name, $config);
}
}
+12 -13
View File
@@ -151,21 +151,16 @@ class cache_helper {
}
/**
* Returns the cache store to be used for locking or false if there is not one.
* @return cache_store|boolean
* Returns a cache_lock instance suitable for use with the store.
*
* @param cache_store $store
* @return cache_lock_interface
*/
public static function get_cachestore_for_locking() {
$factory = cache_factory::instance();
$definition = $factory->create_definition('core', 'locking');
public static function get_cachelock_for_store(cache_store $store) {
$instance = cache_config::instance();
$stores = $instance->get_stores_for_definition($definition);
foreach ($stores as $name => $details) {
if ($details['useforlocking']) {
$instances = self::initialise_cachestore_instances(array($name => $details), $definition);
return reset($instances);
}
}
return false;
$lockconf = $instance->get_lock_for_store($store->my_name());
$factory = cache_factory::instance();
return $factory->create_lock_instance($lockconf);
}
/**
@@ -387,6 +382,10 @@ class cache_helper {
/**
* Purge all of the cache stores of all of their data.
*
* Think twice before calling this method. It will purge **ALL** caches regardless of whether they have been used recently or
* anything. This will involve full setup of the cache + the purge operation. On a site using caching heavily this WILL be
* painful.
*/
public static function purge_all() {
$config = cache_config::instance();
+74 -7
View File
@@ -214,7 +214,7 @@ interface cache_loader_with_locking {
* @return bool True if this code has the lock, false if there is a lock but this code doesn't have it,
* null if there is no lock.
*/
public function has_lock($key);
public function check_lock_state($key);
/**
* Releases the lock for the given key.
@@ -347,6 +347,12 @@ interface cache_store {
*/
public function __construct($name, array $configuration = array());
/**
* Returns the name of this store instance.
* @return string
*/
public function my_name();
/**
* Initialises a new instance of the cache store given the definition the instance is to be used for.
*
@@ -459,29 +465,31 @@ interface cache_is_lockable {
* Acquires a lock on the given key for the given identifier.
*
* @param string $key The key we are locking.
* @param string $identifier The identifier so we can check if we have the lock or if it is someone else.
* @param string $ownerid The identifier so we can check if we have the lock or if it is someone else.
* The use of this property is entirely optional and implementations can act as they like upon it.
* @return bool True if the lock could be acquired, false otherwise.
*/
public function acquire_lock($key, $identifier);
public function acquire_lock($key, $ownerid);
/**
* Test if there is already a lock for the given key and if there is whether it belongs to the calling code.
*
* @param string $key The key we are locking.
* @param string $identifier The identifier so we can check if we have the lock or if it is someone else.
* @param string $ownerid The identifier so we can check if we have the lock or if it is someone else.
* @return bool True if this code has the lock, false if there is a lock but this code doesn't have it, null if there
* is no lock.
*/
public function has_lock($key, $identifier);
public function check_lock_state($key, $ownerid);
/**
* Releases the lock on the given key.
*
* @param string $key The key we are locking.
* @param string $identifier The identifier so we can check if we have the lock or if it is someone else.
* @param string $ownerid The identifier so we can check if we have the lock or if it is someone else.
* The use of this property is entirely optional and implementations can act as they like upon it.
* @return bool True if the lock has been released, false if there was a problem releasing the lock.
*/
public function release_lock($key, $identifier);
public function release_lock($key, $ownerid);
}
/**
@@ -623,3 +631,62 @@ interface cacheable_object {
*/
public static function wake_from_cache($data);
}
/**
* Cache lock interface
*
* This interface needs to be inherited by all cache lock plugins.
*/
interface cache_lock_interface {
/**
* Constructs an instance of the cache lock given its name and its configuration data
*
* @param string $name The unique name of the lock instance
* @param array $configuration
*/
public function __construct($name, array $configuration = array());
/**
* Acquires a lock on a given key.
*
* @param string $key The key to acquire a lock for.
* @param string $ownerid An unique identifier for the owner of this lock. It is entirely optional for the cache lock plugin
* to use this. Each implementation can decide for themselves.
* @param bool $block If set to true the application will wait until a lock can be acquired
* @return bool True if the lock can be acquired false otherwise.
*/
public function lock($key, $ownerid, $block = false);
/**
* Releases the lock held on a certain key.
*
* @param string $key The key to release the lock for.
* @param string $ownerid An unique identifier for the owner of this lock. It is entirely optional for the cache lock plugin
* to use this. Each implementation can decide for themselves.
* @param bool $forceunlock If set to true the lock will be removed if it exists regardless of whether or not we own it.
*/
public function unlock($key, $ownerid, $forceunlock = false);
/**
* Checks the state of the given key.
*
* Returns true if the key is locked and belongs to the ownerid.
* Returns false if the key is locked but does not belong to the ownerid.
* Returns null if there is no lock
*
* @param string $key The key we are checking for.
* @param string $ownerid The identifier so we can check if we have the lock or if it is someone else.
* @return bool True if this code has the lock, false if there is a lock but this code doesn't have it, null if there
* is no lock.
*/
public function check_state($key, $ownerid);
/**
* Cleans up any left over locks.
*
* This function MUST clean up any locks that have been acquired and not released during processing.
* Although the situation of acquiring a lock and not releasing it should be insanely rare we need to deal with it.
* Things such as unfortunate timeouts etc could cause this situation.
*/
public function __destruct();
}
+14 -14
View File
@@ -941,9 +941,9 @@ class cache_application extends cache implements cache_loader_with_locking {
/**
* Gets set to a cache_store to use for locking if the caches primary store doesn't support locking natively.
* @var cache_store
* @var cache_lock_interface
*/
protected $lockstore;
protected $cachelockinstance;
/**
* Overrides the cache construct method.
@@ -1038,8 +1038,8 @@ class cache_application extends cache implements cache_loader_with_locking {
if ($this->nativelocking) {
return $this->get_store()->acquire_lock($key, $this->get_identifier());
} else {
$this->ensure_lock_store_available();
return $this->lockstore->acquire_lock($key, $this->get_identifier());
$this->ensure_cachelock_available();
return $this->cachelockinstance->lock($key, $this->get_identifier());
}
}
@@ -1050,13 +1050,13 @@ class cache_application extends cache implements cache_loader_with_locking {
* @return bool|null Returns true if there is a lock and this cache has it, null if no one has a lock on that key, false if
* someone else has the lock.
*/
public function has_lock($key) {
public function check_lock_state($key) {
$key = $this->parse_key($key);
if ($this->nativelocking) {
return $this->get_store()->has_lock($key, $this->get_identifier());
return $this->get_store()->check_lock_state($key, $this->get_identifier());
} else {
$this->ensure_lock_store_available();
return $this->lockstore->has_lock($key, $this->get_identifier());
$this->ensure_cachelock_available();
return $this->cachelockinstance->check_state($key, $this->get_identifier());
}
}
@@ -1071,8 +1071,8 @@ class cache_application extends cache implements cache_loader_with_locking {
if ($this->nativelocking) {
return $this->get_store()->release_lock($key, $this->get_identifier());
} else {
$this->ensure_lock_store_available();
return $this->lockstore->release_lock($key, $this->get_identifier());
$this->ensure_cachelock_available();
return $this->cachelockinstance->unlock($key, $this->get_identifier());
}
}
@@ -1081,9 +1081,9 @@ class cache_application extends cache implements cache_loader_with_locking {
*
* This should only happen if the cache store doesn't natively support it.
*/
protected function ensure_lock_store_available() {
if ($this->lockstore === null) {
$this->lockstore = cache_helper::get_cachestore_for_locking();
protected function ensure_cachelock_available() {
if ($this->cachelockinstance === null) {
$this->cachelockinstance = cache_helper::get_cachelock_for_store($this->get_store());
}
}
@@ -1168,7 +1168,7 @@ class cache_application extends cache implements cache_loader_with_locking {
* @throws moodle_exception
*/
public function get($key, $strictness = IGNORE_MISSING) {
if ($this->requirelockingread && $this->has_lock($key) === false) {
if ($this->requirelockingread && $this->check_lock_state($key) === false) {
// Read locking required and someone else has the read lock.
return false;
}
-1
View File
@@ -31,7 +31,6 @@
defined('MOODLE_INTERNAL') || die();
// Include the required classes.
require_once($CFG->dirroot.'/cache/classes/lock.php');
require_once($CFG->dirroot.'/cache/classes/interfaces.php');
require_once($CFG->dirroot.'/cache/classes/config.php');
require_once($CFG->dirroot.'/cache/classes/helper.php');
+15 -2
View File
@@ -74,16 +74,22 @@ class cache_config_writer extends cache_config {
$configuration['modemappings'] = $this->configmodemappings;
$configuration['definitions'] = $this->configdefinitions;
$configuration['definitionmappings'] = $this->configdefinitionmappings;
$configuration['locks'] = $this->configlocks;
// Prepare the file content.
$content = "<?php defined('MOODLE_INTERNAL') || die();\n \$configuration = ".var_export($configuration, true).";";
if (cache_lock::lock('config', false)) {
// We need to create a temporary cache lock instance for use here. Remember we are generating the config file
// it doesn't exist and thus we can't use the normal API for this (it'll just try to use config).
$factory = cache_factory::instance();
$locking = $factory->create_lock_instance(reset($this->configlocks));
if ($locking->lock('configwrite', 'config', true)) {
// Its safe to use w mode here because we have already acquired the lock.
$handle = fopen($cachefile, 'w');
fwrite($handle, $content);
fflush($handle);
fclose($handle);
cache_lock::unlock('config');
$locking->unlock('configwrite', 'config');
} else {
throw new cache_exception('ex_configcannotsave', 'cache', '', null, 'Unable to open the cache config file.');
}
@@ -343,6 +349,13 @@ class cache_config_writer extends cache_config {
'sort' => -1
)
);
$writer->configlocks = array(
'default_file_lock' => array(
'name' => 'default_file_lock',
'type' => 'cachelock_file',
'dir' => 'filelocks'
)
);
$writer->config_save();
}
+26
View File
@@ -0,0 +1,26 @@
<?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/>.
/**
* Strings for the cache file locking plugin
*
* @package cachelock_file
* @category cache
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['pluginname'] = 'File locking';
+106 -120
View File
@@ -15,9 +15,9 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Cache lock class. Used for locking when required.
* File locking for the Cache API
*
* @package core
* @package cachelock_file
* @category cache
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -26,36 +26,75 @@
defined('MOODLE_INTERNAL') || die();
/**
* The cache lock class.
* File locking plugin
*
* This class is used for acquiring and releasing locks.
* We use this rather than flock because we can be sure this is cross-platform compatible and thread/process safe.
*
* This class uses the files for locking. It relies on fopens x mode which is documented as follows:
*
* Create and open for writing only; place the file pointer at the beginning of the file. If the file already exists, the
* fopen() call will fail by returning FALSE and generating an error of level E_WARNING.
* http://www.php.net/manual/en/function.fopen.php
*
* Through this we can attempt to call fopen using a lock file name. If the fopen call succeeds we can be sure we have created the
* file and thus ascertained the lock, otherwise fopen fails and we can look at what to do next.
*
* All interaction with this class is handled through its two public static methods, lock and unlock.
* Internally an instance is generated and used for locking and unlocking. It records the locks used during this session and on
* destruction cleans up any left over locks.
* Of course the clean up is just a safe-guard. Really no one should EVER leave a lock and rely on the clean up.
*
* Because this lock system uses files for locking really its probably not ideal, but as I could not think of a better cross
* platform thread safe system it is what we have ended up with.
*
* This system also allows us to lock a file before it is created because it doesn't rely on flock.
*
* @package core
* @category cache
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cache_lock {
class cachelock_file implements cache_lock_interface {
/**
* The name of the cache lock instance
* @var string
*/
protected $name;
/**
* The absolute directory in which lock files will be created and looked for.
* @var string
*/
protected $cachedir;
/**
* The maximum life in seconds for a lock file. By default null for none.
* @var int|null
*/
protected $maxlife = null;
/**
* The number of attempts to acquire a lock when blocking is required before throwing an exception.
* @var int
*/
protected $blockattempts = 100;
/**
* An array containing the locks that have been acquired but not released so far.
* @var array Array of key => lock file path
*/
protected $locks = array();
/**
* Initialises the cache lock instance.
*
* @param string $name The name of the cache lock
* @param array $configuration
*/
public function __construct($name, array $configuration = array()) {
$this->name = $name;
if (!array_key_exists('dir', $configuration)) {
$this->cachedir = make_cache_directory(md5($name));
} else {
$dir = $configuration['dir'];
if (strpos($dir, '/') !== false && strpos($dir, '.') !== 0) {
// This looks like an absolute path.
if (file_exists($dir) && is_dir($dir) && is_writable($dir)) {
$this->cachedir = $dir;
}
}
if (empty($this->cachedir)) {
$dir = preg_replace('#[^a-zA-Z0-9_]#', '_', $dir);
$this->cachedir = make_cache_directory($dir);
}
}
if (array_key_exists('maxlife', $configuration) && is_number($configuration['maxlife'])) {
$maxlife = (int)$configuration['maxlife'];
// Minimum lock time is 60 seconds.
$this->maxlife = max($maxlife, 60);
}
if (array_key_exists('blockattempts', $configuration) && is_number($configuration['blockattempts'])) {
$this->blockattempts = (int)$configuration['blockattempts'];
}
}
/**
* Acquire a lock.
@@ -78,95 +117,12 @@ class cache_lock {
* we can be as sure as possible that the executing code has completed.
*
* @param string $key The key that we want to lock
* @param string $ownerid A unique identifier for the owner of this lock. Not used by default.
* @param bool $block True if we want the program block further execution until the lock has been acquired.
* @param int $maxlife A maximum life for the block file if there should be one. Read the note in the function description
* before using this param.
* @return bool
* @throws cache_exception If block is set to true and more than 100 attempts have been made to acquire a lock.
*/
public static function lock($key, $block = true, $maxlife = null) {
$key = md5($key);
$instance = self::instance();
return $instance->_lock($key, $block, $maxlife);
}
/**
* Releases a lock that has been acquired.
*
* This function can only be used to release locks you have acquired. If you didn't acquire the lock you can't release it.
*
* @param string $key
* @return bool
*/
public static function unlock($key) {
$key = md5($key);
$instance = self::instance();
return $instance->_unlock($key);
}
/**
* Resets the cache lock class, reinitialising it.
*/
public static function reset() {
self::instance(true);
}
/**
* Returns an instance of the cache lock class.
*
* @staticvar bool $instance
* @param bool $forceregeneration
* @return cache_lock
*/
protected static function instance($forceregeneration = false) {
static $instance = false;
if (!$instance || $forceregeneration) {
$instance = new cache_lock();
}
return $instance;
}
/**
* The directory in which lock files will be created
* @var string
*/
protected $cachedir;
/**
* An array of lock files currently held by this cache lock instance.
* @var array
*/
protected $locks = array();
/**
* Constructs this cache lock instance.
*/
protected function __construct() {
$this->cachedir = make_cache_directory('cachelock');
}
/**
* Cleans up the instance what it is no longer needed.
*/
public function __destruct() {
foreach ($this->locks as $lockfile) {
// Naught, naughty developers.
@unlink($lockfile);
}
}
/**
* Acquires a lock, of dies trying (jokes).
*
* Read {@link cache_lock::lock()} for full details.
*
* @param string $key
* @param bool $block
* @param int|null $maxlife
* @return bool
* @throws cache_exception
*/
protected function _lock($key, $block = true, $maxlife = null) {
public function lock($key, $ownerid, $block = false) {
// Get the name of the lock file we want to use.
$lockfile = $this->get_lock_file($key);
@@ -179,11 +135,11 @@ class cache_lock {
// Check if we could create the file or not.
if ($result === false) {
// Lock exists already.
if ($maxlife !== null) {
if ($this->maxlife !== null && !array_key_exists($key, $this->locks)) {
$mtime = filemtime($lockfile);
if ($mtime < time() - $maxlife) {
$this->_unlock($key, true);
$result = $this->_lock($key, false);
if ($mtime < time() - $this->maxlife) {
$this->unlock($key, true);
$result = $this->lock($key, false);
if ($result) {
return true;
}
@@ -192,8 +148,8 @@ class cache_lock {
if ($block) {
// OK we are blocking. We had better sleep and then retry to lock.
$iterations = 0;
$maxiterations = 100;
while (($result = $this->_lock($key, false)) === false) {
$maxiterations = $this->blockattempts;
while (($result = $this->lock($key, false)) === false) {
// usleep causes the application to cleep to x microseconds.
// Before anyone asks there are 1'000'000 microseconds to a second.
usleep(rand(1000, 50000)); // Sleep between 1 and 50 milliseconds
@@ -220,10 +176,11 @@ class cache_lock {
* For more details see {@link cache_lock::unlock()}
*
* @param string $key
* @param string $ownerid A unique identifier for the owner of this lock. Not used by default.
* @param bool $forceunlock If set to true the lock will be removed if it exists regardless of whether or not we own it.
* @return bool
*/
protected function _unlock($key, $forceunlock = false) {
public function unlock($key, $ownerid, $forceunlock = false) {
if (array_key_exists($key, $this->locks)) {
@unlink($this->locks[$key]);
unset($this->locks[$key]);
@@ -239,6 +196,25 @@ class cache_lock {
return false;
}
/**
* Checks if the given key is locked.
*
* @param string $key
* @param string $ownerid
*/
public function check_state($key, $ownerid) {
if (key_exists($key, $this->locks)) {
// The key is locked and we own it.
return true;
}
$lockfile = $this->get_lock_file($key);
if (file_exists($lockfile)) {
// The key is locked and we don't own it.
return false;
}
return null;
}
/**
* Gets the name to use for a lock file.
*
@@ -248,4 +224,14 @@ class cache_lock {
protected function get_lock_file($key) {
return $this->cachedir.'/'. $key .'.lock';
}
/**
* Cleans up the instance what it is no longer needed.
*/
public function __destruct() {
foreach ($this->locks as $lockfile) {
// Naught, naughty developers.
@unlink($lockfile);
}
}
}
+10 -93
View File
@@ -37,7 +37,7 @@
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_file implements cache_store, cache_is_lockable, cache_is_key_aware {
class cachestore_file implements cache_store, cache_is_key_aware {
/**
* The name of the store.
@@ -81,12 +81,6 @@ class cachestore_file implements cache_store, cache_is_lockable, cache_is_key_aw
*/
protected $isready = false;
/**
* An array containing the locks this store instance owns presently.
* @var array
*/
protected $locks = array();
/**
* The cache definition this instance has been initialised with.
* @var cache_definition
@@ -447,81 +441,6 @@ class cachestore_file implements cache_store, cache_is_lockable, cache_is_key_aw
}
return false;
}
/**
* Acquires a lock for the key with the given identifier.
*
* @param string $key The key to acquire a lock for.
* @param string $identifier The identifier who will own the lock.
* @return bool True if the lock could be acquired, false otherwise.
*/
public function acquire_lock($key, $identifier) {
if (array_key_exists($key, $this->locks) && $this->locks[$key] == $identifier) {
// We already have the lock, return true.
return true;
}
$result = cache_lock::lock($key, false);
if ($result) {
$this->locks[$key] = $identifier;
}
return $result;
}
/**
* Releases the lock provided it belongs to the identifier.
*
* @param string $key The key to the lock is for.
* @param string $identifier The identifier of the caller.
* @return bool True if the lock has been released, false if there was a problem releasing the lock.
*/
public function release_lock($key, $identifier) {
if (array_key_exists($key, $this->locks) && $this->locks[$key] == $identifier) {
$outcome = cache_lock::unlock($key);
return $outcome;
}
return false;
}
/**
* Returns true if the given key has a lock and it belongs to the identifier.
*
* @param string $key The key to the lock is for.
* @param string $identifier The identifier of the caller.
* @return bool True if this code has the lock, false if there is a lock but this code doesn't have it, null if there is no lock.
*/
public function has_lock($key, $identifier) {
return (array_key_exists($key, $this->locks) && $this->locks[$key] == $identifier);
}
/**
* Returns the path to the lock file.
*
* @param string $key
* @return string The absolute path to use for a lock file for this key.
*/
protected function get_lock_file($key) {
return $this->path.'/lock-'.$key.'.lock';
}
/**
* Cleans up any left over lock files.
*
* There shouldn't be any left over lock files but clean them up just in case.
*/
public function __destruct() {
$errors = false;
foreach ($this->locks as $file) {
try {
@unlink($file);
} catch (Exception $e) {
// We just want to ensure we unlink everything possible.
$errors = true;
}
}
if ($errors) {
error_log('ERROR ERROR ERROR!!! Unable to release all file cache store locks!');
}
}
/**
* Purges the cache deleting all items within it.
@@ -596,8 +515,7 @@ class cachestore_file implements cache_store, cache_is_lockable, cache_is_key_aw
*
* There are several things going on in this function to try to ensure what we don't end up with partial writes etc.
* 1. Files for writing are opened with the mode xb, the file must be created and can not already exist.
* 2. We use cache_mutex to ensure we acquire a lock.
* 3. Renaming, data is written to a temporary file, where it can be verified using md5 and is then renamed.
* 2. Renaming, data is written to a temporary file, where it can be verified using md5 and is then renamed.
*
* @param string $file Absolute file path
* @param string $content The content to write.
@@ -614,11 +532,6 @@ class cachestore_file implements cache_store, cache_is_lockable, cache_is_key_aw
}
}
// Lock the temp file before we write.
if (!cache_lock::lock($tempfile, false)) {
return false;
}
// Open the file with mode=x. This acts to create and open the file for writing only.
// If the file already exists this will return false.
// We also force binary.
@@ -627,15 +540,11 @@ class cachestore_file implements cache_store, cache_is_lockable, cache_is_key_aw
// File already exists... lock already exists, return false.
return false;
}
// We have the lock. Write our content.
fwrite($handle, $content);
fflush($handle);
// Close the handle, we're done.
fclose($handle);
// Unlock the temp file.
cache_lock::unlock($tempfile);
if (md5_file($tempfile) !== md5($content)) {
// The md5 of the content of the file must match the md5 of the content given to be written.
@unlink($tempfile);
@@ -650,4 +559,12 @@ class cachestore_file implements cache_store, cache_is_lockable, cache_is_key_aw
}
return $result;
}
/**
* Returns the name of this instance.
* @return string
*/
public function my_name() {
return $this->name;
}
}
+8
View File
@@ -362,4 +362,12 @@ class cachestore_memcache implements cache_store {
return $store;
}
/**
* Returns the name of this instance.
* @return string
*/
public function my_name() {
return $this->name;
}
}
+8
View File
@@ -442,4 +442,12 @@ class cachestore_memcached implements cache_store {
return $store;
}
/**
* Returns the name of this instance.
* @return string
*/
public function my_name() {
return $this->name;
}
}
+8
View File
@@ -473,4 +473,12 @@ class cachestore_mongodb implements cache_store {
return $store;
}
/**
* Returns the name of this instance.
* @return string
*/
public function my_name() {
return $this->name;
}
}
+8
View File
@@ -362,6 +362,14 @@ class cachestore_session extends session_data_store implements cache_store, cach
$cache->initialise($definition);
return $cache;
}
/**
* Returns the name of this instance.
* @return string
*/
public function my_name() {
return $this->name;
}
}
/**
+8
View File
@@ -362,6 +362,14 @@ class cachestore_static extends static_data_store implements cache_store, cache_
$cache->initialise($definition);
return $cache;;
}
/**
* Returns the name of this instance.
* @return string
*/
public function my_name() {
return $this->name;
}
}
/**
+2 -2
View File
@@ -324,8 +324,8 @@ class cache_phpunit_tests extends advanced_testcase {
$this->assertTrue($cache1->acquire_lock('testkey'));
$this->assertFalse($cache2->acquire_lock('testkey'));
$this->assertTrue($cache1->has_lock('testkey'));
$this->assertFalse($cache2->has_lock('testkey'));
$this->assertTrue($cache1->check_lock_state('testkey'));
$this->assertFalse($cache2->check_lock_state('testkey'));
$this->assertTrue($cache1->release_lock('testkey'));
$this->assertFalse($cache2->release_lock('testkey'));
+1
View File
@@ -35,6 +35,7 @@ $string['editstore'] = 'Edit store';
$string['editstoresuccess'] = 'Succesfully edited the cache store.';
$string['editdefinitionmappings'] = '{$a} definition store mappings';
$string['ex_configcannotsave'] = 'Unable to save the cache config to file.';
$string['ex_nodefaultlock'] = 'Unable to find a default lock instance.';
$string['ex_unabletolock'] = 'Unable to acquire a lock for caching.';
$string['gethit'] = 'Get - Hit';
$string['getmiss'] = 'Get - Miss';
+2 -1
View File
@@ -8011,7 +8011,8 @@ function get_plugin_types($fullpaths=true) {
'qformat' => 'question/format',
'plagiarism' => 'plagiarism',
'tool' => $CFG->admin.'/tool',
'cachestore' => 'cache/stores',
'cachestore' => 'cache/stores',
'cachelock' => 'cache/locks',
'theme' => 'theme', // this is a bit hacky, themes may be in $CFG->themedir too
);
+4 -1
View File
@@ -534,8 +534,11 @@ class phpunit_util {
make_cache_directory('');
make_cache_directory('htmlpurifier');
// Reset the cache API so that it recreates it's required directories as well.
cache_lock::reset();
cache_factory::reset();
// Purge all data from the caches. This is required for consistency.
// Any file caches that happened to be within the data root will have already been clearer (because we just deleted cache)
// and now we will purge any other caches as well.
cache_helper::purge_all();
}
/**