Merge branch 'wip-MDL-38565-m25' of git://github.com/samhemelryk/moodle

This commit is contained in:
Dan Poltawski 2013-04-19 13:29:07 +01:00
commit 00a850b33e
16 changed files with 1135 additions and 54 deletions

4
cache/README.md vendored
View File

@ -18,6 +18,7 @@ A definition:
'requiremultipleidentifiers' => false, // Optional
'requirelockingread' => false, // Optional
'requirelockingwrite' => false, // Optional
'requiresearchable' => false, // Optional
'maxsize' => null, // Optional
'overrideclass' => null, // Optional
'overrideclassfile' => null, // Optional
@ -135,6 +136,7 @@ The following optional settings can also be defined:
* requiremultipleidentifiers - If set to true then only stores that support multiple identifiers will be used.
* requirelockingread - If set to true a lock will be acquired for reading. Don't use this setting unless you have a REALLY good reason to.
* requirelockingwrite - If set to true a lock will be acquired before writing to the cache. Avoid this unless necessary.
* requiresearchable - If set to true only stores that support key searching will be used for this definition. Its not recommended to use this unless absolutely unavoidable.
* maxsize - This gives a cache an indication about the maximum items it should store. Cache stores don't have to use this, it is up to them to decide if its required.
* overrideclass - If provided this class will be used for the loader. It must extend one of the core loader classes (based upon mode).
* overrideclassfile - Included if required when using the overrideclass param.
@ -236,4 +238,4 @@ The following snippet illustates how to configure the three core cache stores th
define('TEST_CACHESTORE_MEMCACHE_TESTSERVERS', '127.0.0.1:11211');
define('TEST_CACHESTORE_MEMCACHED_TESTSERVERS', '127.0.0.1:11211');
define('TEST_CACHESTORE_MONGODB_TESTSERVER', 'mongodb://localhost:27017');
define('TEST_CACHESTORE_MONGODB_TESTSERVER', 'mongodb://localhost:27017');

View File

@ -183,6 +183,13 @@ class cache_definition {
*/
protected $requirelockingwrite = false;
/**
* Gets set to true if this definition requires searchable stores.
* @since 2.4.4
* @var bool
*/
protected $requiresearchable = false;
/**
* Sets the maximum number of items that can exist in the cache.
* Please note this isn't a hard limit, and doesn't need to be enforced by the caches. They can choose to do so optionally.
@ -307,6 +314,7 @@ class cache_definition {
$requiremultipleidentifiers = false;
$requirelockingread = false;
$requirelockingwrite = false;
$requiresearchable = ($mode === cache_store::MODE_SESSION) ? true : false;
$maxsize = null;
$overrideclass = null;
$overrideclassfile = null;
@ -342,6 +350,10 @@ class cache_definition {
}
$requirelocking = $requirelockingwrite || $requirelockingread;
if (array_key_exists('requiresearchable', $definition)) {
$requiresearchable = (bool)$definition['requiresearchable'];
}
if (array_key_exists('maxsize', $definition)) {
$maxsize = (int)$definition['maxsize'];
}
@ -433,6 +445,7 @@ class cache_definition {
$cachedefinition->requirelocking = $requirelocking;
$cachedefinition->requirelockingread = $requirelockingread;
$cachedefinition->requirelockingwrite = $requirelockingwrite;
$cachedefinition->requiresearchable = $requiresearchable;
$cachedefinition->maxsize = $maxsize;
$cachedefinition->overrideclass = $overrideclass;
$cachedefinition->overrideclassfile = $overrideclassfile;
@ -633,6 +646,15 @@ class cache_definition {
return $this->requirelockingwrite;
}
/**
* Returns true if this definition requires a searchable cache.
* @since 2.4.4
* @return bool
*/
public function require_searchable() {
return $this->requiresearchable;
}
/**
* Returns true if this definition has an associated data source.
* @return bool
@ -686,6 +708,9 @@ class cache_definition {
if ($this->require_multiple_identifiers()) {
$requires += cache_store::SUPPORTS_MULTIPLE_IDENTIFIERS;
}
if ($this->require_searchable()) {
$requires += cache_store::IS_SEARCHABLE;
}
return $requires;
}
@ -694,7 +719,7 @@ class cache_definition {
* @return bool
*/
public function should_be_persistent() {
return $this->persistent;
return $this->persistent || $this->mode === cache_store::MODE_SESSION;
}
/**

View File

@ -207,9 +207,7 @@ class cache_factory {
if (array_key_exists($key, $this->cachesfromparams)) {
return $this->cachesfromparams[$key];
}
// Get the class. Note this is a late static binding so we need to use get_called_class.
$definition = cache_definition::load_adhoc($mode, $component, $area, $options);
$config = $this->create_config_instance();
$definition->set_identifiers($identifiers);
$cache = $this->create_cache($definition, $identifiers);
if ($definition->should_be_persistent()) {

View File

@ -244,10 +244,9 @@ class cache_helper {
if ($definition->invalidates_on_event($event)) {
// OK at this point we know that the definition has information to invalidate on the event.
// There are two routes, either its an application cache in which case we can invalidate it now.
// or it is a session cache in which case we need to set something to the "Event invalidation" definition.
// No need to deal with request caches, we don't want to change data half way through a request.
if ($definition->get_mode() === cache_store::MODE_APPLICATION) {
$cache = $factory->create_cache($definition);
// or it is a persistent cache that also needs to be invalidated now.
if ($definition->get_mode() === cache_store::MODE_APPLICATION || $definition->should_be_persistent()) {
$cache = $factory->create_cache_from_definition($definition->get_component(), $definition->get_area());
$cache->delete_many($keys);
}
@ -568,4 +567,64 @@ class cache_helper {
global $CFG;
return (string)$CFG->version;
}
/**
* Runs cron routines for MUC.
*/
public static function cron() {
self::clean_old_session_data(true);
}
/**
* Cleans old session data from cache stores used for session based definitions.
*
* @param bool $output If set to true output will be given.
*/
public static function clean_old_session_data($output = false) {
global $CFG;
if ($output) {
mtrace('Cleaning up stale session data from cache stores.');
}
$factory = cache_factory::instance();
$config = $factory->create_config_instance();
$definitions = $config->get_definitions();
$purgetime = time() - $CFG->sessiontimeout;
foreach ($definitions as $definitionarray) {
// We are only interested in session caches.
if (!($definitionarray['mode'] & cache_store::MODE_SESSION)) {
continue;
}
$definition = $factory->create_definition($definitionarray['component'], $definitionarray['area']);
$stores = $config->get_stores_for_definition($definition);
// Turn them into store instances.
$stores = self::initialise_cachestore_instances($stores, $definition);
// Initialise all of the stores used for that definition.
foreach ($stores as $store) {
// If the store doesn't support searching we can skip it.
if (!($store instanceof cache_is_searchable)) {
debugging('Cache stores used for session definitions should ideally be searchable.', DEBUG_DEVELOPER);
continue;
}
// Get all of the keys.
$keys = $store->find_by_prefix(cache_session::KEY_PREFIX);
$todelete = array();
foreach ($store->get_many($keys) as $key => $value) {
if (strpos($key, cache_session::KEY_PREFIX) !== 0 || !is_array($value) || !isset($value['lastaccess'])) {
continue;
}
if ((int)$value['lastaccess'] < $purgetime || true) {
$todelete[] = $key;
}
}
if (count($todelete)) {
$outcome = (int)$store->delete_many($todelete);
if ($output) {
$strdef = s($definition->get_id());
$strstore = s($store->my_name());
mtrace("- Removed {$outcome} old {$strdef} sessions from the '{$strstore}' cache store.");
}
}
}
}
}
}

