mirror of
https://github.com/moodle/moodle.git
synced 2025-04-20 07:56:06 +02:00
MDL-69088 cache: Make file cache store purge async
This commit is contained in:
parent
4f9a539600
commit
18de3388f8
4
cache/stores/file/addinstanceform.php
vendored
4
cache/stores/file/addinstanceform.php
vendored
@ -58,5 +58,9 @@ class cachestore_file_addinstance_form extends cachestore_addinstance_form {
|
||||
$form->addElement('checkbox', 'prescan', get_string('prescan', 'cachestore_file'));
|
||||
$form->setType('prescan', PARAM_BOOL);
|
||||
$form->addHelpButton('prescan', 'prescan', 'cachestore_file');
|
||||
|
||||
$form->addElement('checkbox', 'asyncpurge', get_string('asyncpurge', 'cachestore_file'));
|
||||
$form->setType('asyncpurge', PARAM_BOOL);
|
||||
$form->addHelpButton('asyncpurge', 'asyncpurge', 'cachestore_file');
|
||||
}
|
||||
}
|
53
cache/stores/file/classes/task/asyncpurge.php
vendored
Normal file
53
cache/stores/file/classes/task/asyncpurge.php
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace cachestore_file\task;
|
||||
|
||||
/**
|
||||
* Task deletes old cache revision directory.
|
||||
*
|
||||
* @package cachestore_file
|
||||
* @copyright Catalyst IT Europe Ltd 2021
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @author Jackson D'Souza <jackson.dsouza@catalyst-eu.net>
|
||||
*/
|
||||
class asyncpurge extends \core\task\adhoc_task {
|
||||
|
||||
/**
|
||||
* Executes the scheduled task.
|
||||
*
|
||||
* @return boolean True if old cache revision directory exists and is deleted. False otherwise.
|
||||
*/
|
||||
public function execute(): bool {
|
||||
|
||||
$returnvar = true;
|
||||
$output = 'Cleaning up file store old cache revision directory:' . PHP_EOL;
|
||||
|
||||
$data = $this->get_custom_data();
|
||||
if (is_dir($data->path)) {
|
||||
remove_dir($data->path);
|
||||
$output .= 'Directory deleted: ' . $data->path;
|
||||
} else {
|
||||
$output .= 'Directory not found: ' . $data->path;
|
||||
$returnvar = false;
|
||||
}
|
||||
if (!PHPUNIT_TEST) {
|
||||
mtrace($output);
|
||||
}
|
||||
return $returnvar;
|
||||
}
|
||||
|
||||
}
|
@ -28,6 +28,8 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['asyncpurge'] = 'Asynchronously purge directory';
|
||||
$string['asyncpurge_help'] = 'If enabled, new directory is created with cache revision and old directory will be deleted Asynchronously via schedule task';
|
||||
$string['autocreate'] = 'Auto create directory';
|
||||
$string['autocreate_help'] = 'If enabled the directory specified in path will be automatically created if it does not already exist.';
|
||||
$string['path'] = 'Cache path';
|
||||
@ -45,6 +47,7 @@ It is advisable to only turn this on if the following is true:
|
||||
|
||||
* If you know the number of items in the cache is going to be small enough that it won\'t cause issues on the file system you are running with.
|
||||
* The data being cached is not expensive to generate. If it is then sticking with the default may still be the better option as it reduces the chance of issues.';
|
||||
$string['task_asyncpurge'] = 'Asynchronously purge file store old cache revision directories';
|
||||
|
||||
/**
|
||||
* This is is like the file store, but designed for siutations where:
|
||||
|
68
cache/stores/file/lib.php
vendored
68
cache/stores/file/lib.php
vendored
@ -77,6 +77,13 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
|
||||
*/
|
||||
protected $autocreate = false;
|
||||
|
||||
/**
|
||||
* Set to true if new cache revision directory needs to be created. Old directory will be purged asynchronously
|
||||
* via Schedule task.
|
||||
* @var bool
|
||||
*/
|
||||
protected $asyncpurge = false;
|
||||
|
||||
/**
|
||||
* Set to true if a custom path is being used.
|
||||
* @var bool
|
||||
@ -180,6 +187,12 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
|
||||
// Default: No, we will use multiple directories.
|
||||
$this->singledirectory = false;
|
||||
}
|
||||
// Check if directory needs to be purged asynchronously.
|
||||
if (array_key_exists('asyncpurge', $configuration)) {
|
||||
$this->asyncpurge = (bool)$configuration['asyncpurge'];
|
||||
} else {
|
||||
$this->asyncpurge = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -271,10 +284,25 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
|
||||
* @param cache_definition $definition
|
||||
*/
|
||||
public function initialise(cache_definition $definition) {
|
||||
global $CFG;
|
||||
|
||||
$this->definition = $definition;
|
||||
$hash = preg_replace('#[^a-zA-Z0-9]+#', '_', $this->definition->get_id());
|
||||
$this->path = $this->filestorepath.'/'.$hash;
|
||||
make_writable_directory($this->path, false);
|
||||
|
||||
if ($this->asyncpurge) {
|
||||
$timestampfile = $this->path . '/.lastpurged';
|
||||
if (!file_exists($timestampfile)) {
|
||||
touch($timestampfile);
|
||||
@chmod($timestampfile, $CFG->filepermissions);
|
||||
}
|
||||
$cacherev = gmdate("YmdHis", filemtime($timestampfile));
|
||||
// Update file path with new cache revision.
|
||||
$this->path .= '/' . $cacherev;
|
||||
make_writable_directory($this->path, false);
|
||||
}
|
||||
|
||||
if ($this->prescan && $definition->get_mode() !== self::MODE_REQUEST) {
|
||||
$this->prescan = false;
|
||||
}
|
||||
@ -569,14 +597,38 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
|
||||
* @return boolean True on success. False otherwise.
|
||||
*/
|
||||
public function purge() {
|
||||
global $CFG;
|
||||
if ($this->isready) {
|
||||
$files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
|
||||
if (is_array($files)) {
|
||||
foreach ($files as $filename) {
|
||||
@unlink($filename);
|
||||
// If asyncpurge = true, create a new cache revision directory and adhoc task to delete old directory.
|
||||
if ($this->asyncpurge && isset($this->definition)) {
|
||||
$hash = preg_replace('#[^a-zA-Z0-9]+#', '_', $this->definition->get_id());
|
||||
$filepath = $this->filestorepath . '/' . $hash;
|
||||
$timestampfile = $filepath . '/.lastpurged';
|
||||
if (file_exists($timestampfile)) {
|
||||
$oldcacherev = gmdate("YmdHis", filemtime($timestampfile));
|
||||
$oldcacherevpath = $filepath . '/' . $oldcacherev;
|
||||
// Delete old cache revision file.
|
||||
@unlink($timestampfile);
|
||||
|
||||
// Create adhoc task to delete old cache revision folder.
|
||||
$purgeoldcacherev = new \cachestore_file\task\asyncpurge();
|
||||
$purgeoldcacherev->set_custom_data(['path' => $oldcacherevpath]);
|
||||
\core\task\manager::queue_adhoc_task($purgeoldcacherev);
|
||||
}
|
||||
touch($timestampfile, time());
|
||||
@chmod($timestampfile, $CFG->filepermissions);
|
||||
$newcacherev = gmdate("YmdHis", filemtime($timestampfile));
|
||||
$filepath .= '/' . $newcacherev;
|
||||
make_writable_directory($filepath, false);
|
||||
} else {
|
||||
$files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
|
||||
if (is_array($files)) {
|
||||
foreach ($files as $filename) {
|
||||
@unlink($filename);
|
||||
}
|
||||
}
|
||||
$this->keys = [];
|
||||
}
|
||||
$this->keys = array();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -618,6 +670,9 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
|
||||
if (isset($data->prescan)) {
|
||||
$config['prescan'] = $data->prescan;
|
||||
}
|
||||
if (isset($data->asyncpurge)) {
|
||||
$config['asyncpurge'] = $data->asyncpurge;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
@ -642,6 +697,9 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
|
||||
if (isset($config['prescan'])) {
|
||||
$data['prescan'] = (bool)$config['prescan'];
|
||||
}
|
||||
if (isset($config['asyncpurge'])) {
|
||||
$data['asyncpurge'] = (bool)$config['asyncpurge'];
|
||||
}
|
||||
$editform->set_data($data);
|
||||
}
|
||||
|
||||
|
99
cache/stores/file/tests/asyncpurge_test.php
vendored
Normal file
99
cache/stores/file/tests/asyncpurge_test.php
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace cachestore_file;
|
||||
|
||||
use advanced_testcase;
|
||||
use cache_definition;
|
||||
use cache_store;
|
||||
use cachestore_file;
|
||||
|
||||
/**
|
||||
* Async purge support test for File cache.
|
||||
*
|
||||
* @package cachestore_file
|
||||
* @copyright Catalyst IT Europe Ltd 2021
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @author Jackson D'Souza <jackson.dsouza@catalyst-eu.net>
|
||||
* @coversDefaultClass \cachestore_file
|
||||
*/
|
||||
class asyncpurge_test extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Testing Asynchronous file store cache purge
|
||||
*
|
||||
* @covers ::initialise
|
||||
* @covers ::set
|
||||
* @covers ::get
|
||||
* @covers ::purge
|
||||
*/
|
||||
public function test_cache_async_purge() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Cache definition.
|
||||
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_file', 'phpunit_test');
|
||||
|
||||
// Extra config, set async purge = true.
|
||||
$extraconfig = ['asyncpurge' => true, 'filecacherev' => time()];
|
||||
$configuration = array_merge(cachestore_file::unit_test_configuration(), $extraconfig);
|
||||
$name = 'File async test';
|
||||
|
||||
// Create file cache store.
|
||||
$cache = new cachestore_file($name, $configuration);
|
||||
|
||||
// Initialise file cache store.
|
||||
$cache->initialise($definition);
|
||||
$cache->set('foo', 'bar');
|
||||
$this->assertSame('bar', $cache->get('foo'));
|
||||
|
||||
// Purge this file cache store.
|
||||
$cache->purge();
|
||||
|
||||
// Purging file cache store shouldn't purge the data but create a new cache revision directory.
|
||||
$this->assertSame('bar', $cache->get('foo'));
|
||||
$cache->set('foo', 'bar 2');
|
||||
$this->assertSame('bar 2', $cache->get('foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing Adhoc Cron - deletes old cache revision directory
|
||||
*
|
||||
* @covers \cachestore_file\task
|
||||
*/
|
||||
public function test_cache_async_purge_cron() {
|
||||
global $CFG, $USER;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$tmpdir = realpath($CFG->tempdir);
|
||||
$directorypath = '/cachefile_store';
|
||||
$cacherevdir = $tmpdir . $directorypath;
|
||||
|
||||
// Create cache revision directory.
|
||||
mkdir($cacherevdir, $CFG->directorypermissions, true);
|
||||
|
||||
// Create / execute adhoc task to delete cache revision directory.
|
||||
$asynctask = new cachestore_file\task\asyncpurge();
|
||||
$asynctask->set_blocking(false);
|
||||
$asynctask->set_custom_data(['path' => $cacherevdir]);
|
||||
$asynctask->set_userid($USER->id);
|
||||
\core\task\manager::queue_adhoc_task($asynctask);
|
||||
$asynctask->execute();
|
||||
|
||||
// Check if cache revision directory has been deleted.
|
||||
$this->assertDirectoryDoesNotExist($cacherevdir);
|
||||
}
|
||||
}
|
2
cache/stores/file/version.php
vendored
2
cache/stores/file/version.php
vendored
@ -27,6 +27,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
$plugin->version = 2021052500; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2021052501; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2021052500; // Requires this Moodle version.
|
||||
$plugin->component = 'cachestore_file'; // Full name of the plugin.
|
Loading…
x
Reference in New Issue
Block a user