Browse files

MDL-25290 cache: Added cache locking plugin and converted locking imp…

…lementations to that
  • Loading branch information...
1 parent 6fec182 commit 34c84c723a7f8cc365cbdc82d339fb59016ccbf0 Sam Hemelryk committed Sep 18, 2012
View
66 cache/classes/config.php
@@ -66,6 +66,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.
*/
public function __construct() {
@@ -114,8 +120,32 @@ public function load() {
$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 @@ public function load() {
}
$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 @@ protected function include_configuration() {
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 @@ public function get_mode_mappings() {
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');
+ }
}
View
8 cache/classes/dummystore.php
@@ -266,4 +266,12 @@ public static function initialise_test_instance(cache_definition $definition) {
$cache->initialise($definition);
return $cache;;
}
+
+ /**
+ * Returns the name of this instance.
+ * @return string
+ */
+ public function my_name() {
+ return $this->name;
+ }
}
View
34 cache/classes/factory.php
@@ -77,6 +77,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.
*
* @param bool $forcereload If set to true a new cache_factory instance will be created and used.
@@ -106,6 +112,7 @@ public static function reset() {
$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 @@ public function create_cache_from_params($mode, $component, $area, array $identi
}
/**
- * 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 @@ protected function create_dummy_store(cache_definition $definition) {
$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);
+ }
}
View
25 cache/classes/helper.php
@@ -151,21 +151,16 @@ protected static function initialise_cachestore_instances(array $stores, cache_d
}
/**
- * 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 @@ public static function get_stats() {
/**
* 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();
View
81 cache/classes/interfaces.php
@@ -214,7 +214,7 @@ public function acquire_lock($key);
* @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.
@@ -348,6 +348,12 @@ public static function can_add_instance();
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.
*
* This function should prepare any given connections etc.
@@ -459,29 +465,31 @@ public static function initialise_test_instance(cache_definition $definition);
* 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 @@ public function prepare_to_cache();
*/
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();
+}
View
28 cache/classes/loaders.php
@@ -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 @@ public function acquire_lock($key) {
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 @@ public function acquire_lock($key) {
* @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 @@ public function release_lock($key) {
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 @@ public function release_lock($key) {
*
* 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 @@ public function set_many(array $keyvaluearray) {
* @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;
}
View
1 cache/lib.php
@@ -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');
View
17 cache/locallib.php
@@ -74,16 +74,22 @@ protected function config_save() {
$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 @@ public static function create_default_configuration() {
'sort' => -1
)
);
+ $writer->configlocks = array(
+ 'default_file_lock' => array(
+ 'name' => 'default_file_lock',
+ 'type' => 'cachelock_file',
+ 'dir' => 'filelocks'
+ )
+ );
$writer->config_save();
}
View
26 cache/locks/file/lang/en/cachelock_file.php
@@ -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';
View
224 cache/classes/lock.php → cache/locks/file/lib.php
@@ -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,147 +26,103 @@
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 {
-
- /**
- * Acquire a lock.
- *
- * If the lock can be acquired:
- * This function will return true.
- *
- * If the lock cannot be acquired the result of this method is determined by the block param:
- * $block = true (default)
- * The function will block any further execution unti the lock can be acquired.
- * This involves the function attempting to acquire the lock and the sleeping for a period of time. This process
- * will be repeated until the lock is required or until a limit is hit (100 by default) in which case a cache
- * exception will be thrown.
- * $block = false
- * The function will return false immediately.
- *
- * If a max life has been specified and the lock can not be acquired then the lock file will be checked against this time.
- * In the case that the file exceeds that max time it will be forcefully deleted.
- * Because this can obviously be a dangerous thing it is not used by default. If it is used it should be set high enough that
- * we can be as sure as possible that the executing code has completed.
- *
- * @param string $key The key that we want to lock
- * @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);
- }
+class cachelock_file implements cache_lock_interface {
/**
- * 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
+ * The name of the cache lock instance
+ * @var string
*/
- public static function unlock($key) {
- $key = md5($key);
- $instance = self::instance();
- return $instance->_unlock($key);
- }
+ protected $name;
/**
- * Resets the cache lock class, reinitialising it.
+ * The absolute directory in which lock files will be created and looked for.
+ * @var string
*/
- public static function reset() {
- self::instance(true);
- }
+ protected $cachedir;
/**
- * Returns an instance of the cache lock class.
- *
- * @staticvar bool $instance
- * @param bool $forceregeneration
- * @return cache_lock
+ * The maximum life in seconds for a lock file. By default null for none.
+ * @var int|null
*/
- protected static function instance($forceregeneration = false) {
- static $instance = false;
- if (!$instance || $forceregeneration) {
- $instance = new cache_lock();
- }
- return $instance;
- }
+ protected $maxlife = null;
/**
- * The directory in which lock files will be created
- * @var string
+ * The number of attempts to acquire a lock when blocking is required before throwing an exception.
+ * @var int
*/
- protected $cachedir;
+ protected $blockattempts = 100;
/**
- * An array of lock files currently held by this cache lock instance.
- * @var array
+ * 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();
/**
- * Constructs this cache lock instance.
- */
- protected function __construct() {
- $this->cachedir = make_cache_directory('cachelock');
- }
-
- /**
- * Cleans up the instance what it is no longer needed.
+ * Initialises the cache lock instance.
+ *
+ * @param string $name The name of the cache lock
+ * @param array $configuration
*/
- public function __destruct() {
- foreach ($this->locks as $lockfile) {
- // Naught, naughty developers.
- @unlink($lockfile);
+ 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'];
}
}
/**
- * Acquires a lock, of dies trying (jokes).
+ * Acquire a lock.
*
- * Read {@link cache_lock::lock()} for full details.
+ * If the lock can be acquired:
+ * This function will return true.
*
- * @param string $key
- * @param bool $block
- * @param int|null $maxlife
+ * If the lock cannot be acquired the result of this method is determined by the block param:
+ * $block = true (default)
+ * The function will block any further execution unti the lock can be acquired.
+ * This involves the function attempting to acquire the lock and the sleeping for a period of time. This process
+ * will be repeated until the lock is required or until a limit is hit (100 by default) in which case a cache
+ * exception will be thrown.
+ * $block = false
+ * The function will return false immediately.
+ *
+ * If a max life has been specified and the lock can not be acquired then the lock file will be checked against this time.
+ * In the case that the file exceeds that max time it will be forcefully deleted.
+ * Because this can obviously be a dangerous thing it is not used by default. If it is used it should be set high enough that
+ * 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.
* @return bool
- * @throws cache_exception
+ * @throws cache_exception If block is set to true and more than 100 attempts have been made to acquire a lock.
*/
- 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 @@ protected function _lock($key, $block = true, $maxlife = null) {
// 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 @@ protected function _lock($key, $block = true, $maxlife = null) {
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 @@ protected function _lock($key, $block = true, $maxlife = null) {
* 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]);
@@ -240,6 +197,25 @@ protected function _unlock($key, $forceunlock = 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.
*
* @param string $key
@@ -248,4 +224,14 @@ protected function _unlock($key, $forceunlock = false) {
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);
+ }
+ }
}
View
103 cache/stores/file/lib.php
@@ -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.
@@ -82,12 +82,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 @@ public function has_any(array $keys) {
}
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 @@ public static function initialise_test_instance(cache_definition $definition) {
*
* 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 @@ protected function write_file($file, $content) {
}
}
- // 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 @@ protected function write_file($file, $content) {
// 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 @@ protected function write_file($file, $content) {
}
return $result;
}
+
+ /**
+ * Returns the name of this instance.
+ * @return string
+ */
+ public function my_name() {
+ return $this->name;
+ }
}
View
8 cache/stores/memcache/lib.php
@@ -362,4 +362,12 @@ public static function initialise_test_instance(cache_definition $definition) {
return $store;
}
+
+ /**
+ * Returns the name of this instance.
+ * @return string
+ */
+ public function my_name() {
+ return $this->name;
+ }
}
View
8 cache/stores/memcached/lib.php
@@ -442,4 +442,12 @@ public static function initialise_test_instance(cache_definition $definition) {
return $store;
}
+
+ /**
+ * Returns the name of this instance.
+ * @return string
+ */
+ public function my_name() {
+ return $this->name;
+ }
}
View
8 cache/stores/mongodb/lib.php
@@ -473,4 +473,12 @@ public static function initialise_test_instance(cache_definition $definition) {
return $store;
}
+
+ /**
+ * Returns the name of this instance.
+ * @return string
+ */
+ public function my_name() {
+ return $this->name;
+ }
}
View
8 cache/stores/session/lib.php
@@ -362,6 +362,14 @@ public static function initialise_test_instance(cache_definition $definition) {
$cache->initialise($definition);
return $cache;
}
+
+ /**
+ * Returns the name of this instance.
+ * @return string
+ */
+ public function my_name() {
+ return $this->name;
+ }
}
/**
View
8 cache/stores/static/lib.php
@@ -362,6 +362,14 @@ public static function initialise_test_instance(cache_definition $definition) {
$cache->initialise($definition);
return $cache;;
}
+
+ /**
+ * Returns the name of this instance.
+ * @return string
+ */
+ public function my_name() {
+ return $this->name;
+ }
}
/**
View
4 cache/tests/cache_test.php
@@ -324,8 +324,8 @@ public function test_application_manual_locking() {
$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'));
View
1 lang/en/cache.php
@@ -35,6 +35,7 @@
$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';
View
3 lib/moodlelib.php
@@ -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
);
View
5 lib/phpunit/classes/util.php
@@ -534,8 +534,11 @@ public static function reset_dataroot() {
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();
}
/**

0 comments on commit 34c84c7

Please sign in to comment.