feat: add negative compare-and-delete to imemcache

Signed-off-by: Robin Appelman <robin@icewind.nl>
pull/47259/head
Robin Appelman 3 months ago
parent 307608b26c
commit 20dbb6c7e8
  1. 17
      lib/private/Memcache/CADTrait.php
  2. 11
      lib/private/Memcache/LoggerWrapperCache.php
  3. 5
      lib/private/Memcache/NullCache.php
  4. 12
      lib/private/Memcache/ProfilerWrapperCache.php
  5. 10
      lib/private/Memcache/Redis.php
  6. 20
      lib/public/IMemcache.php
  7. 23
      tests/lib/Memcache/Cache.php

@ -35,4 +35,21 @@ trait CADTrait {
return false;
}
}
public function ncad(string $key, mixed $old): bool {
//no native cad, emulate with locking
if ($this->add($key . '_lock', true)) {
$value = $this->get($key);
if ($value !== null && $value !== $old) {
$this->remove($key);
$this->remove($key . '_lock');
return true;
} else {
$this->remove($key . '_lock');
return false;
}
} else {
return false;
}
}
}

@ -149,6 +149,17 @@ class LoggerWrapperCache extends Cache implements IMemcacheTTL {
return $this->wrappedCache->cad($key, $old);
}
/** @inheritDoc */
public function ncad(string $key, mixed $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);

@ -43,6 +43,11 @@ class NullCache extends Cache implements \OCP\IMemcache {
return true;
}
public function ncad(string $key, mixed $old): bool {
return true;
}
public function clear($prefix = '') {
return true;
}

@ -166,6 +166,18 @@ class ProfilerWrapperCache extends AbstractDataCollector implements IMemcacheTTL
return $ret;
}
/** @inheritDoc */
public function ncad(string $key, mixed $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);

@ -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 @@ class Redis extends Cache implements IMemcacheTTL {
return $this->evalLua('cad', [$key], [$old]) > 0;
}
public function ncad(string $key, mixed $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

@ -56,10 +56,12 @@ interface IMemcache extends ICache {
/**
* Compare and set
*
* Set $key to $new only if it's current value is $new
*
* @param string $key
* @param mixed $old
* @param mixed $new
* @return bool
* @return bool true if the value was successfully set or false if $key wasn't set to $old
* @since 8.1.0
*/
public function cas($key, $old, $new);
@ -67,10 +69,24 @@ interface IMemcache extends ICache {
/**
* Compare and delete
*
* Delete $key if the stored value is equal to $old
*
* @param string $key
* @param mixed $old
* @return bool
* @return bool true if the value was successfully deleted or false if $key wasn't set to $old
* @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 true if the value was successfully deleted or false if $key was set to $old or is not set
* @since 30.0.0
*/
public function ncad(string $key, mixed $old): bool;
}

@ -109,6 +109,10 @@ abstract class Cache extends \Test\Cache\TestCache {
$this->assertEquals('bar1', $this->instance->get('foo'));
}
public function testCasNotSet() {
$this->assertFalse($this->instance->cas('foo', 'bar', 'asd'));
}
public function testCadNotChanged() {
$this->instance->set('foo', 'bar');
$this->assertTrue($this->instance->cad('foo', 'bar'));
@ -121,6 +125,25 @@ abstract class Cache extends \Test\Cache\TestCache {
$this->assertTrue($this->instance->hasKey('foo'));
}
public function testCadNotSet() {
$this->assertFalse($this->instance->cad('foo', 'bar'));
}
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'));
}
public function testNcadNotSet() {
$this->assertFalse($this->instance->ncad('foo', 'bar'));
}
protected function tearDown(): void {
if ($this->instance) {

Loading…
Cancel
Save