From 01ec2325a096ac508b886bb2b0e99e11548a8c9a Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 15 Aug 2024 15:27:41 +0200 Subject: [PATCH] feat: add negative compare-and-delete to imemcache Signed-off-by: Robin Appelman --- lib/private/Memcache/CADTrait.php | 16 ++++++++++++++++ lib/private/Memcache/LoggerWrapperCache.php | 11 +++++++++++ lib/private/Memcache/NullCache.php | 5 +++++ lib/private/Memcache/ProfilerWrapperCache.php | 12 ++++++++++++ lib/private/Memcache/Redis.php | 10 ++++++++++ lib/public/IMemcache.php | 14 ++++++++++++++ tests/lib/Memcache/Cache.php | 12 ++++++++++++ 7 files changed, 80 insertions(+) diff --git a/lib/private/Memcache/CADTrait.php b/lib/private/Memcache/CADTrait.php index bb010e238dc95..2fc9c558d6485 100644 --- a/lib/private/Memcache/CADTrait.php +++ b/lib/private/Memcache/CADTrait.php @@ -35,4 +35,20 @@ public function cad($key, $old) { return false; } } + + public function ncad(string $key, $old): bool { + //no native cas, emulate with locking + if ($this->add($key . '_lock', true)) { + if ($this->get($key) !== $old) { + $this->remove($key); + $this->remove($key . '_lock'); + return true; + } else { + $this->remove($key . '_lock'); + return false; + } + } else { + return false; + } + } } diff --git a/lib/private/Memcache/LoggerWrapperCache.php b/lib/private/Memcache/LoggerWrapperCache.php index 11497e2a5d8ab..c219d16e01275 100644 --- a/lib/private/Memcache/LoggerWrapperCache.php +++ b/lib/private/Memcache/LoggerWrapperCache.php @@ -149,6 +149,17 @@ public function cad($key, $old) { return $this->wrappedCache->cad($key, $old); } + /** @inheritDoc */ + public function ncad(string $key, $old): bool { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::ncad::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->cad($key, $old); + } + /** @inheritDoc */ public function setTTL(string $key, int $ttl) { $this->wrappedCache->setTTL($key, $ttl); diff --git a/lib/private/Memcache/NullCache.php b/lib/private/Memcache/NullCache.php index ab5c491913a3f..c28bf4ce34274 100644 --- a/lib/private/Memcache/NullCache.php +++ b/lib/private/Memcache/NullCache.php @@ -43,6 +43,11 @@ public function cad($key, $old) { return true; } + public function ncad(string $key, $old): bool { + return true; + } + + public function clear($prefix = '') { return true; } diff --git a/lib/private/Memcache/ProfilerWrapperCache.php b/lib/private/Memcache/ProfilerWrapperCache.php index 84e3d880a0c9b..bc20ef5bce89e 100644 --- a/lib/private/Memcache/ProfilerWrapperCache.php +++ b/lib/private/Memcache/ProfilerWrapperCache.php @@ -166,6 +166,18 @@ public function cad($key, $old) { return $ret; } + /** @inheritDoc */ + public function ncad(string $key, $old): bool { + $start = microtime(true); + $ret = $this->wrappedCache->ncad($key, $old); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::ncad::' . $key, + ]; + return $ret; + } + /** @inheritDoc */ public function setTTL(string $key, int $ttl) { $this->wrappedCache->setTTL($key, $ttl); diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php index 87dc86ab10d30..8c07b9ae96a10 100644 --- a/lib/private/Memcache/Redis.php +++ b/lib/private/Memcache/Redis.php @@ -23,6 +23,10 @@ class Redis extends Cache implements IMemcacheTTL { 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end', 'cf0e94b2e9ffc7e04395cf88f7583fc309985910', ], + 'ncad' => [ + 'if redis.call("get", KEYS[1]) ~= ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end', + '75526f8048b13ce94a41b58eee59c664b4990ab2', + ], 'caSetTtl' => [ 'if redis.call("get", KEYS[1]) == ARGV[1] then redis.call("expire", KEYS[1], ARGV[2]) return 1 else return 0 end', 'fa4acbc946d23ef41d7d3910880b60e6e4972d72', @@ -164,6 +168,12 @@ public function cad($key, $old) { return $this->evalLua('cad', [$key], [$old]) > 0; } + public function ncad(string $key, $old): bool { + $old = self::encodeValue($old); + + return $this->evalLua('ncad', [$key], [$old]) > 0; + } + public function setTTL($key, $ttl) { if ($ttl === 0) { // having infinite TTL can lead to leaked keys as the prefix changes with version upgrades diff --git a/lib/public/IMemcache.php b/lib/public/IMemcache.php index 6e63884ff4513..b5f7e189ee3ec 100644 --- a/lib/public/IMemcache.php +++ b/lib/public/IMemcache.php @@ -67,10 +67,24 @@ public function cas($key, $old, $new); /** * Compare and delete * + * Delete $key if the stored value is equal to $old + * * @param string $key * @param mixed $old * @return bool * @since 8.1.0 */ public function cad($key, $old); + + /** + * Negative compare and delete + * + * Delete $key if the stored value is not equal to $old + * + * @param string $key + * @param mixed $old + * @return bool + * @since 30.0.0 + */ + public function ncad(string $key, $old): bool; } diff --git a/tests/lib/Memcache/Cache.php b/tests/lib/Memcache/Cache.php index efdaffc94ebc5..fa4b01800bbeb 100644 --- a/tests/lib/Memcache/Cache.php +++ b/tests/lib/Memcache/Cache.php @@ -121,6 +121,18 @@ public function testCadChanged() { $this->assertTrue($this->instance->hasKey('foo')); } + public function testNcadNotChanged() { + $this->instance->set('foo', 'bar'); + $this->assertFalse($this->instance->ncad('foo', 'bar')); + $this->assertTrue($this->instance->hasKey('foo')); + } + + public function testNcadChanged() { + $this->instance->set('foo', 'bar1'); + $this->assertTrue($this->instance->ncad('foo', 'bar')); + $this->assertFalse($this->instance->hasKey('foo')); + } + protected function tearDown(): void { if ($this->instance) {