MDL-39117 cachestore_apc: imported into core

This commit is contained in:
Sam Hemelryk 2014-12-03 16:15:56 +13:00 committed by Russell Smith
parent 1f2744851f
commit cb3e2e144a
7 changed files with 736 additions and 0 deletions

62
cache/stores/apc/README.md vendored Normal file
View File

@ -0,0 +1,62 @@
Alternative PHP cache (APC)
===========================
The alternative PHP cache (APC) is an opcode cache for PHP that provides a persistent application data store cache to PHP applications.
This plugin allows the use of the APC data store as a Moodle cache store. In turn allowing you to use APC within Moodle.
Its also important to note that because the APCu API is backwards compatible this store can also be used for APCu.
Installation of APC
-------------------
It is recommended that you read through the APC documentation http://www.php.net/manual/en/book.apc.php before beginning with this plugin.
The above documentation recommends installing the PECL APC extension that can be found at http://pecl.php.net/package/apc.
http://www.php.net/manual/en/install.pecl.php contains information on installing PECL extensions.
Its also worth noting for this those using Linux that there is usually a php5-apc package that can be installed very easily.
If you have installed PHP under Linux through a package manager then this will be by far the easiest way to proceed.
Once installed ensure you restart your web server before proceeding.
Installation within Moodle
--------------------------
Browse to your site and log in as an administrator.
Moodle should detect that a new plugin has been added and will proceed to prompt you through an upgrade process to install it.
The installation of this plugin is very minimal. Once installed you will need to need to create an APC cache store instance within the Moodle administration interfaces.
Making use of APC within Moodle
-------------------------------
Installing this plugin makes APC available to use within Moodle however it does not put it into use.
The first thing you will need to do is create an APC cache store instance.
This is done through the Cache configuration interface.
1. Log in as an Administrator.
2. In the settings block browse to Site Administration > Plugins > Caching > Configuration.
3. Once the page loads locate the APC row within the Installed cache stores table.
4. You should see an "Add instance" link within that row. If not then the APC extension has not being installed correctly.
5. Click "Add instance".
6. Give the new instance a name and click "Save changes". You should be directed back to the configuration page.
7. Locate the Configured cache store instances table and ensure there is now a row for you APC instance and that it has a green tick in the ready column.
Once done you have an APC instance that is ready to be used. The next step is to map definitions to make use of the APC instance.
Locate the known cache definitions table. This table lists the caches being used within Moodle at the moment.
For each cache you should be able to Edit mappings. Find a cache that you would like to map to the APC instance and click Edit mappings.
One the next screen proceed to select your APC instance as the primary cache and save changes.
Back in the known cache definitions table you should now see your APC instance listed under the store mappings for the cache you had selected.
You can proceed to map as many or as few cache definitions to the APC instance as you see fit.
That is it! you are now using APC within Moodle.
Information and advice on using APC within Moodle
-------------------------------------------------
APC provides a shared application cache that is usually very limited in size but provides excellent performance.
It doesn't provide the ability to configure multiple instances of itself and as such within Moodle you are only able to create a single APC cache store instance.
Because of its incredible performance but very limited size it is strongly suggested that you map only small, crucial caches to the APC store.
Another important thing to understand about the APC store is that it provides no garbage cleaning, or storage reclamation facilities. As such cache data will persist there until APC is restarted or the store is purged.
On top of that once the store is full requests to store information within the cache fail until there is once more sufficient space.
Because of this it is recommended that you regularly purge or restart APC.
Also recommended is to map a secondary application cache instance to any definition with the APC mapped. This ensures that if it does indeed full up that an alternative cache is available.

85
cache/stores/apc/addinstanceform.php vendored Normal file
View File

