Skip to content

Commit

Permalink
MDL-25290 cache: Added cache locking plugin and converted locking imp…
Browse files Browse the repository at this point in the history
…lementations to that
  • Loading branch information
Sam Hemelryk committed Oct 7, 2012
1 parent 6fec182 commit 34c84c7
Show file tree
Hide file tree
Showing 20 changed files with 412 additions and 254 deletions.
66 changes: 66 additions & 0 deletions cache/classes/config.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ class cache_config {
*/ */
protected $configdefinitionmappings = array(); 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. * Please use cache_config::instance to get an instance of the cache config that is ready to be used.
*/ */
Expand Down Expand Up @@ -114,8 +120,32 @@ public function load() {


$this->configstores = array(); $this->configstores = array();
$this->configdefinitions = array(); $this->configdefinitions = array();
$this->configlocks = array();
$this->configmodemappings = array(); $this->configmodemappings = array();
$this->configdefinitionmappings = 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 // Filter the stores
$availableplugins = cache_helper::early_get_cache_plugins(); $availableplugins = cache_helper::early_get_cache_plugins();
Expand Down Expand Up @@ -151,6 +181,10 @@ public function load() {
} }
$store['class'] = $class; $store['class'] = $class;
$store['default'] = !empty($store['default']); $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; $this->configstores[$store['name']] = $store;
} }


Expand Down Expand Up @@ -256,6 +290,9 @@ protected function include_configuration() {
if (!array_key_exists('definitionmappings', $configuration) || !is_array($configuration['definitionmappings'])) { if (!array_key_exists('definitionmappings', $configuration) || !is_array($configuration['definitionmappings'])) {
$configuration['definitionmappings'] = array(); $configuration['definitionmappings'] = array();
} }
if (!array_key_exists('locks', $configuration) || !is_array($configuration['locks'])) {
$configuration['locks'] = array();
}


return $configuration; return $configuration;
} }
Expand Down Expand Up @@ -394,4 +431,33 @@ public function get_mode_mappings() {
public function get_definition_mappings() { public function get_definition_mappings() {
return $this->configdefinitionmappings; return $this->configdefinitionmappings;
} }

/**
* Returns an array of the configured locks.
* @return array
*/
public function get_locks() {
return $this->configlocks;
}

/**
* Returns the lock store configuration to use with a given store.
* @param string $storename
* @return array
* @throws cache_exception
*/
public function get_lock_for_store($storename) {
if (array_key_exists($storename, $this->configstores)) {
if (array_key_exists($this->configstores[$storename]['lock'], $this->configlocks)) {
$lock = $this->configstores[$storename]['lock'];
return $this->configlocks[$lock];
}
}
foreach ($this->configlocks as $lockconf) {
if (!empty($lockconf['default'])) {
return $lockconf;
}
}
throw new cache_exception('ex_nodefaultlock');
}
} }
8 changes: 8 additions & 0 deletions cache/classes/dummystore.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -266,4 +266,12 @@ public static function initialise_test_instance(cache_definition $definition) {
$cache->initialise($definition); $cache->initialise($definition);
return $cache;; return $cache;;
} }

/**
* Returns the name of this instance.
* @return string
*/
public function my_name() {
return $this->name;
}
} }
34 changes: 33 additions & 1 deletion cache/classes/factory.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ class cache_factory {
*/ */
protected $definitions = array(); protected $definitions = array();


/**
* An array of lock plugins.
* @var array
*/
protected $lockplugins = null;

/** /**
* Returns an instance of the cache_factor method. * Returns an instance of the cache_factor method.
* *
Expand Down Expand Up @@ -106,6 +112,7 @@ public static function reset() {
$factory->stores = array(); $factory->stores = array();
$factory->configs = array(); $factory->configs = array();
$factory->definitions = array(); $factory->definitions = array();
$factory->lockplugins = null; // MUST be null in order to force its regeneration.
} }


/** /**
Expand Down Expand Up @@ -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. * This is used by the static make methods.
* *
Expand Down Expand Up @@ -304,4 +311,29 @@ protected function create_dummy_store(cache_definition $definition) {
$store->initialise($definition); $store->initialise($definition);
return $store; 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);
}
} }
25 changes: 12 additions & 13 deletions cache/classes/helper.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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. * Returns a cache_lock instance suitable for use with the store.
* @return cache_store|boolean *
* @param cache_store $store
* @return cache_lock_interface
*/ */
public static function get_cachestore_for_locking() { public static function get_cachelock_for_store(cache_store $store) {
$factory = cache_factory::instance();
$definition = $factory->create_definition('core', 'locking');
$instance = cache_config::instance(); $instance = cache_config::instance();
$stores = $instance->get_stores_for_definition($definition); $lockconf = $instance->get_lock_for_store($store->my_name());
foreach ($stores as $name => $details) { $factory = cache_factory::instance();
if ($details['useforlocking']) { return $factory->create_lock_instance($lockconf);
$instances = self::initialise_cachestore_instances(array($name => $details), $definition);
return reset($instances);
}
}
return false;
} }


/** /**
Expand Down Expand Up @@ -387,6 +382,10 @@ public static function get_stats() {


/** /**
* Purge all of the cache stores of all of their data. * 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() { public static function purge_all() {
$config = cache_config::instance(); $config = cache_config::instance();
Expand Down
81 changes: 74 additions & 7 deletions cache/classes/interfaces.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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, * @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. * null if there is no lock.
*/ */
public function has_lock($key); public function check_lock_state($key);


/** /**
* Releases the lock for the given key. * Releases the lock for the given key.
Expand Down Expand Up @@ -347,6 +347,12 @@ public static function can_add_instance();
*/ */
public function __construct($name, array $configuration = array()); 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. * Initialises a new instance of the cache store given the definition the instance is to be used for.
* *
Expand Down Expand Up @@ -459,29 +465,31 @@ interface cache_is_lockable {
* Acquires a lock on the given key for the given identifier. * Acquires a lock on the given key for the given identifier.
* *
* @param string $key The key we are locking. * @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. * @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. * 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 $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 * @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. * is no lock.
*/ */
public function has_lock($key, $identifier); public function check_lock_state($key, $ownerid);


/** /**
* Releases the lock on the given key. * Releases the lock on the given key.
* *
* @param string $key The key we are locking. * @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. * @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);
} }


/** /**
Expand Down Expand Up @@ -623,3 +631,62 @@ public function prepare_to_cache();
*/ */
public static function wake_from_cache($data); 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();
}
Loading

0 comments on commit 34c84c7

Please sign in to comment.