Merge branch 'MDL-76076_master' of https://github.com/marxjohnson/moodle

This commit is contained in:
Andrew Nicols
2022-11-01 10:47:14 +08:00
2 changed files with 105 additions and 4 deletions
+25 -4
View File
@@ -607,11 +607,18 @@ class cache implements cache_loader {
// 6. Set it to the store if we got it from the loader/datasource. Only set to this direct
// store; parent method will have set it to all stores if needed.
if ($setaftervalidation) {
$lock = null;
if (!empty($this->requirelockingbeforewrite)) {
$lock = $this->acquire_lock($key);
}
if ($requiredversion === self::VERSION_NONE) {
$this->set_implementation($key, self::VERSION_NONE, $result, false);
} else {
$this->set_implementation($key, $actualversion, $result, false);
}
if ($lock) {
$this->release_lock($key);
}
}
// 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.
@@ -719,9 +726,16 @@ class cache implements cache_loader {
}
foreach ($resultmissing as $key => $value) {
$result[$keysparsed[$key]] = $value;
$lock = null;
if (!empty($this->requirelockingbeforewrite)) {
$lock = $this->acquire_lock($key);
}
if ($value !== false) {
$this->set($key, $value);
}
if ($lock) {
$this->release_lock($key);
}
}
unset($resultmissing);
}
@@ -1662,6 +1676,9 @@ class cache_application extends cache implements cache_loader_with_locking {
*/
public function acquire_lock($key) {
global $CFG;
if ($this->get_loader() !== false) {
$this->get_loader()->acquire_lock($key);
}
$key = $this->parse_key($key);
$before = microtime(true);
if ($this->nativelocking) {
@@ -1708,6 +1725,7 @@ class cache_application extends cache implements cache_loader_with_locking {
* @return bool True if the operation succeeded, false otherwise.
*/
public function release_lock($key) {
$loaderkey = $key;
$key = $this->parse_key($key);
if ($this->nativelocking) {
$released = $this->get_store()->release_lock($key, $this->get_identifier());
@@ -1721,6 +1739,9 @@ class cache_application extends cache implements cache_loader_with_locking {
\core\lock\timing_wrapper_lock_factory::record_lock_released_data($this->get_identifier() . $key);
}
}
if ($this->get_loader() !== false) {
$this->get_loader()->release_lock($loaderkey);
}
return $released;
}
@@ -1792,11 +1813,11 @@ class cache_application extends cache implements cache_loader_with_locking {
*/
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. '
foreach ($keyvaluearray as $key => $value) {
if (!$this->check_lock_state($key)) {
debugging('Attempted to set cache key "' . $key . '" without a lock. '
. 'Locking before writes is required for ' . $this->get_definition()->get_name(), DEBUG_DEVELOPER);
unset($keyvaluearray[$id]);
unset($keyvaluearray[$key]);
}
}
}
+80
View File
@@ -2160,6 +2160,86 @@ class cache_test extends \advanced_testcase {
cache::make('phpunit', 'test_application_locking');
}
/**
* Test that locking before write works when writing across multiple layers.
*
* @covers \cache_loader
* @return void
*/
public function test_application_locking_multiple_layers() {
$instance = cache_config_testing::instance(true);
$instance->phpunit_add_definition('phpunit/test_application_locking', array(
'mode' => cache_store::MODE_APPLICATION,
'component' => 'phpunit',
'area' => 'test_application_locking',
'staticacceleration' => true,
'staticaccelerationsize' => 1,
'requirelockingbeforewrite' => true
), false);
$instance->phpunit_add_file_store('phpunittest1');
$instance->phpunit_add_file_store('phpunittest2');
$instance->phpunit_add_definition_mapping('phpunit/test_application_locking', 'phpunittest1', 1);
$instance->phpunit_add_definition_mapping('phpunit/test_application_locking', 'phpunittest2', 2);
$cache = cache::make('phpunit', 'test_application_locking');
$this->assertInstanceOf(cache_application::class, $cache);
// Check that we can set a key across multiple layers.
$cache->acquire_lock('a');
$this->assertTrue($cache->set('a', 'A'));
$cache->release_lock('a');
// Delete from the current layer.
$cache->delete('a', false);
// Check that we can get the value from the deeper layer, which will also re-set it in the current one.
$this->assertEquals('A', $cache->get('a'));
// Try set/delete/get_many.
$cache->acquire_lock('x');
$cache->acquire_lock('y');
$cache->acquire_lock('z');
$this->assertEquals(3, $cache->set_many(['x' => 'X', 'y' => 'Y', 'z' => 'Z']));
$cache->release_lock('x');
$cache->release_lock('y');
$cache->release_lock('z');
$cache->delete_many(['x', 'y', 'z'], false);
$this->assertEquals(['x' => 'X', 'y' => 'Y', 'z' => 'Z'], $cache->get_many(['x', 'y', 'z']));
$cache->purge();
// Try the tests again with a third layer.
$instance->phpunit_add_file_store('phpunittest3');
$instance->phpunit_add_definition_mapping('phpunit/test_application_locking', 'phpunittest3', 3);
$cache = cache::make('phpunit', 'test_application_locking');
$this->assertInstanceOf(cache_application::class, $cache);
// Check that we can set a key across multiple layers.
$cache->acquire_lock('a');
$this->assertTrue($cache->set('a', 'A'));
$cache->release_lock('a');
// Delete from the current layer.
$cache->delete('a', false);
// Check that we can get the value from the deeper layer, which will also re-set it in the current one.
$this->assertEquals('A', $cache->get('a'));
// Try set/delete/get_many.
$cache->acquire_lock('x');
$cache->acquire_lock('y');
$cache->acquire_lock('z');
$this->assertEquals(3, $cache->set_many(['x' => 'X', 'y' => 'Y', 'z' => 'Z']));
$cache->release_lock('x');
$cache->release_lock('y');
$cache->release_lock('z');
$cache->delete_many(['x', 'y', 'z'], false);
$this->assertEquals(['x' => 'X', 'y' => 'Y', 'z' => 'Z'], $cache->get_many(['x', 'y', 'z']));
}
/**
* Test the static cache_helper method purge_stores_used_by_definition.
*/