@ -0,0 +1,85 @@
<?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/>.
/**
* The library file for the apc cache store.
*
* This file is part of the apc cache store, it contains the API for interacting with an instance of the store.
*
* @package cachestore_apc
* @copyright 2014 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/cache/forms.php');
/**
* Form for adding a apc instance.
*
* @copyright 2014 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_apc_addinstance_form extends cachestore_addinstance_form {
/**
* Add the desired form elements.
*/
protected function configuration_definition() {
global $CFG;
$form = $this->_form;
$form->addElement('text', 'prefix', get_string('prefix', 'cachestore_apc'),
array('maxlength' => 5, 'size' => 5));
$form->addHelpButton('prefix', 'prefix', 'cachestore_apc');
$form->setType('prefix', PARAM_TEXT); // We set to text but we have a rule to limit to alphanumext.
$form->setDefault('prefix', $CFG->prefix);
$form->addRule('prefix', get_string('prefixinvalid', 'cachestore_apc'), 'regex', '#^[a-zA-Z0-9\-_]+$#');
$form->addElement('header', 'apc_notice', get_string('notice', 'cachestore_apc'));
$form->setExpanded('apc_notice');
$link = get_docs_url('Caching#APC');
$form->addElement('html', nl2br(get_string('clusternotice', 'cachestore_apc', $link)));
}
/**
* Validates the configuration data.
*
* We need to check that prefix is unique.
*
* @param array $data
* @param array $files
* @param array $errors
* @return array
* @throws coding_exception
*/
public function configuration_validation($data, $files, array $errors) {
if (empty($errors['prefix'])) {
$factory = cache_factory::instance();
$config = $factory->create_config_instance();
foreach ($config->get_all_stores() as $store) {
if ($store['plugin'] === 'apc') {
if (isset($store['configuration']['prefix'])) {
if ($data['prefix'] === $store['configuration']['prefix']) {
// The new store has the same prefix as an existing store, thats a problem.
$errors['prefix'] = get_string('prefixnotunique', 'cachestore_apc');
break;
}
} else if (empty($data['prefix'])) {
// The existing store hasn't got a prefix and neither does the new store, that's a problem.
$errors['prefix'] = get_string('prefixnotunique', 'cachestore_apc');
break;
}
}
}
}
return $errors;
}
}

View File

