MDL-56675 cachestore_memcached: Disable store if not purgeable
authorAndrew Nicols <andrew@nicols.co.uk>
Wed, 2 Nov 2016 01:41:14 +0000 (09:41 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Wed, 3 May 2017 01:24:23 +0000 (09:24 +0800)
If the store is shared and the getAllKeys function is broken due to an
incompatability between libmemcached and memcached >= 1.4.23, then it is
not possible to purge the cache, and we cannot support the plugin.

This patch adjusts the isready check to additionally check if the
combination of libmemcached and memcached is affected by this issue.

cache/stores/memcached/lib.php
cache/stores/memcached/tests/memcached_test.php

index 56df937..ba7a6d5 100644 (file)
@@ -229,8 +229,56 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
         $version = phpversion('memcached');
         $this->candeletemulti = ($version && version_compare($version, self::REQUIRED_VERSION, '>='));
 
-        // Test the connection to the main connection.
-        $this->isready = @$this->connection->set("ping", 'ping', 1);
+        $this->isready = $this->is_connection_ready();
+    }
+
+    /**
+     * Confirm whether the connection is ready and usable.
+     *
+     * @return boolean
+     */
+    public function is_connection_ready() {
+        if (!@$this->connection->set("ping", 'ping', 1)) {
+            // Test the connection to the server.
+            return false;
+        }
+
+        if ($this->isshared) {
+            // There is a bug in libmemcached which means that it is not possible to purge the cache in a shared cache
+            // configuration.
+            // This issue currently affects:
+            // - memcached 1.4.23+ with php-memcached <= 2.2.0
+            // The following combinations are not affected:
+            // - memcached <= 1.4.22 with any version of php-memcached
+            // - any version of memcached with php-memcached >= 3.0.1
+
+
+            // This check is cheapest as it does not involve connecting to the server at all.
+            $safecombination = false;
+            $extension = new ReflectionExtension('memcached');
+            if ((version_compare($extension->getVersion(), '3.0.1') >= 0)) {
+                // This is php-memcached version >= 3.0.1 which is a safe combination.
+                $safecombination = true;
+            }
+
+            if (!$safecombination && (version_compare($this->connection->getVersion(), '1.4.22') <= 0)) {
+                // This is memcached server version <= 1.4.22 which is a safe combination.
+                $safecombination = true;
+            }
+
+            if (!$safecombination) {
+                // This is memcached 1.4.23+ and php-memcached < 3.0.1.
+                // The issue may have been resolved in a subsequent update to any of the three libraries.
+                // The only way to safely determine if the combination is safe is to call getAllKeys.
+                // A safe combination will return an array, whilst an affected combination will return false.
+                // This is the most expensive check.
+                if (!is_array($this->connection->getAllKeys())) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
     }
 
     /**
index bef5cbe..db41269 100644 (file)
@@ -288,6 +288,10 @@ class cachestore_memcached_test extends cachestore_tests {
 
         $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcached', 'phpunit_test');
         $cachestore = $this->create_test_cache_with_config($definition, array('isshared' => true));
+        if (!$cachestore->is_connection_ready()) {
+            $this->markTestSkipped('Could not test cachestore_memcached. Connection is not ready.');
+        }
+
         $connection = new Memcached(crc32(__METHOD__));
         $connection->addServers($this->get_servers(TEST_CACHESTORE_MEMCACHED_TESTSERVERS));
         $connection->setOptions(array(