MDL-67020 Cache: Make locking work for local file caches

* Add new requirelockingbeforewrite flag for cache definitions
* Implement native locking in cachestore_file (leveraging
file_lock_factory).
This commit is contained in:
Mark Johnson
2022-10-20 11:31:33 +01:00
parent dcc7d7b8df
commit 045ee091da
12 changed files with 328 additions and 27 deletions
+51 -4
View File
@@ -1581,12 +1581,24 @@ class cache_application extends cache implements cache_loader_with_locking {
*/
protected $requirelockingwrite = false;
/**
* Gets set to true if the cache writes (set|delete) must have a manual lock created first
* @var bool
*/
protected $requirelockingbeforewrite = false;
/**
* Gets set to a cache_store to use for locking if the caches primary store doesn't support locking natively.
* @var cache_lock_interface
*/
protected $cachelockinstance;
/**
* Store a list of locks acquired by this process.
* @var array
*/
protected $locks;
/**
* Overrides the cache construct method.
*
@@ -1603,6 +1615,7 @@ class cache_application extends cache implements cache_loader_with_locking {
$this->requirelocking = true;
$this->requirelockingread = $definition->require_locking_read();
$this->requirelockingwrite = $definition->require_locking_write();
$this->requirelockingbeforewrite = $definition->require_locking_before_write();
}
$this->handle_invalidation_events();
@@ -1648,13 +1661,24 @@ class cache_application extends cache implements cache_loader_with_locking {
* @return bool Returns true if the lock could be acquired, false otherwise.
*/
public function acquire_lock($key) {
global $CFG;
$key = $this->parse_key($key);
$before = microtime(true);
if ($this->nativelocking) {
return $this->get_store()->acquire_lock($key, $this->get_identifier());
$lock = $this->get_store()->acquire_lock($key, $this->get_identifier());
} else {
$this->ensure_cachelock_available();
return $this->cachelockinstance->lock($key, $this->get_identifier());
$lock = $this->cachelockinstance->lock($key, $this->get_identifier());
}
$after = microtime(true);
if ($lock) {
$this->locks[$key] = $lock;
if (defined('MDL_PERF') || !empty($CFG->perfdebug)) {
\core\lock\timing_wrapper_lock_factory::record_lock_data($after, $before,
$this->get_definition()->get_id(), $key, $lock, $this->get_identifier() . $key);
}
}
return $lock;
}
/**
@@ -1666,6 +1690,9 @@ class cache_application extends cache implements cache_loader_with_locking {
*/
public function check_lock_state($key) {
$key = $this->parse_key($key);
if (!empty($this->locks[$key])) {
return true; // Shortcut to save having to make a call to the cache store if the lock is held by this process.
}
if ($this->nativelocking) {
return $this->get_store()->check_lock_state($key, $this->get_identifier());
} else {
@@ -1683,11 +1710,18 @@ class cache_application extends cache implements cache_loader_with_locking {
public function release_lock($key) {
$key = $this->parse_key($key);
if ($this->nativelocking) {
return $this->get_store()->release_lock($key, $this->get_identifier());
$released = $this->get_store()->release_lock($key, $this->get_identifier());
} else {
$this->ensure_cachelock_available();
return $this->cachelockinstance->unlock($key, $this->get_identifier());
$released = $this->cachelockinstance->unlock($key, $this->get_identifier());
}
if ($released && array_key_exists($key, $this->locks)) {
unset($this->locks[$key]);
if (defined('MDL_PERF') || !empty($CFG->perfdebug)) {
\core\lock\timing_wrapper_lock_factory::record_lock_released_data($this->get_identifier() . $key);
}
}
return $released;
}
/**
@@ -1719,6 +1753,10 @@ class cache_application extends cache implements cache_loader_with_locking {
* @return bool True on success, false otherwise.
*/
protected function set_implementation($key, int $version, $data, bool $setparents = true): bool {
if ($this->requirelockingbeforewrite && !$this->check_lock_state($key)) {
throw new coding_exception('Attempted to set cache key "' . $key . '" without a lock. '
. 'Locking before writes is required for ' . $this->get_definition()->get_id());
}
if ($this->requirelockingwrite && !$this->acquire_lock($key)) {
return false;
}
@@ -1753,6 +1791,15 @@ class cache_application extends cache implements cache_loader_with_locking {
* ... if they care that is.
*/
public function set_many(array $keyvaluearray) {
if ($this->requirelockingbeforewrite) {
foreach ($keyvaluearray as $id => $pair) {
if (!$this->check_lock_state($pair['key'])) {
debugging('Attempted to set cache key "' . $pair['key'] . '" without a lock. '
. 'Locking before writes is required for ' . $this->get_definition()->get_name(), DEBUG_DEVELOPER);
unset($keyvaluearray[$id]);
}
}
}
if ($this->requirelockingwrite) {
$locks = array();
foreach ($keyvaluearray as $id => $pair) {