@ -0,0 +1,35 @@
<?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/>.
/**
* APC cache store language strings.
*
* @package cachestore_apc
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['clusternotice'] = 'Please be aware that APC is not a suitable choice on sites running clustered web servers. Use of it in such a setup is highly likely to lead to cached content ending up out of sync with other nodes in the cluster, unless very carefully managed.
We recommend you only make use of APC in situations where a single web server is being used.
For more information see <a href="{$a}">Moodle docs</a>';
$string['notice'] = 'Notice';
$string['pluginname'] = 'Alternative PHP cache (APC)';
$string['prefix'] = 'Prefix';
$string['prefix_help'] = 'The above prefix gets used for all keys being stored in this APC store instance. By default the database prefix is used.';
$string['prefixinvalid'] = 'The prefix you have selected is invalid. You can only use a-z A-Z 0-9-_.';
$string['prefixnotunique'] = 'The prefix you have selected is not unique. Please choose a unique prefix.';
$string['testperformance'] = 'Test performance';
$string['testperformance_desc'] = 'If enabled APC performance will be included when viewing the Test performance page in the administration block. Enabling this on a production site is not recommended.';

390
cache/stores/apc/lib.php vendored Normal file
View File

@ -0,0 +1,390 @@
<?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/>.
/**
* APC cache store main library.
*
* @package cachestore_apc
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* The APC cache store class.
*
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_apc extends cache_store implements cache_is_key_aware, cache_is_configurable {
/**
* The required version of APC for this extension.
*/
const REQUIRED_VERSION = '3.1.1';
/**
* The name of this store instance.
* @var string
*/
protected $name;
/**
* The definition used when this instance was initialised.
* @var cache_definition
*/
protected $definition = null;
/**
* The prefix to use on all keys.
* @var null
*/
protected $prefix = null;
/**
* Static method to check that the APC stores requirements have been met.
*
* It checks that the APC extension has been loaded and that it has been enabled.
*
* @return bool True if the stores software/hardware requirements have been met and it can be used. False otherwise.
*/
public static function are_requirements_met() {
if (!extension_loaded('apc') || // APC PHP extension is not available.
!ini_get('apc.enabled') // APC is not enabled.
) {
return false;
}
$version = phpversion('apc');
return $version && version_compare($version, self::REQUIRED_VERSION, '>=');
}
/**
* Static method to check if a store is usable with the given mode.
*
* @param int $mode One of cache_store::MODE_*
* @return bool True if the mode is supported.
*/
public static function is_supported_mode($mode) {
return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION);
}
/**
* Returns the supported features as a binary flag.
*
* @param array $configuration The configuration of a store to consider specifically.
* @return int The supported features.
*/
public static function get_supported_features(array $configuration = array()) {
return self::SUPPORTS_DATA_GUARANTEE + self::SUPPORTS_NATIVE_TTL;
}
/**
* Returns the supported modes as a binary flag.
*
* @param array $configuration The configuration of a store to consider specifically.
* @return int The supported modes.
*/
public static function get_supported_modes(array $configuration = array()) {
return self::MODE_APPLICATION + self::MODE_SESSION;
}
/**
* Constructs an instance of the cache store.
*
* This method should not create connections or perform and processing, it should be used
*
* @param string $name The name of the cache store
* @param array $configuration The configuration for this store instance.
*/
public function __construct($name, array $configuration = array()) {
global $CFG;
$this->name = $name;
$this->prefix = $CFG->prefix;
if (isset($configuration['prefix'])) {
$this->prefix = $configuration['prefix'];
}
}
/**
* Returns the name of this store instance.
* @return string
*/
public function my_name() {
return $this->name;
}
/**
* Initialises a new instance of the cache store given the definition the instance is to be used for.
*
* This function should prepare any given connections etc.
*
* @param cache_definition $definition
* @return bool
*/
public function initialise(cache_definition $definition) {
$this->definition = $definition;
$this->prefix = $definition->generate_definition_hash().'__';
return true;
}
/**
* Returns true if this cache store instance has been initialised.
* @return bool
*/
public function is_initialised() {
return ($this->definition !== null);
}
/**
* Returns true if this cache store instance is ready to use.
* @return bool
*/
public function is_ready() {
// No set up is actually required, providing apc is installed and enabled.
return true;
}
/**
* Prepares the given key for use.
*
* Should be called before all interaction.
*
* @return string
*/
protected function prepare_key($key) {
return $this->prefix . $key;
}
/**
* Retrieves an item from the cache store given its key.
*
* @param string $key The key to retrieve
* @return mixed The data that was associated with the key, or false if the key did not exist.
*/
public function get($key) {
$key = $this->prepare_key($key);
$success = false;
$outcome = apc_fetch($key, $success);
if ($success) {
return $outcome;
}
return $success;
}
/**
* Retrieves several items from the cache store in a single transaction.
*
* If not all of the items are available in the cache then the data value for those that are missing will be set to false.
*
* @param array $keys The array of keys to retrieve
* @return array An array of items from the cache. There will be an item for each key, those that were not in the store will
* be set to false.
*/
public function get_many($keys) {
$map = array();
foreach ($keys as $key) {
$map[$key] = $this->prepare_key($key);
}
$outcomes = array();
$success = false;
$results = apc_fetch($map, $success);
foreach ($map as $key => $used) {
if ($success && array_key_exists($used, $results) && !empty($results[$used])) {
$outcomes[$key] = $results[$used];
} else {
$outcomes[$key] = false;
}
}
return $outcomes;
}
/**
* Sets an item in the cache given its key and data value.
*
* @param string $key The key to use.
* @param mixed $data The data to set.
* @return bool True if the operation was a success false otherwise.
*/
public function set($key, $data) {
$key = $this->prepare_key($key);
return apc_store($key, $data, $this->definition->get_ttl());
}
/**
* Sets many items in the cache in a single transaction.
*
* @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
* keys, 'key' and 'value'.
* @return int The number of items successfully set. It is up to the developer to check this matches the number of items
* sent ... if they care that is.
*/
public function set_many(array $keyvaluearray) {
$map = array();
foreach ($keyvaluearray as $pair) {
$key = $this->prepare_key($pair['key']);
$map[$key] = $pair['value'];
}
$result = apc_store($map, null, $this->definition->get_ttl());
return count($map) - count($result);
}
/**
* Deletes an item from the cache store.
*
* @param string $key The key to delete.
* @return bool Returns true if the operation was a success, false otherwise.
*/
public function delete($key) {
$key = $this->prepare_key($key);
return apc_delete($key);
}
/**
* Deletes several keys from the cache in a single action.
*
* @param array $keys The keys to delete
* @return int The number of items successfully deleted.
*/
public function delete_many(array $keys) {
$count = 0;
foreach ($keys as $key) {
if ($this->delete($key)) {
$count++;
}
}
return $count;
}
/**
* Purges the cache deleting all items within it.
*
* @return boolean True on success. False otherwise.
*/
public function purge() {
$iterator = new APCIterator('user', '#^' . preg_quote($this->prefix, '#') . '#');
return apc_delete($iterator);
}
/**
* Performs any necessary clean up when the store instance is being deleted.
*/
public function cleanup() {
$this->purge();
}
/**
* Generates an instance of the cache store that can be used for testing.
*
* Returns an instance of the cache store, or false if one cannot be created.
*
* @param cache_definition $definition
* @return cache_store
*/
public static function initialise_test_instance(cache_definition $definition) {
$testperformance = get_config('cachestore_apc', 'testperformance');
if (empty($testperformance)) {
return false;
}
if (!self::are_requirements_met()) {
return false;
}
$name = 'APC test';
$cache = new cachestore_apc($name);
$cache->initialise($definition);
return $cache;
}
/**
* Test is a cache has a key.
*
* @param string|int $key
* @return bool True if the cache has the requested key, false otherwise.
*/
public function has($key) {
return apc_exists($key);
}
/**
* Test if a cache has at least one of the given keys.
*
* @param array $keys
* @return bool True if the cache has at least one of the given keys
*/
public function has_any(array $keys) {
$result = apc_exists($keys);
return count($result) > 0;
}
/**
* Test is a cache has all of the given keys.
*
* @param array $keys
* @return bool True if the cache has all of the given keys, false otherwise.
*/
public function has_all(array $keys) {
$result = apc_exists($keys);
return count($result) === count($keys);
}
/**
* Generates an instance of the cache store that can be used for testing.
*
* @param cache_definition $definition
* @return cachestore_apc|false
*/
public static function initialise_unit_test_instance(cache_definition $definition) {
if (!self::are_requirements_met()) {
return false;
}
if (!defined('TEST_CACHESTORE_APC')) {
return false;
}
$store = new cachestore_apc('Test APC', array('prefix' => 'phpunit'));
if (!$store->is_ready()) {
return false;
}
$store->initialise($definition);
return $store;
}
/**
* Given the data from the add instance form this function creates a configuration array.
*
* @param stdClass $data
* @return array
*/
public static function config_get_configuration_array($data) {
return array(
'prefix' => $data->prefix,
);
}
/**
* Allows the cache store to set its data against the edit form before it is shown to the user.
*
* @param moodleform $editform
* @param array $config
*/
public static function config_set_edit_form_data(moodleform $editform, array $config) {
if (isset($config['prefix'])) {
$data['prefix'] = $config['prefix'];
} else {
$data['prefix'] = '';
}
$editform->set_data($data);
}
}

