From e2e109b8c16f5941b3c3624a50a91335c6172b86 Mon Sep 17 00:00:00 2001 From: Philipp Holzer Date: Thu, 15 Aug 2019 13:58:01 +0200 Subject: [PATCH] Fix getAllKeys() method for memcache instances --- src/Core/Cache/ArrayCache.php | 2 +- src/Core/Cache/Cache.php | 8 +-- src/Core/Cache/MemcachedCache.php | 106 +++++++++++++++++++++++++++-- src/Core/Lock/DatabaseLock.php | 2 +- tests/src/Core/Cache/CacheTest.php | 7 +- 5 files changed, 108 insertions(+), 17 deletions(-) diff --git a/src/Core/Cache/ArrayCache.php b/src/Core/Cache/ArrayCache.php index 5add98cc2..c6f3983ee 100644 --- a/src/Core/Cache/ArrayCache.php +++ b/src/Core/Cache/ArrayCache.php @@ -21,7 +21,7 @@ class ArrayCache extends Cache implements IMemoryCache */ public function getAllKeys($prefix = null) { - return $this->filterArrayKeysByPrefix($this->cachedData, $prefix); + return $this->filterArrayKeysByPrefix(array_keys($this->cachedData), $prefix); } /** diff --git a/src/Core/Cache/Cache.php b/src/Core/Cache/Cache.php index b40c129ae..cf5b15d05 100644 --- a/src/Core/Cache/Cache.php +++ b/src/Core/Cache/Cache.php @@ -84,19 +84,19 @@ abstract class Cache implements ICache * Filters the keys of an array with a given prefix * Returns the filtered keys as an new array * - * @param array $array The array, which should get filtered + * @param array $keys The keys, which should get filtered * @param string|null $prefix The prefix (if null, all keys will get returned) * * @return array The filtered array with just the keys */ - protected function filterArrayKeysByPrefix($array, $prefix = null) + protected function filterArrayKeysByPrefix(array $keys, string $prefix = null) { if (empty($prefix)) { - return array_keys($array); + return $keys; } else { $result = []; - foreach (array_keys($array) as $key) { + foreach ($keys as $key) { if (strpos($key, $prefix) === 0) { array_push($result, $key); } diff --git a/src/Core/Cache/MemcachedCache.php b/src/Core/Cache/MemcachedCache.php index ac0648a6c..89685c3f2 100644 --- a/src/Core/Cache/MemcachedCache.php +++ b/src/Core/Cache/MemcachedCache.php @@ -27,6 +27,17 @@ class MemcachedCache extends Cache implements IMemoryCache */ private $logger; + /** + * @var string First server address + */ + + private $firstServer; + + /** + * @var int First server port + */ + private $firstPort; + /** * Due to limitations of the INI format, the expected configuration for Memcached servers is the following: * array { @@ -58,6 +69,9 @@ class MemcachedCache extends Cache implements IMemoryCache } }); + $this->firstServer = $memcached_hosts[0][0] ?? 'localhost'; + $this->firstPort = $memcached_hosts[0][1] ?? 11211; + $this->memcached->addServers($memcached_hosts); if (count($this->memcached->getServerList()) == 0) { @@ -70,14 +84,94 @@ class MemcachedCache extends Cache implements IMemoryCache */ public function getAllKeys($prefix = null) { - $keys = $this->getOriginalKeys($this->memcached->getAllKeys()); + $keys = $this->getOriginalKeys($this->getMemcachedKeys()); - if ($this->memcached->getResultCode() == Memcached::RES_SUCCESS) { - return $this->filterArrayKeysByPrefix($keys, $prefix); - } else { - $this->logger->debug('Memcached \'getAllKeys\' failed', ['result' => $this->memcached->getResultMessage()]); - return []; + return $this->filterArrayKeysByPrefix($keys, $prefix); + } + + /** + * Get all memcached keys. + * Special function because getAllKeys() is broken since memcached 1.4.23. + * + * cleaned up version of code found on Stackoverflow.com by Maduka Jayalath + * + * @return array|int - all retrieved keys (or negative number on error) + */ + private function getMemcachedKeys() + { + $mem = @fsockopen($this->firstServer, $this->firstPort); + if ($mem === false) { + return -1; } + + // retrieve distinct slab + $r = @fwrite($mem, 'stats items' . chr(10)); + if ($r === false) { + return -2; + } + + $slab = []; + while (($l = @fgets($mem, 1024)) !== false) { + // finished? + $l = trim($l); + if ($l == 'END') { + break; + } + + $m = []; + // + $r = preg_match('/^STAT\sitems\:(\d+)\:/', $l, $m); + if ($r != 1) { + return -3; + } + $a_slab = $m[1]; + + if (!array_key_exists($a_slab, $slab)) { + $slab[$a_slab] = []; + } + } + + reset($slab); + foreach ($slab as $a_slab_key => &$a_slab) { + $r = @fwrite($mem, 'stats cachedump ' . $a_slab_key . ' 100' . chr(10)); + if ($r === false) { + return -4; + } + + while (($l = @fgets($mem, 1024)) !== false) { + // finished? + $l = trim($l); + if ($l == 'END') { + break; + } + + $m = []; + // ITEM 42 [118 b; 1354717302 s] + $r = preg_match('/^ITEM\s([^\s]+)\s/', $l, $m); + if ($r != 1) { + return -5; + } + $a_key = $m[1]; + + $a_slab[] = $a_key; + } + } + + // close the connection + @fclose($mem); + unset($mem); + + $keys = []; + reset($slab); + foreach ($slab AS &$a_slab) { + reset($a_slab); + foreach ($a_slab AS &$a_key) { + $keys[] = $a_key; + } + } + unset($slab); + + return $keys; } /** diff --git a/src/Core/Lock/DatabaseLock.php b/src/Core/Lock/DatabaseLock.php index 2f409cd3d..07cf88c49 100644 --- a/src/Core/Lock/DatabaseLock.php +++ b/src/Core/Lock/DatabaseLock.php @@ -138,7 +138,7 @@ class DatabaseLock extends Lock if (empty($prefix)) { $where = ['`expires` >= ?', DateTimeFormat::utcNow()]; } else { - $where = ['`expires` >= ? AND `k` LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix]; + $where = ['`expires` >= ? AND `name` LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix]; } $stmt = $this->dba->select('locks', ['name'], $where); diff --git a/tests/src/Core/Cache/CacheTest.php b/tests/src/Core/Cache/CacheTest.php index 92fdaffa3..9071a55c4 100644 --- a/tests/src/Core/Cache/CacheTest.php +++ b/tests/src/Core/Cache/CacheTest.php @@ -2,7 +2,6 @@ namespace Friendica\Test\src\Core\Cache; -use Friendica\Core\Cache\MemcachedCache; use Friendica\Test\MockedTest; use Friendica\Util\PidFile; @@ -202,10 +201,6 @@ abstract class CacheTest extends MockedTest */ public function testGetAllKeys($value1, $value2, $value3) { - if ($this->cache instanceof MemcachedCache) { - $this->markTestSkipped('Memcached doesn\'t support getAllKeys anymore'); - } - $this->assertTrue($this->instance->set('value1', $value1)); $this->assertTrue($this->instance->set('value2', $value2)); $this->assertTrue($this->instance->set('test_value3', $value3)); @@ -219,5 +214,7 @@ abstract class CacheTest extends MockedTest $list = $this->instance->getAllKeys('test'); $this->assertContains('test_value3', $list); + $this->assertNotContains('value1', $list); + $this->assertNotContains('value2', $list); } }