mirror of
https://github.com/moodle/moodle.git
synced 2025-07-21 14:21:42 +02:00
MDL-48506 cachestore_memcached: is now multi-site safe
This commit is contained in:
committed by
Ryan Wyllie
parent
4f33514063
commit
957e0c7567
82
cache/stores/memcached/lib.php
vendored
82
cache/stores/memcached/lib.php
vendored
@@ -44,6 +44,12 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class cachestore_memcached extends cache_store implements cache_is_configurable {
|
||||
|
||||
/**
|
||||
* The minimum required version of memcached in order to use this store.
|
||||
*/
|
||||
const REQUIRED_VERSION = '2.0.0';
|
||||
|
||||
/**
|
||||
* The name of the store
|
||||
* @var store
|
||||
@@ -104,6 +110,26 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
|
||||
*/
|
||||
protected $setconnections = array();
|
||||
|
||||
/**
|
||||
* The prefix to use on all keys.
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix = '';
|
||||
|
||||
/**
|
||||
* True if Memcached::deleteMulti can be used, false otherwise.
|
||||
* This required extension version 2.0.0 or greater.
|
||||
* @var bool
|
||||
*/
|
||||
protected $candeletemulti = false;
|
||||
|
||||
/**
|
||||
* True if Memcached::getAllKeys can be used, false otherwise.
|
||||
* This required extension version 2.0.0 or greater.
|
||||
* @var bool
|
||||
*/
|
||||
protected $cangetallkeys = false;
|
||||
|
||||
/**
|
||||
* Constructs the store instance.
|
||||
*
|
||||
@@ -171,7 +197,7 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
|
||||
|
||||
$this->options[Memcached::OPT_COMPRESSION] = $compression;
|
||||
$this->options[Memcached::OPT_SERIALIZER] = $serialiser;
|
||||
$this->options[Memcached::OPT_PREFIX_KEY] = $prefix;
|
||||
$this->options[Memcached::OPT_PREFIX_KEY] = $this->prefix = (string)$prefix;
|
||||
$this->options[Memcached::OPT_HASH] = $hashmethod;
|
||||
$this->options[Memcached::OPT_BUFFER_WRITES] = $bufferwrites;
|
||||
|
||||
@@ -196,6 +222,10 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
|
||||
}
|
||||
}
|
||||
|
||||
$version = phpversion('memcached');
|
||||
$this->candeletemulti = ($version && version_compare($version, self::REQUIRED_VERSION, '>='));
|
||||
$this->cangetallkeys = $this->candeletemulti;
|
||||
|
||||
// Test the connection to the main connection.
|
||||
$this->isready = @$this->connection->set("ping", 'ping', 1);
|
||||
}
|
||||
@@ -205,6 +235,7 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
|
||||
*
|
||||
* Once this has been done the cache is all set to be used.
|
||||
*
|
||||
* @throws coding_exception if the instance has already been initialised.
|
||||
* @param cache_definition $definition
|
||||
*/
|
||||
public function initialise(cache_definition $definition) {
|
||||
@@ -238,7 +269,7 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
|
||||
* @return bool
|
||||
*/
|
||||
public static function are_requirements_met() {
|
||||
return class_exists('Memcached');
|
||||
return extension_loaded('memcached') && class_exists('Memcached');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -411,6 +442,18 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
|
||||
*/
|
||||
protected function delete_many_connection(Memcached $connection, array $keys) {
|
||||
$count = 0;
|
||||
if ($this->candeletemulti) {
|
||||
// We can use deleteMulti, this is a bit faster yay!
|
||||
$result = $connection->deleteMulti($keys);
|
||||
foreach ($result as $key => $outcome) {
|
||||
if ($outcome === true) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
// They are running an older version of the php memcached extension.
|
||||
foreach ($keys as $key) {
|
||||
if ($connection->delete($key)) {
|
||||
$count++;
|
||||
@@ -428,16 +471,47 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
|
||||
if ($this->isready) {
|
||||
if ($this->clustered) {
|
||||
foreach ($this->setconnections as $connection) {
|
||||
if ($this->candeletemulti && $this->cangetallkeys) {
|
||||
$keys = self::get_prefixed_keys($connection, $this->prefix);
|
||||
$connection->deleteMulti($keys);
|
||||
} else {
|
||||
// Oh damn, this isn't multi-site safe.
|
||||
$connection->flush();
|
||||
}
|
||||
}
|
||||
} else if ($this->candeletemulti && $this->cangetallkeys) {
|
||||
$keys = self::get_prefixed_keys($this->connection, $this->prefix);
|
||||
$this->connection->deleteMulti($keys);
|
||||
} else {
|
||||
// Oh damn, this isn't multi-site safe.
|
||||
$this->connection->flush();
|
||||
}
|
||||
}
|
||||
|
||||
// It never fails. Ever.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all of the keys in the given connection that belong to this cache store instance.
|
||||
*
|
||||
* Requires php memcached extension version 2.0.0 or greater.
|
||||
* You should always check $this->cangetallkeys before calling this.
|
||||
*
|
||||
* @param Memcached $connection
|
||||
* @param string $prefix
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_prefixed_keys(Memcached $connection, $prefix) {
|
||||
$keys = array();
|
||||
$start = strlen($prefix);
|
||||
foreach ($connection->getAllKeys() as $key) {
|
||||
if (strpos($key, $prefix) === 0) {
|
||||
$keys[] = substr($key, $start);
|
||||
}
|
||||
}
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of options to use as the serialiser.
|
||||
* @return array
|
||||
@@ -585,6 +659,8 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
|
||||
$connection->addServers($this->servers);
|
||||
}
|
||||
}
|
||||
// We have to flush here to be sure we are completely cleaned up.
|
||||
// Bad for performance but this is incredibly rare.
|
||||
@$connection->flush();
|
||||
unset($connection);
|
||||
unset($this->connection);
|
||||
|
80
cache/stores/memcached/tests/memcached_test.php
vendored
80
cache/stores/memcached/tests/memcached_test.php
vendored
@@ -54,6 +54,10 @@ class cachestore_memcached_test extends cachestore_tests {
|
||||
* Tests the valid keys to ensure they work.
|
||||
*/
|
||||
public function test_valid_keys() {
|
||||
if (!cachestore_memcached::are_requirements_met() || !defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
|
||||
$this->markTestSkipped('Could not test cachestore_memcached. Requirements are not met.');
|
||||
}
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcached', 'phpunit_test');
|
||||
@@ -139,16 +143,16 @@ class cachestore_memcached_test extends cachestore_tests {
|
||||
* Tests the clustering feature.
|
||||
*/
|
||||
public function test_clustered() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
if (!defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
|
||||
$this->markTestSkipped();
|
||||
if (!cachestore_memcached::are_requirements_met() || !defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
|
||||
$this->markTestSkipped('Could not test cachestore_memcached. Requirements are not met.');
|
||||
}
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$testservers = explode("\n", trim(TEST_CACHESTORE_MEMCACHED_TESTSERVERS));
|
||||
|
||||
if (count($testservers) < 2) {
|
||||
$this->markTestSkipped();
|
||||
$this->markTestSkipped('Could not test clustered memcached, there are not enough test servers defined.');
|
||||
}
|
||||
|
||||
// Use the first server as our primary.
|
||||
@@ -270,4 +274,70 @@ class cachestore_memcached_test extends cachestore_tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that memcached cache store doesn't just flush everything and instead deletes only what belongs to it.
|
||||
*/
|
||||
public function test_multi_use_compatibility() {
|
||||
if (!cachestore_memcached::are_requirements_met() || !defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
|
||||
$this->markTestSkipped('Could not test cachestore_memcached. Requirements are not met.');
|
||||
}
|
||||
|
||||
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcached', 'phpunit_test');
|
||||
$cachestore = cachestore_memcached::initialise_unit_test_instance($definition);
|
||||
$connection = new Memcached(crc32(__METHOD__));
|
||||
$connection->addServers($this->get_servers(TEST_CACHESTORE_MEMCACHED_TESTSERVERS));
|
||||
$connection->setOptions(array(
|
||||
Memcached::OPT_COMPRESSION => true,
|
||||
Memcached::OPT_SERIALIZER => Memcached::SERIALIZER_PHP,
|
||||
Memcached::OPT_PREFIX_KEY => 'phpunit_',
|
||||
Memcached::OPT_BUFFER_WRITES => false
|
||||
));
|
||||
|
||||
// We must flush first to make sure nothing is there.
|
||||
$connection->flush();
|
||||
|
||||
// Test the cachestore.
|
||||
$this->assertFalse($cachestore->get('test'));
|
||||
$this->assertTrue($cachestore->set('test', 'cachestore'));
|
||||
$this->assertSame('cachestore', $cachestore->get('test'));
|
||||
|
||||
// Test the connection.
|
||||
$this->assertFalse($connection->get('test'));
|
||||
$this->assertEquals(Memcached::RES_NOTFOUND, $connection->getResultCode());
|
||||
$this->assertTrue($connection->set('test', 'connection'));
|
||||
$this->assertSame('connection', $connection->get('test'));
|
||||
|
||||
// Test both again and make sure the values are correct.
|
||||
$this->assertSame('cachestore', $cachestore->get('test'));
|
||||
$this->assertSame('connection', $connection->get('test'));
|
||||
|
||||
// Purge the cachestore and check the connection was not purged.
|
||||
$this->assertTrue($cachestore->purge());
|
||||
$this->assertFalse($cachestore->get('test'));
|
||||
$this->assertSame('connection', $connection->get('test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a server string this returns an array of servers.
|
||||
*
|
||||
* @param string $serverstring
|
||||
* @return array
|
||||
*/
|
||||
public function get_servers($serverstring) {
|
||||
$servers = array();
|
||||
foreach (explode("\n", $serverstring) as $server) {
|
||||
if (!is_array($server)) {
|
||||
$server = explode(':', $server, 3);
|
||||
}
|
||||
if (!array_key_exists(1, $server)) {
|
||||
$server[1] = 11211;
|
||||
$server[2] = 100;
|
||||
} else if (!array_key_exists(2, $server)) {
|
||||
$server[2] = 100;
|
||||
}
|
||||
$servers[] = $server;
|
||||
}
|
||||
return $servers;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user