36
cache/stores/apc/settings.php vendored Normal file
View File

@ -0,0 +1,36 @@
<?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/>.
/**
* The settings for the APC store.
*
* This file is part of the APC cache store, it contains the API for interacting with an instance of the store.
*
* @package cachestore_apc
* @copyright 2014 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$settings->add(
new admin_setting_configcheckbox(
'cachestore_apc/testperformance',
new lang_string('testperformance', 'cachestore_apc'),
new lang_string('testperformance_desc', 'cachestore_apc'),
false
)
);

98
cache/stores/apc/tests/apc_test.php vendored Normal file
View File

@ -0,0 +1,98 @@
<?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/>.
/**
* APC unit tests.
*
* If you wish to use these unit tests all you need to do is add the following definition to
* your config.php file.
*
* define('TEST_CACHESTORE_XCACHE', true);
*
* @package cachestore_apc
* @copyright 2014 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
// Include the necessary evils.
global $CFG;
require_once($CFG->dirroot.'/cache/tests/fixtures/stores.php');
require_once($CFG->dirroot.'/cache/stores/apc/lib.php');
/**
* APC unit test class.
*
* @package cachestore_apc
* @copyright 2014 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_apc_test extends cachestore_tests {
/**
* Returns the apc class name
* @return string
*/
protected function get_class_name() {
return 'cachestore_apc';
}
/**
* Test purging the apc cache store.
*/
public function test_purge() {
if (!cachestore_apc::are_requirements_met() || !defined('TEST_CACHESTORE_APC')) {
$this->markTestSkipped('Could not test cachestore_apc. Requirements are not met.');
}
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_apc', 'phpunit_test');
$instance = cachestore_apc::initialise_unit_test_instance($definition);
// Test a simple purge return.
$this->assertTrue($instance->purge());
// Test purge works.
$this->assertTrue($instance->set('test', 'monster'));
$this->assertSame('monster', $instance->get('test'));
$this->assertTrue($instance->purge());
$this->assertFalse($instance->get('test'));
}
/**
* Test that the Moodle APC store doesn't cross paths with other code using APC as well.
*/
public function test_cross_application_interaction() {
if (!cachestore_apc::are_requirements_met() || !defined('TEST_CACHESTORE_APC')) {
$this->markTestSkipped('Could not test cachestore_apc. Requirements are not met.');
}
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_apc', 'phpunit_test');
$instance = cachestore_apc::initialise_unit_test_instance($definition);
// Test purge with custom data.
$this->assertTrue($instance->set('test', 'monster'));
$this->assertSame('monster', $instance->get('test'));
$this->assertTrue(apc_store('test', 'pirate', 180));
$this->assertSame('monster', $instance->get('test'));
$this->assertTrue(apc_exists('test'));
$this->assertSame('pirate', apc_fetch('test'));
// Purge and check that our data is gone but the the custom data is still there.
$this->assertTrue($instance->purge());
$this->assertFalse($instance->get('test'));
$this->assertTrue(apc_exists('test'));
$this->assertSame('pirate', apc_fetch('test'));
}
}

30
cache/stores/apc/version.php vendored Normal file
View File

@ -0,0 +1,30 @@
<?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/>.
/**
* APC cache store version information.
*
* @package cachestore_apc
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$plugin->version = 2014111400;
$plugin->requires = 2014111300;
$plugin->maturity = MATURITY_STABLE;
$plugin->component = 'cachestore_apc';