MDL-25290 cache: Added cache locking plugin and converted locking implementations to that
This commit is contained in:
Vendored
+66
@@ -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');
|
||||
}
|
||||
}
|
||||
Vendored
+8
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+33
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+12
-13
@@ -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();
|
||||
|
||||
Vendored
+74
-7
@@ -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();
|
||||
}
|
||||
Vendored
+14
-14
@@ -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;
|
||||
}
|
||||
|
||||
Vendored
-1
@@ -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');
|
||||
|
||||
Vendored
+15
-2
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+10
-93
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+8
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+8
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+8
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+8
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+8
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+2
-2
@@ -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'));
|
||||
|
||||
@@ -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
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user