diff --git a/cache/classes/config.php b/cache/classes/config.php index be09cdc102f..d45b84e5bbb 100644 --- a/cache/classes/config.php +++ b/cache/classes/config.php @@ -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'); + } } \ No newline at end of file diff --git a/cache/classes/dummystore.php b/cache/classes/dummystore.php index ca6a806ac0b..2d691a8cef1 100644 --- a/cache/classes/dummystore.php +++ b/cache/classes/dummystore.php @@ -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; + } } \ No newline at end of file diff --git a/cache/classes/factory.php b/cache/classes/factory.php index 7fc69837d21..be123d0facc 100644 --- a/cache/classes/factory.php +++ b/cache/classes/factory.php @@ -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); + } } \ No newline at end of file diff --git a/cache/classes/helper.php b/cache/classes/helper.php index 18901728c4c..7f936605356 100644 --- a/cache/classes/helper.php +++ b/cache/classes/helper.php @@ -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(); diff --git a/cache/classes/interfaces.php b/cache/classes/interfaces.php index 44b5d6b00ec..1f96fa0c568 100644 --- a/cache/classes/interfaces.php +++ b/cache/classes/interfaces.php @@ -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(); +} \ No newline at end of file diff --git a/cache/classes/loaders.php b/cache/classes/loaders.php index 299b7894689..205872bf98f 100644 --- a/cache/classes/loaders.php +++ b/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 @@ 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; } diff --git a/cache/lib.php b/cache/lib.php index 059993d7a02..0f48b684f46 100644 --- a/cache/lib.php +++ b/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'); diff --git a/cache/locallib.php b/cache/locallib.php index 8c4bf59f844..add82cd90e2 100644 --- a/cache/locallib.php +++ b/cache/locallib.php @@ -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 = "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(); } diff --git a/cache/locks/file/lang/en/cachelock_file.php b/cache/locks/file/lang/en/cachelock_file.php new file mode 100644 index 00000000000..b57188ce58e --- /dev/null +++ b/cache/locks/file/lang/en/cachelock_file.php @@ -0,0 +1,26 @@ +. + +/** + * 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'; \ No newline at end of file diff --git a/cache/classes/lock.php b/cache/locks/file/lib.php similarity index 59% rename from cache/classes/lock.php rename to cache/locks/file/lib.php index 4c8e4f791a7..7b25f092447 100644 --- a/cache/classes/lock.php +++ b/cache/locks/file/lib.php @@ -15,9 +15,9 @@ // along with Moodle. If not, see . /** - * 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); + } + } } \ No newline at end of file diff --git a/cache/stores/file/lib.php b/cache/stores/file/lib.php index c124948e74e..f07d9ed4f2d 100644 --- a/cache/stores/file/lib.php +++ b/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. @@ -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; + } } \ No newline at end of file diff --git a/cache/stores/memcache/lib.php b/cache/stores/memcache/lib.php index c21d64eea4c..d00eb630a41 100644 --- a/cache/stores/memcache/lib.php +++ b/cache/stores/memcache/lib.php @@ -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; + } } \ No newline at end of file diff --git a/cache/stores/memcached/lib.php b/cache/stores/memcached/lib.php index 770775510cc..0ec1300a807 100644 --- a/cache/stores/memcached/lib.php +++ b/cache/stores/memcached/lib.php @@ -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; + } } \ No newline at end of file diff --git a/cache/stores/mongodb/lib.php b/cache/stores/mongodb/lib.php index 7a6fca2e1a6..18f17d5d35d 100644 --- a/cache/stores/mongodb/lib.php +++ b/cache/stores/mongodb/lib.php @@ -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; + } } \ No newline at end of file diff --git a/cache/stores/session/lib.php b/cache/stores/session/lib.php index dfe205e662e..f73009b06c4 100644 --- a/cache/stores/session/lib.php +++ b/cache/stores/session/lib.php @@ -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; + } } /** diff --git a/cache/stores/static/lib.php b/cache/stores/static/lib.php index 2b252836aec..11ba2a6045d 100644 --- a/cache/stores/static/lib.php +++ b/cache/stores/static/lib.php @@ -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; + } } /** diff --git a/cache/tests/cache_test.php b/cache/tests/cache_test.php index 810767d60d4..9035230c51a 100644 --- a/cache/tests/cache_test.php +++ b/cache/tests/cache_test.php @@ -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')); diff --git a/lang/en/cache.php b/lang/en/cache.php index 41398c2f2aa..7ffc7423b0f 100644 --- a/lang/en/cache.php +++ b/lang/en/cache.php @@ -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'; diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 3686a1358f2..ff9ecad8897 100644 --- a/lib/moodlelib.php +++ b/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 ); diff --git a/lib/phpunit/classes/util.php b/lib/phpunit/classes/util.php index 5d23173549a..a4b3d9fd8b1 100644 --- a/lib/phpunit/classes/util.php +++ b/lib/phpunit/classes/util.php @@ -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(); } /**