View File

@ -338,6 +338,30 @@ interface cache_is_key_aware {
public function has_all(array $keys);
}
/**
* Cache store feature: keys are searchable.
*
* Cache stores can choose to implement this interface.
* In order for a store to be usable as a session cache it must implement this interface.
*
* @since 2.4.4
*/
interface cache_is_searchable {
/**
* Finds all of the keys being used by the cache store.
*
* @return array.
*/
public function find_all();
/**
* Finds all of the keys whose keys start with the given prefix.
*
* @param string $prefix
*/
public function find_by_prefix($prefix);
}
/**
* Cache store feature: configurable.
*

View File

@ -141,7 +141,7 @@ class cache implements cache_loader {
* and having it here helps speed up processing.
* @var strubg
*/
private $storetype = 'unknown';
protected $storetype = 'unknown';
/**
* Gets set to true if we want to collect performance information about the cache API.
@ -365,6 +365,7 @@ class cache implements cache_loader {
*/
public function get_many(array $keys, $strictness = IGNORE_MISSING) {
$keysparsed = array();
$parsedkeys = array();
$resultpersist = array();
$resultstore = array();
@ -374,6 +375,7 @@ class cache implements cache_loader {
$isusingpersist = $this->is_using_persist_cache();
foreach ($keys as $key) {
$pkey = $this->parse_key($key);
$keysparsed[$key] = $pkey;
$parsedkeys[$pkey] = $key;
$keystofind[$pkey] = $key;
if ($isusingpersist) {
@ -426,9 +428,11 @@ class cache implements cache_loader {
$resultmissing = $this->datasource->load_many_for_cache($missingkeys);
}
foreach ($resultmissing as $key => $value) {
$result[$key] = $value;
$pkey = ($usingloader) ? $key : $keysparsed[$key];
$realkey = ($usingloader) ? $parsedkeys[$key] : $key;
$result[$pkey] = $value;
if ($value !== false) {
$this->set($parsedkeys[$key], $value);
$this->set($realkey, $value);
}
}
unset($resultmissing);
@ -748,7 +752,7 @@ class cache implements cache_loader {
public function delete($key, $recurse = true) {
$parsedkey = $this->parse_key($key);
$this->delete_from_persist_cache($parsedkey);
if ($recurse && !empty($this->loader)) {
if ($recurse && $this->loader !== false) {
// Delete from the bottom of the stack first.
$this->loader->delete($key, $recurse);
}
@ -770,7 +774,7 @@ class cache implements cache_loader {
$this->delete_from_persist_cache($parsedkey);
}
}
if ($recurse && !empty($this->loader)) {
if ($recurse && $this->loader !== false) {
// Delete from the bottom of the stack first.
$this->loader->delete_many($keys, $recurse);
}
@ -848,6 +852,26 @@ class cache implements cache_loader {
return $this->store;
}
/**
* Returns the loader associated with this instance.
*
* @since 2.4.4
* @return cache_loader|false
*/
protected function get_loader() {
return $this->loader;
}
/**
* Returns the data source associated with this cache.
*
* @since 2.4.4
* @return cache_data_source|false
*/
protected function get_datasource() {
return $this->datasource;
}
/**
* Returns true if the store supports key awareness.
*
@ -1396,6 +1420,18 @@ class cache_application extends cache implements cache_loader_with_locking {
*
* This class is used for session caches returned by the cache::make methods.
*
* It differs from the application loader in a couple of noteable ways:
* 1. Sessions are always expected to be persistent.
* Because of this we don't ever use the persist cache and instead a session array
* containing all of the data is maintained by this object.
* 2. Session data for a loader instance (store + definition) is consolidate into a
* single array for storage within the store.
* Along with this we embed a lastaccessed time with the data. This way we can
* check sessions for a last access time.
* 3. Session stores are required to support key searching and must
* implement cache_is_searchable. This ensures stores used for the cache can be
* targetted for garbage collection of session data.
*
* This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
* It is technically possible to call those methods through this class however there is no guarantee that you will get an
* instance of this class back again.
@ -1421,6 +1457,24 @@ class cache_session extends cache {
* @var int
*/
protected $currentuserid = null;
/**
* The session id we are currently using.
* @var array
*/
protected $sessionid = null;
/**
* The session data for the above session id.
* @var array
*/
protected $session = null;
/**
* Constant used to prefix keys.
*/
const KEY_PREFIX = 'sess_';
/**
* Override the cache::construct method.
*
@ -1494,12 +1548,16 @@ class cache_session extends cache {
* This function is called for every operation that uses keys. For this reason we use this function to also check
* that the current user is the same as the user who last used this cache.
*
* On top of that if prepends the string 'sess_' to the start of all keys. The _ ensures things are easily identifiable.
*
* @param string|int $key As passed to get|set|delete etc.
* @return string|array String unless the store supports multi-identifiers in which case an array if returned.
*/
protected function parse_key($key) {
$this->check_tracked_user();
return parent::parse_key($key);
if ($key === 'lastaccess') {
$key = '__lastaccess__';
}
return 'sess_'.parent::parse_key($key);
}
/**
@ -1514,11 +1572,13 @@ class cache_session extends cache {
$new = 0;
}
if ($new !== self::$loadeduserid) {
// The current user doesn't match the tracker userid for this request.
// The current user doesn't match the tracked userid for this request.
if (!is_null(self::$loadeduserid)) {
// Purge the data we have for the old user.
// This way we don't bloat the session.
$this->purge();
// Update the session id just in case!
$this->sessionid = session_id();
}
self::$loadeduserid = $new;
$this->currentuserid = $new;
@ -1526,8 +1586,427 @@ class cache_session extends cache {
// The current user matches the loaded user but not the user last used by this cache.
$this->purge();
$this->currentuserid = $new;
// Update the session id just in case!
$this->sessionid = session_id();
}
}
/**
* Gets the session data.
*
* @param bool $force If true the session data will be loaded from the store again.
* @return array An array of session data.
*/
protected function get_session_data($force = false) {
if ($this->sessionid === null) {
$this->sessionid = session_id();
}
if (is_array($this->session) && !$force) {
return $this->session;
}
$session = parent::get($this->sessionid);
if ($session === false) {
$session = array();
}
// We have to write here to ensure that the lastaccess time is recorded.
// And also in order to ensure the session entry exists as when we save it on __destruct
// $CFG is likely to have already been destroyed.
$this->save_session($session);
return $this->session;
}
/**
* Saves the session data.
*
* This function also updates the last access time.
*
* @param array $session
* @return bool
*/
protected function save_session(array $session) {
$session['lastaccess'] = time();
$this->session = $session;
return parent::set($this->sessionid, $this->session);
}
/**
* Retrieves the value for the given key from the cache.
*
* @param string|int $key The key for the data being requested.
* It can be any structure although using a scalar string or int is recommended in the interests of performance.
* In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
* @param int $strictness One of IGNORE_MISSING | MUST_EXIST
* @return mixed|false The data from the cache or false if the key did not exist within the cache.
* @throws moodle_exception
*/
public function get($key, $strictness = IGNORE_MISSING) {
// Check the tracked user.
$this->check_tracked_user();
// 2. Parse the key.
$parsedkey = $this->parse_key($key);
// 3. Get it from the store.
$result = false;
$session = $this->get_session_data();
if (array_key_exists($parsedkey, $session)) {
$result = $session[$parsedkey];
if ($result instanceof cache_ttl_wrapper) {
if ($result->has_expired()) {
$this->get_store()->delete($parsedkey);
$result = false;
} else {
$result = $result->data;
}
}
if ($result instanceof cache_cached_object) {
$result = $result->restore_object();
}
}
// 4. Load if from the loader/datasource if we don't already have it.
$setaftervalidation = false;
if ($result === false) {
if ($this->perfdebug) {
cache_helper::record_cache_miss('**static session**', $this->get_definition()->get_id());
}
if ($this->get_loader() !== false) {
// We must pass the original (unparsed) key to the next loader in the chain.
// The next loader will parse the key as it sees fit. It may be parsed differently
// depending upon the capabilities of the store associated with the loader.
$result = $this->get_loader()->get($key);
} else if ($this->get_datasource() !== false) {
$result = $this->get_datasource()->load_for_cache($key);
}
$setaftervalidation = ($result !== false);
} else if ($this->perfdebug) {
cache_helper::record_cache_hit('**static session**', $this->get_definition()->get_id());
}
// 5. Validate strictness.
if ($strictness === MUST_EXIST && $result === false) {
throw new moodle_exception('Requested key did not exist in any cache stores and could not be loaded.');
}
// 6. Set it to the store if we got it from the loader/datasource.
if ($setaftervalidation) {
$this->set($key, $result);
}
// 7. Make sure we don't pass back anything that could be a reference.
// We don't want people modifying the data in the cache.
if (!is_scalar($result)) {
// If data is an object it will be a reference.
// If data is an array if may contain references.
// We want to break references so that the cache cannot be modified outside of itself.
// Call the function to unreference it (in the best way possible).
$result = $this->unref($result);
}
return $result;
}
/**
* Sends a key => value pair to the cache.
*
* <code>
* // This code will add four entries to the cache, one for each url.
* $cache->set('main', 'http://moodle.org');
* $cache->set('docs', 'http://docs.moodle.org');
* $cache->set('tracker', 'http://tracker.moodle.org');
* $cache->set('qa', 'http://qa.moodle.net');
* </code>
*
* @param string|int $key The key for the data being requested.
* It can be any structure although using a scalar string or int is recommended in the interests of performance.
* In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
* @param mixed $data The data to set against the key.
* @return bool True on success, false otherwise.
*/
public function set($key, $data) {
$this->check_tracked_user();
if ($this->perfdebug) {
cache_helper::record_cache_set('**static session**', $this->get_definition()->get_id());
}
if (is_object($data) && $data instanceof cacheable_object) {
$data = new cache_cached_object($data);
} else if (!is_scalar($data)) {
// If data is an object it will be a reference.
// If data is an array if may contain references.
// We want to break references so that the cache cannot be modified outside of itself.
// Call the function to unreference it (in the best way possible).
$data = $this->unref($data);
}
// We dont' support native TTL here as we consolidate data for sessions.
if ($this->has_a_ttl()) {
$data = new cache_ttl_wrapper($data, $this->get_definition()->get_ttl());
}
$session = $this->get_session_data();
$session[$this->parse_key($key)] = $data;
return $this->save_session($session);
}
/**
* Delete the given key from the cache.
*
* @param string|int $key The key to delete.
* @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
* This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
* @return bool True of success, false otherwise.
*/
public function delete($key, $recurse = true) {
$this->check_tracked_user();
$parsedkey = $this->parse_key($key);
if ($recurse && $this->get_loader() !== false) {
// Delete from the bottom of the stack first.
$this->get_loader()->delete($key, $recurse);
}
$session = $this->get_session_data();
unset($session[$parsedkey]);
return $this->save_session($session);
}
/**
* Retrieves an array of values for an array of keys.
*
* Using this function comes with potential performance implications.
* Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
* the equivalent singular method for each item provided.
* This should not deter you from using this function as there is a performance benefit in situations where the cache store
* does support it, but you should be aware of this fact.
*
* @param array $keys The keys of the data being requested.
* Each key can be any structure although using a scalar string or int is recommended in the interests of performance.
* In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
* @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
* @return array An array of key value pairs for the items that could be retrieved from the cache.
* If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
* Otherwise any key that did not exist will have a data value of false within the results.
* @throws moodle_exception
*/
public function get_many(array $keys, $strictness = IGNORE_MISSING) {
$this->check_tracked_user();
$return = array();
foreach ($keys as $key) {
$return[$key] = $this->get($key, $strictness);
}
return $return;
}
/**
* Delete all of the given keys from the cache.
*
* @param array $keys The key to delete.
* @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
* This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
* @return int The number of items successfully deleted.
*/
public function delete_many(array $keys, $recurse = true) {
$this->check_tracked_user();
$parsedkeys = array_map(array($this, 'parse_key'), $keys);
if ($recurse && $this->get_loader() !== false) {
// Delete from the bottom of the stack first.
$this->get_loader()->delete_many($keys, $recurse);
}
$session = $this->get_session_data();
foreach ($parsedkeys as $parsedkey) {
unset($session[$parsedkey]);
}
$this->save_session($session);
return count($keys);
}
/**
* Sends several key => value pairs to the cache.
*
* Using this function comes with potential performance implications.
* Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
* the equivalent singular method for each item provided.
* This should not deter you from using this function as there is a performance benefit in situations where the cache store
* does support it, but you should be aware of this fact.
*
* <code>
* // This code will add four entries to the cache, one for each url.
* $cache->set_many(array(
* 'main' => 'http://moodle.org',
* 'docs' => 'http://docs.moodle.org',
* 'tracker' => 'http://tracker.moodle.org',
* 'qa' => ''http://qa.moodle.net'
* ));
* </code>
*
* @param array $keyvaluearray An array of key => value pairs to send to the cache.
* @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
* ... if they care that is.
*/
public function set_many(array $keyvaluearray) {
$this->check_tracked_user();
$session = $this->get_session_data();
$simulatettl = $this->has_a_ttl();
foreach ($keyvaluearray as $key => $value) {
if (is_object($value) && $value instanceof cacheable_object) {
$value = new cache_cached_object($value);
} else if (!is_scalar($value)) {
// If data is an object it will be a reference.
// If data is an array if may contain references.
// We want to break references so that the cache cannot be modified outside of itself.
// Call the function to unreference it (in the best way possible).
$value = $this->unref($value);
}
if ($simulatettl) {
$value = new cache_ttl_wrapper($value, $this->get_definition()->get_ttl());
}
$parsedkey = $this->parse_key($key);
$session[$parsedkey] = $value;
}
if ($this->perfdebug) {
cache_helper::record_cache_set($this->storetype, $this->get_definition()->get_id());
}
$this->save_session($session);
return count($keyvaluearray);
}
/**
* Purges the cache store, and loader if there is one.
*
* @return bool True on success, false otherwise
*/
public function purge() {
// 1. Purge the session object.
$this->session = array();
// 2. Delete the record for this users session from the store.
$this->get_store()->delete($this->sessionid);
// 3. Optionally purge any stacked loaders in the same way.
if ($this->get_loader()) {
$this->get_loader()->delete($this->sessionid);
}
return true;
}
/**
* Test is a cache has a key.
*
* The use of the has methods is strongly discouraged. In a high load environment the cache may well change between the
* test and any subsequent action (get, set, delete etc).
* Instead it is recommended to write your code in such a way they it performs the following steps:
* <ol>
* <li>Attempt to retrieve the information.</li>
* <li>Generate the information.</li>
* <li>Attempt to set the information</li>
* </ol>
*
* Its also worth mentioning that not all stores support key tests.
* For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
* Just one more reason you should not use these methods unless you have a very good reason to do so.
*
* @param string|int $key
* @param bool $tryloadifpossible If set to true, the cache doesn't contain the key, and there is another cache loader or
* data source then the code will try load the key value from the next item in the chain.
* @return bool True if the cache has the requested key, false otherwise.
*/
public function has($key, $tryloadifpossible = false) {
$this->check_tracked_user();
$parsedkey = $this->parse_key($key);
$session = $this->get_session_data();
$has = false;
if ($this->has_a_ttl()) {
// The data has a TTL and the store doesn't support it natively.
// We must fetch the data and expect a ttl wrapper.
if (array_key_exists($parsedkey, $session)) {
$data = $session[$parsedkey];
$has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
}
} else {
$has = array_key_exists($parsedkey, $session);
}
if (!$has && $tryloadifpossible) {
if ($this->get_loader() !== false) {
$result = $this->get_loader()->get($key);
} else if ($this->get_datasource() !== null) {
$result = $this->get_datasource()->load_for_cache($key);
}
$has = ($result !== null);
if ($has) {
$this->set($key, $result);
}
}
return $has;
}
/**
* Test is a cache has all of the given keys.
*
* It is strongly recommended to avoid the use of this function if not absolutely required.
* In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
*
* Its also worth mentioning that not all stores support key tests.
* For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
* Just one more reason you should not use these methods unless you have a very good reason to do so.
*
* @param array $keys
* @return bool True if the cache has all of the given keys, false otherwise.
*/
public function has_all(array $keys) {
$this->check_tracked_user();
$session = $this->get_session_data();
foreach ($keys as $key) {
$has = false;
$parsedkey = $this->parse_key($key);
if ($this->has_a_ttl()) {
// The data has a TTL and the store doesn't support it natively.
// We must fetch the data and expect a ttl wrapper.
if (array_key_exists($parsedkey, $session)) {
$data = $session[$parsedkey];
$has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
}
} else {
$has = array_key_exists($parsedkey, $session);
}
if (!$has) {
return false;
}
}
return true;
}
/**
* Test if a cache has at least one of the given keys.
*
* It is strongly recommended to avoid the use of this function if not absolutely required.
* In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
*
* Its also worth mentioning that not all stores support key tests.
* For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
* Just one more reason you should not use these methods unless you have a very good reason to do so.
*
* @param array $keys
* @return bool True if the cache has at least one of the given keys
*/
public function has_any(array $keys) {
$this->check_tracked_user();
$session = $this->get_session_data();
foreach ($keys as $key) {
$has = false;
$parsedkey = $this->parse_key($key);
if ($this->has_a_ttl()) {
// The data has a TTL and the store doesn't support it natively.
// We must fetch the data and expect a ttl wrapper.
if (array_key_exists($parsedkey, $session)) {
$data = $session[$parsedkey];
$has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
}
} else {
$has = array_key_exists($parsedkey, $session);
}
if ($has) {
return true;
}
}
return false;
}
/**
* The session loader never uses the persist cache.
* Instead it stores things in the static $session variable. Shared between all session loaders.
*
* @return bool
*/
protected function is_using_persist_cache() {
return false;
}
}
/**

View File

@ -110,6 +110,11 @@ abstract class cache_store implements cache_store_interface {
*/
const SUPPORTS_NATIVE_TTL = 4;
/**
* The cache is searchable by key.
*/
const IS_SEARCHABLE = 8;
// Constants for the modes of a cache store
/**
@ -307,6 +312,15 @@ abstract class cache_store implements cache_store_interface {
return $this::get_supported_features() & self::SUPPORTS_NATIVE_TTL;
}
/**
* Returns true if the store instance is searchable.
*
* @return bool
*/
public function is_searchable() {
return in_array('cache_is_searchable', class_implements($this));
}
/**
* Creates a clone of this store instance ready to be initialised.
*

2
cache/forms.php vendored
View File

@ -200,7 +200,7 @@ class cache_mode_mappings_form extends moodleform {
);
foreach ($stores as $storename => $store) {
foreach ($store['modes'] as $mode => $enabled) {
if ($enabled) {
if ($enabled && ($mode !== cache_store::MODE_SESSION || $store['supports']['searchable'])) {
if (empty($store['default'])) {
$options[$mode][$storename] = $store['name'];
} else {

74
cache/locallib.php vendored
View File

@ -340,32 +340,7 @@ class cache_config_writer extends cache_config {
require_once($CFG->dirroot.'/cache/stores/static/lib.php');
$writer = new self;
$writer->configstores = array(
'default_application' => array(
'name' => 'default_application',
'plugin' => 'file',
'configuration' => array(),
'features' => cachestore_file::get_supported_features(),
'modes' => cache_store::MODE_APPLICATION,
'default' => true,
),
'default_session' => array(
'name' => 'default_session',
'plugin' => 'session',
'configuration' => array(),
'features' => cachestore_session::get_supported_features(),
'modes' => cache_store::MODE_SESSION,
'default' => true,
),
'default_request' => array(
'name' => 'default_request',
'plugin' => 'static',
'configuration' => array(),
'features' => cachestore_static::get_supported_features(),
'modes' => cache_store::MODE_REQUEST,
'default' => true,
)
);
$writer->configstores = self::get_default_stores();
$writer->configdefinitions = self::locate_definitions();
$writer->configmodemappings = array(
array(
@ -404,6 +379,52 @@ class cache_config_writer extends cache_config {
return true;
}
/**
* Returns an array of default stores for use.
*
* @return array
*/
protected static function get_default_stores() {
return array(
'default_application' => array(
'name' => 'default_application',
'plugin' => 'file',
'configuration' => array(),
'features' => cachestore_file::get_supported_features(),
'modes' => cachestore_file::get_supported_modes(),
'default' => true,
),
'default_session' => array(
'name' => 'default_session',
'plugin' => 'session',
'configuration' => array(),
'features' => cachestore_session::get_supported_features(),
'modes' => cachestore_session::get_supported_modes(),
'default' => true,
),
'default_request' => array(
'name' => 'default_request',
'plugin' => 'static',
'configuration' => array(),
'features' => cachestore_static::get_supported_features(),
'modes' => cachestore_static::get_supported_modes(),
'default' => true,
)
);
}
/**
* Updates the default stores within the MUC config file.
*/
public static function update_default_config_stores() {
$factory = cache_factory::instance();
$factory->updating_started();
$config = $factory->create_config_instance(true);
$config->configstores = array_merge($config->configstores, self::get_default_stores());
$config->config_save();
$factory->updating_finished();
}
/**
* Updates the definition in the configuration from those found in the cache files.
*
@ -581,6 +602,7 @@ abstract class cache_administration_helper extends cache_helper {
'nativettl' => $store->supports_native_ttl(),
'nativelocking' => ($store instanceof cache_is_lockable),
'keyawareness' => ($store instanceof cache_is_key_aware),
'searchable' => ($store instanceof cache_is_searchable)
)
);
if (empty($details['default'])) {

View File

@ -37,7 +37,7 @@
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_file extends cache_store implements cache_is_key_aware, cache_is_configurable {
class cachestore_file extends cache_store implements cache_is_key_aware, cache_is_configurable, cache_is_searchable {
/**
* The name of the store.
@ -190,7 +190,8 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
*/
public static function get_supported_features(array $configuration = array()) {
$supported = self::SUPPORTS_DATA_GUARANTEE +
self::SUPPORTS_NATIVE_TTL;
self::SUPPORTS_NATIVE_TTL +
self::IS_SEARCHABLE;
return $supported;
}
@ -257,13 +258,15 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
/**
* Gets a pattern suitable for use with glob to find all keys in the cache.
*
* @param string $prefix A prefix to use.
* @return string The pattern.
*/
protected function glob_keys_pattern() {
protected function glob_keys_pattern($prefix = '') {
if ($this->singledirectory) {
return $this->path . '/*.cache';
return $this->path . '/'.$prefix.'*.cache';
} else {
return $this->path . '/*/*.cache';
return $this->path . '/*/'.$prefix.'*.cache';
}
}
@ -365,7 +368,6 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
public function delete($key) {
$filename = $key.'.cache';
$file = $this->file_path_for_key($key);
if (@unlink($file)) {
unset($this->keys[$filename]);
return true;
@ -687,4 +689,42 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
public function my_name() {
return $this->name;
}
/**
* Finds all of the keys being used by this cache store instance.
*
* @return array
*/
public function find_all() {
$this->ensure_path_exists();
$files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
$return = array();
if ($files === false) {
return $return;
}
foreach ($files as $file) {
$return[] = substr(basename($file), 0, -6);
}
return $return;
}
/**
* Finds all of the keys whose keys start with the given prefix.
*
* @param string $prefix
*/
public function find_by_prefix($prefix) {
$this->ensure_path_exists();
$prefix = preg_replace('#(\*|\?|\[)#', '[$1]', $prefix);
$files = glob($this->glob_keys_pattern($prefix), GLOB_MARK | GLOB_NOSORT);
$return = array();
if ($files === false) {
return $return;
}
foreach ($files as $file) {
// Trim off ".cache" from the end.
$return[] = substr(basename($file), 0, -6);
}
return $return;
}
}

View File

@ -90,7 +90,7 @@ abstract class session_data_store extends cache_store {
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_session extends session_data_store implements cache_is_key_aware {
class cachestore_session extends session_data_store implements cache_is_key_aware, cache_is_searchable {
/**
* The name of the store
@ -137,7 +137,8 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
*/
public static function get_supported_features(array $configuration = array()) {
return self::SUPPORTS_DATA_GUARANTEE +
self::SUPPORTS_NATIVE_TTL;
self::SUPPORTS_NATIVE_TTL +
self::IS_SEARCHABLE;
}
/**
@ -403,4 +404,28 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
public function my_name() {
return $this->name;
}
/**
* Finds all of the keys being stored in the cache store instance.
*
* @return array
*/
public function find_all() {
return array_keys($this->store);
}
/**
* Finds all of the keys whose keys start with the given prefix.
*
* @param string $prefix
*/
public function find_by_prefix($prefix) {
$return = array();
foreach ($this->find_all() as $key) {
if (strpos($key, $prefix) === 0) {
$return[] = $key;
}
}
return $return;
}
}

View File

@ -126,6 +126,18 @@ class cache_phpunit_tests extends advanced_testcase {
$cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'phpunit', 'applicationtest');
$this->assertInstanceOf('cache_application', $cache);
$this->run_on_cache($cache);
$instance = cache_config_phpunittest::instance(true);
$instance->phpunit_add_definition('phpunit/test_default_application_cache', array(
'mode' => cache_store::MODE_APPLICATION,
'component' => 'phpunit',
'area' => 'test_default_application_cache',
'persistent' => true,
'persistentmaxsize' => 1
));
$cache = cache::make('phpunit', 'test_default_application_cache');
$this->assertInstanceOf('cache_application', $cache);
$this->run_on_cache($cache);
}
/**
@ -231,7 +243,7 @@ class cache_phpunit_tests extends advanced_testcase {
$this->assertEquals('red_ptc_wfc', $result->property1);
$this->assertEquals('blue_ptc_wfc', $result->property2);
// Test array of objects
// Test array of objects.
$specobject = new cache_phpunit_dummy_object('red', 'blue');
$data = new cacheable_object_array(array(
clone($specobject),
@ -255,6 +267,18 @@ class cache_phpunit_tests extends advanced_testcase {
$this->assertTrue($cache->delete('key1'));
$this->assertTrue($cache->delete('key2'));
$cache->set_many(array(
'key1' => array(1, 2, 3),
'key2' => array(3, 2, 1),
));
$this->assertInternalType('array', $cache->get('key1'));
$this->assertInternalType('array', $cache->get('key2'));
$this->assertCount(3, $cache->get('key1'));
$this->assertCount(3, $cache->get('key2'));
$this->assertInternalType('array', $cache->get_many(array('key1', 'key2')));
$this->assertCount(2, $cache->get_many(array('key1', 'key2')));
$this->assertEquals(2, $cache->delete_many(array('key1', 'key2')));
// Test delete many.
$this->assertTrue($cache->set('key1', 'data1'));
$this->assertTrue($cache->set('key2', 'data2'));
@ -333,6 +357,27 @@ class cache_phpunit_tests extends advanced_testcase {
$this->assertEquals('value', $var2->key);
$this->assertTrue($cache->delete('obj'));
// Test strictness exceptions.
try {
$cache->get('exception', MUST_EXIST);
$this->fail('Exception expected from cache::get using MUST_EXIST');
} catch (Exception $e) {
$this->assertTrue(true);
}
try {
$cache->get_many(array('exception1', 'exception2'), MUST_EXIST);
$this->fail('Exception expected from cache::get_many using MUST_EXIST');
} catch (Exception $e) {
$this->assertTrue(true);
}
$cache->set('test', 'test');
try {
$cache->get_many(array('test', 'exception'), MUST_EXIST);
$this->fail('Exception expected from cache::get_many using MUST_EXIST');
} catch (Exception $e) {
$this->assertTrue(true);
}
}
/**
@ -355,13 +400,25 @@ class cache_phpunit_tests extends advanced_testcase {
$this->assertTrue($cache->purge());
// It won't be there yet.
$this->assertFalse($cache->has('Test'));
// It should load it ;).
$this->assertTrue($cache->has('Test', true));
// Purge it to be sure.
$this->assertTrue($cache->purge());
$this->assertEquals('Test has no value really.', $cache->get('Test'));
// Test multiple values.
$this->assertTrue($cache->purge());
$this->assertTrue($cache->set('b', 'B'));
$result = $cache->get_many(array('a', 'b', 'c'));
$this->assertInternalType('array', $result);
$this->assertCount(3, $result);
$this->assertArrayHasKey('a', $result);
$this->assertArrayHasKey('b', $result);
$this->assertArrayHasKey('c', $result);
$this->assertEquals('a has no value really.', $result['a']);
$this->assertEquals('B', $result['b']);
$this->assertEquals('c has no value really.', $result['c']);
}
/**
@ -433,7 +490,10 @@ class cache_phpunit_tests extends advanced_testcase {
$this->assertEquals('test data 2', $cache->get('2'));
}
public function test_definition_ttl() {
/**
* Test a negative TTL on an application cache.
*/
public function test_application_ttl_negative() {
$instance = cache_config_phpunittest::instance(true);
$instance->phpunit_add_definition('phpunit/ttltest', array(
'mode' => cache_store::MODE_APPLICATION,
@ -454,6 +514,127 @@ class cache_phpunit_tests extends advanced_testcase {
$this->assertFalse($cache->has('Test'));
// Double check by trying to get it.
$this->assertFalse($cache->get('Test'));
// Test with multiple keys.
$this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
$result = $cache->get_many(array('a', 'b', 'c'));
$this->assertInternalType('array', $result);
$this->assertCount(3, $result);
$this->assertArrayHasKey('a', $result);
$this->assertArrayHasKey('b', $result);
$this->assertArrayHasKey('c', $result);
$this->assertFalse($result['a']);
$this->assertFalse($result['b']);
$this->assertFalse($result['c']);
// Test with multiple keys including missing ones.
$result = $cache->get_many(array('a', 'c', 'e'));
$this->assertInternalType('array', $result);
$this->assertCount(3, $result);
$this->assertArrayHasKey('a', $result);
$this->assertArrayHasKey('c', $result);
$this->assertArrayHasKey('e', $result);
$this->assertFalse($result['a']);
$this->assertFalse($result['c']);
$this->assertFalse($result['e']);
}
/**
* Test a positive TTL on an application cache.
*/
public function test_application_ttl_positive() {
$instance = cache_config_phpunittest::instance(true);
$instance->phpunit_add_definition('phpunit/ttltest', array(
'mode' => cache_store::MODE_APPLICATION,
'component' => 'phpunit',
'area' => 'ttltest',
'ttl' => 86400 // Set to a day in the future to be extra sure.
));
$cache = cache::make('phpunit', 'ttltest');
$this->assertInstanceOf('cache_application', $cache);
// Purge it to be sure.
$this->assertTrue($cache->purge());
// It won't be there yet.
$this->assertFalse($cache->has('Test'));
// Set it now.
$this->assertTrue($cache->set('Test', 'Test'));
// Check its there.
$this->assertTrue($cache->has('Test'));
// Double check by trying to get it.
$this->assertEquals('Test', $cache->get('Test'));
// Test with multiple keys.
$this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
$result = $cache->get_many(array('a', 'b', 'c'));
$this->assertInternalType('array', $result);
$this->assertCount(3, $result);
$this->assertArrayHasKey('a', $result);
$this->assertArrayHasKey('b', $result);
$this->assertArrayHasKey('c', $result);
$this->assertEquals('A', $result['a']);
$this->assertEquals('B', $result['b']);
$this->assertEquals('C', $result['c']);
// Test with multiple keys including missing ones.
$result = $cache->get_many(array('a', 'c', 'e'));
$this->assertInternalType('array', $result);
$this->assertCount(3, $result);
$this->assertArrayHasKey('a', $result);
$this->assertArrayHasKey('c', $result);
$this->assertArrayHasKey('e', $result);
$this->assertEquals('A', $result['a']);
$this->assertEquals('C', $result['c']);
$this->assertEquals(false, $result['e']);
}
/**
* Test a negative TTL on an session cache.
*/
public function test_session_ttl_positive() {
$instance = cache_config_phpunittest::instance(true);
$instance->phpunit_add_definition('phpunit/ttltest', array(
'mode' => cache_store::MODE_SESSION,
'component' => 'phpunit',
'area' => 'ttltest',
'ttl' => 86400 // Set to a day in the future to be extra sure.
));
$cache = cache::make('phpunit', 'ttltest');
$this->assertInstanceOf('cache_session', $cache);
// Purge it to be sure.
$this->assertTrue($cache->purge());
// It won't be there yet.
$this->assertFalse($cache->has('Test'));
// Set it now.
$this->assertTrue($cache->set('Test', 'Test'));
// Check its there.
$this->assertTrue($cache->has('Test'));
// Double check by trying to get it.
$this->assertEquals('Test', $cache->get('Test'));
// Test with multiple keys.
$this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
$result = $cache->get_many(array('a', 'b', 'c'));
$this->assertInternalType('array', $result);
$this->assertCount(3, $result);
$this->assertArrayHasKey('a', $result);
$this->assertArrayHasKey('b', $result);
$this->assertArrayHasKey('c', $result);
$this->assertEquals('A', $result['a']);
$this->assertEquals('B', $result['b']);
$this->assertEquals('C', $result['c']);
// Test with multiple keys including missing ones.
$result = $cache->get_many(array('a', 'c', 'e'));
$this->assertInternalType('array', $result);
$this->assertCount(3, $result);
$this->assertArrayHasKey('a', $result);
$this->assertArrayHasKey('c', $result);
$this->assertArrayHasKey('e', $result);
$this->assertEquals('A', $result['a']);
$this->assertEquals('C', $result['c']);
$this->assertEquals(false, $result['e']);
}
/**
@ -520,6 +701,42 @@ class cache_phpunit_tests extends advanced_testcase {
$this->assertFalse($cache->get('testkey2'));
}
/**
* Tests session cache event invalidation
*/
public function test_session_event_invalidation() {
$instance = cache_config_phpunittest::instance();
$instance->phpunit_add_definition('phpunit/test_session_event_invalidation', array(
'mode' => cache_store::MODE_SESSION,
'component' => 'phpunit',
'area' => 'test_session_event_invalidation',
'invalidationevents' => array(
'crazyevent'
)
));
$cache = cache::make('phpunit', 'test_session_event_invalidation');
$this->assertInstanceOf('cache_session', $cache);
$this->assertTrue($cache->set('testkey1', 'test data 1'));
$this->assertEquals('test data 1', $cache->get('testkey1'));
$this->assertTrue($cache->set('testkey2', 'test data 2'));
$this->assertEquals('test data 2', $cache->get('testkey2'));
// Test invalidating a single entry.
cache_helper::invalidate_by_event('crazyevent', array('testkey1'));
$this->assertFalse($cache->get('testkey1'));
$this->assertEquals('test data 2', $cache->get('testkey2'));
$this->assertTrue($cache->set('testkey1', 'test data 1'));
// Test invalidating both entries.
cache_helper::invalidate_by_event('crazyevent', array('testkey1', 'testkey2'));
$this->assertFalse($cache->get('testkey1'));
$this->assertFalse($cache->get('testkey2'));
}
/**
* Tests application cache definition invalidation
*/
@ -556,6 +773,45 @@ class cache_phpunit_tests extends advanced_testcase {
$this->assertFalse($cache->get('testkey2'));
}
/**
* Tests session cache definition invalidation
*/
public function test_session_definition_invalidation() {
$instance = cache_config_phpunittest::instance();
$instance->phpunit_add_definition('phpunit/test_session_definition_invalidation', array(
'mode' => cache_store::MODE_SESSION,
'component' => 'phpunit',
'area' => 'test_session_definition_invalidation'
));
$cache = cache::make('phpunit', 'test_session_definition_invalidation');
$this->assertInstanceOf('cache_session', $cache);
$this->assertTrue($cache->set('testkey1', 'test data 1'));
$this->assertEquals('test data 1', $cache->get('testkey1'));
$this->assertTrue($cache->set('testkey2', 'test data 2'));
$this->assertEquals('test data 2', $cache->get('testkey2'));
cache_helper::invalidate_by_definition('phpunit', 'test_session_definition_invalidation', array(), 'testkey1');
$this->assertFalse($cache->get('testkey1'));
$this->assertEquals('test data 2', $cache->get('testkey2'));
$this->assertTrue($cache->set('testkey1', 'test data 1'));
cache_helper::invalidate_by_definition('phpunit', 'test_session_definition_invalidation', array(),
array('testkey1'));
$this->assertFalse($cache->get('testkey1'));
$this->assertEquals('test data 2', $cache->get('testkey2'));
$this->assertTrue($cache->set('testkey1', 'test data 1'));
cache_helper::invalidate_by_definition('phpunit', 'test_session_definition_invalidation', array(),
array('testkey1', 'testkey2'));
$this->assertFalse($cache->get('testkey1'));
$this->assertFalse($cache->get('testkey2'));
}
/**
* Tests application cache event invalidation over a distributed setup.
*/
@ -865,6 +1121,34 @@ class cache_phpunit_tests extends advanced_testcase {
$this->assertFalse($cache->get('test'));
$this->assertTrue($cache->set('test', 'test'));
$this->assertEquals('test', $cache->get('test'));
$this->assertTrue($cache->delete('test'));
$this->assertFalse($cache->get('test'));
$this->assertTrue($cache->set('test', 'test'));
$this->assertTrue($cache->purge());
$this->assertFalse($cache->get('test'));
// Test the many commands.
$this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
$result = $cache->get_many(array('a', 'b', 'c'));
$this->assertInternalType('array', $result);
$this->assertCount(3, $result);
$this->assertArrayHasKey('a', $result);
$this->assertArrayHasKey('b', $result);
$this->assertArrayHasKey('c', $result);
$this->assertEquals('A', $result['a']);
$this->assertEquals('B', $result['b']);
$this->assertEquals('C', $result['c']);
$this->assertEquals($result, $cache->get_many(array('a', 'b', 'c')));
$this->assertEquals(2, $cache->delete_many(array('a', 'c')));
$result = $cache->get_many(array('a', 'b', 'c'));
$this->assertInternalType('array', $result);
$this->assertCount(3, $result);
$this->assertArrayHasKey('a', $result);
$this->assertArrayHasKey('b', $result);
$this->assertArrayHasKey('c', $result);
$this->assertFalse($result['a']);
$this->assertEquals('B', $result['b']);
$this->assertFalse($result['c']);
}
/**
@ -895,6 +1179,76 @@ class cache_phpunit_tests extends advanced_testcase {
$this->assertEquals(false, $cache->get('var'));
}
/**
* Test switching users with session caches.
*/
public function test_session_cache_switch_user_application_mapping() {
$this->resetAfterTest(true);
$instance = cache_config_phpunittest::instance(true);
$instance->phpunit_add_file_store('testfilestore');
$instance->phpunit_add_definition('phpunit/testappsession', array(
'mode' => cache_store::MODE_SESSION,
'component' => 'phpunit',
'area' => 'testappsession'
));
$instance->phpunit_add_definition_mapping('phpunit/testappsession', 'testfilestore', 3);
$cache = cache::make('phpunit', 'testappsession');
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
// Log in as the first user.
$this->setUser($user1);
$sesskey1 = sesskey();
// Set a basic value in the cache.
$cache->set('var', 1);
$this->assertTrue($cache->has('var'));
$this->assertEquals(1, $cache->get('var'));
// Change to the second user.
$this->setUser($user2);
$sesskey2 = sesskey();
// Make sure the cache doesn't give us the data for the last user.
$this->assertNotEquals($sesskey1, $sesskey2);
$this->assertFalse($cache->has('var'));
$this->assertEquals(false, $cache->get('var'));
}
/**
* Test two session caches being used at once to confirm collisions don't occur.
*/
public function test_dual_session_caches() {
$instance = cache_config_phpunittest::instance(true);
$instance->phpunit_add_definition('phpunit/testsess1', array(
'mode' => cache_store::MODE_SESSION,
'component' => 'phpunit',
'area' => 'testsess1'
));
$instance->phpunit_add_definition('phpunit/testsess2', array(
'mode' => cache_store::MODE_SESSION,
'component' => 'phpunit',
'area' => 'testsess2'
));
$cache1 = cache::make('phpunit', 'testsess1');
$cache2 = cache::make('phpunit', 'testsess2');
$this->assertFalse($cache1->has('test'));
$this->assertFalse($cache2->has('test'));
$this->assertTrue($cache1->set('test', '1'));
$this->assertTrue($cache1->has('test'));
$this->assertFalse($cache2->has('test'));
$this->assertTrue($cache2->set('test', '2'));
$this->assertEquals(1, $cache1->get('test'));
$this->assertEquals(2, $cache2->get('test'));
$this->assertTrue($cache1->delete('test'));
}
/**
* Test multiple session caches when switching user.
*/
@ -925,4 +1279,30 @@ class cache_phpunit_tests extends advanced_testcase {
$this->assertEquals(false, $cache1->get('var'));
$this->assertEquals(false, $cache2->get('var'));
}
/**
* Test application locking.
*/
public function test_application_locking() {
$instance = cache_config_phpunittest::instance(true);
$instance->phpunit_add_definition('phpunit/test_application_locking', array(
'mode' => cache_store::MODE_APPLICATION,
'component' => 'phpunit',
'area' => 'test_application_locking',
'persistent' => true,
'persistentmaxsize' => 1,
'requirelockingread' => true,
'requirelockingwrite' => true
));
$cache = cache::make('phpunit', 'test_application_locking');
$this->assertInstanceOf('cache_application', $cache);
$this->assertTrue($cache->set('a', 'A'));
$this->assertTrue($cache->set('b', 'B'));
$this->assertTrue($cache->set('c', 'C'));
$this->assertEquals('A', $cache->get('a'));
$this->assertEquals(array('b' => 'B', 'c' => 'C'), $cache->get_many(array('b', 'c')));
$this->assertTrue($cache->delete('a'));
$this->assertFalse($cache->has('a'));
}
}

View File

@ -35,6 +35,7 @@ defined('MOODLE_INTERNAL') || die();
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cache_config_phpunittest extends cache_config_writer {
/**
* Adds a definition to the stack
* @param string $area

View File

@ -135,6 +135,7 @@ $string['supports_dataguarantee'] = 'data guarantee';
$string['supports_nativettl'] = 'ttl';
$string['supports_nativelocking'] = 'locking';
$string['supports_keyawareness'] = 'key awareness';
$string['supports_searchable'] = 'searching by key';
$string['tested'] = 'Tested';
$string['testperformance'] = 'Test performance';
$string['unsupportedmode'] = 'Unsupported mode';

View File

@ -453,6 +453,9 @@ function cron_run() {
mtrace('done.');
}
mtrace('Running cache cron routines');
cache_helper::cron();
mtrace('done.');
// Run automated backups if required - these may take a long time to execute
require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php');

View File

@ -2117,5 +2117,13 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2013041601.01);
}
if ($oldversion < 2013041900.00) {
require_once($CFG->dirroot . '/cache/locallib.php');
// The features bin needs updating.
cache_config_writer::update_default_config_stores();
// Main savepoint reached.
upgrade_main_savepoint(true, 2013041900.00);
}
return true;
}