aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private
diff options
context:
space:
mode:
authorAndy Scherzinger <info@andy-scherzinger.de>2023-06-01 17:12:05 +0200
committerGitHub <noreply@github.com>2023-06-01 17:12:05 +0200
commitf5d5636272c2a0c0d0c4bfdac8e5af459973d154 (patch)
tree9fca8cbf628e683fe966a4f3cfe84767b64c60e3 /lib/private
parentca1d9a167ebde00fa18f34823a5a993351538905 (diff)
parent24875c02dbad1b413067e001f5f1a5945d4310ac (diff)
downloadnextcloud-server-f5d5636272c2a0c0d0c4bfdac8e5af459973d154.tar.gz
nextcloud-server-f5d5636272c2a0c0d0c4bfdac8e5af459973d154.zip
Merge pull request #38538 from nextcloud/backport/37758/stable27
[stable27] redis: use atomic operations everywhere
Diffstat (limited to 'lib/private')
-rw-r--r--lib/private/Memcache/Redis.php90
1 files changed, 55 insertions, 35 deletions
diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php
index b6f96fffba4..bde25a3385a 100644
--- a/lib/private/Memcache/Redis.php
+++ b/lib/private/Memcache/Redis.php
@@ -32,6 +32,22 @@ namespace OC\Memcache;
use OCP\IMemcacheTTL;
class Redis extends Cache implements IMemcacheTTL {
+ /** name => [script, sha1] */
+ public const LUA_SCRIPTS = [
+ 'dec' => [
+ 'if redis.call("exists", KEYS[1]) == 1 then return redis.call("decrby", KEYS[1], ARGV[1]) else return "NEX" end',
+ '720b40cb66cef1579f2ef16ec69b3da8c85510e9',
+ ],
+ 'cas' => [
+ 'if redis.call("get", KEYS[1]) == ARGV[1] then redis.call("set", KEYS[1], ARGV[2]) return 1 else return 0 end',
+ '94eac401502554c02b811e3199baddde62d976d4',
+ ],
+ 'cad' => [
+ 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end',
+ 'cf0e94b2e9ffc7e04395cf88f7583fc309985910',
+ ],
+ ];
+
/**
* @var \Redis|\RedisCluster $cache
*/
@@ -54,18 +70,19 @@ class Redis extends Cache implements IMemcacheTTL {
public function get($key) {
$result = $this->getCache()->get($this->getPrefix() . $key);
- if ($result === false && !$this->getCache()->exists($this->getPrefix() . $key)) {
+ if ($result === false) {
return null;
- } else {
- return json_decode($result, true);
}
+
+ return self::decodeValue($result);
}
public function set($key, $value, $ttl = 0) {
+ $value = self::encodeValue($value);
if ($ttl > 0) {
- return $this->getCache()->setex($this->getPrefix() . $key, $ttl, json_encode($value));
+ return $this->getCache()->setex($this->getPrefix() . $key, $ttl, $value);
} else {
- return $this->getCache()->set($this->getPrefix() . $key, json_encode($value));
+ return $this->getCache()->set($this->getPrefix() . $key, $value);
}
}
@@ -82,6 +99,7 @@ class Redis extends Cache implements IMemcacheTTL {
}
public function clear($prefix = '') {
+ // TODO: this is slow and would fail with Redis cluster
$prefix = $this->getPrefix() . $prefix . '*';
$keys = $this->getCache()->keys($prefix);
$deleted = $this->getCache()->del($keys);
@@ -98,17 +116,14 @@ class Redis extends Cache implements IMemcacheTTL {
* @return bool
*/
public function add($key, $value, $ttl = 0) {
- // don't encode ints for inc/dec
- if (!is_int($value)) {
- $value = json_encode($value);
- }
+ $value = self::encodeValue($value);
$args = ['nx'];
if ($ttl !== 0 && is_int($ttl)) {
$args['ex'] = $ttl;
}
- return $this->getCache()->set($this->getPrefix() . $key, (string)$value, $args);
+ return $this->getCache()->set($this->getPrefix() . $key, $value, $args);
}
/**
@@ -130,10 +145,8 @@ class Redis extends Cache implements IMemcacheTTL {
* @return int | bool
*/
public function dec($key, $step = 1) {
- if (!$this->hasKey($key)) {
- return false;
- }
- return $this->getCache()->decrBy($this->getPrefix() . $key, $step);
+ $res = $this->evalLua('dec', [$key], [$step]);
+ return ($res === 'NEX') ? false : $res;
}
/**
@@ -145,18 +158,10 @@ class Redis extends Cache implements IMemcacheTTL {
* @return bool
*/
public function cas($key, $old, $new) {
- if (!is_int($new)) {
- $new = json_encode($new);
- }
- $this->getCache()->watch($this->getPrefix() . $key);
- if ($this->get($key) === $old) {
- $result = $this->getCache()->multi()
- ->set($this->getPrefix() . $key, $new)
- ->exec();
- return $result !== false;
- }
- $this->getCache()->unwatch();
- return false;
+ $old = self::encodeValue($old);
+ $new = self::encodeValue($new);
+
+ return $this->evalLua('cas', [$key], [$old, $new]) > 0;
}
/**
@@ -167,15 +172,9 @@ class Redis extends Cache implements IMemcacheTTL {
* @return bool
*/
public function cad($key, $old) {
- $this->getCache()->watch($this->getPrefix() . $key);
- if ($this->get($key) === $old) {
- $result = $this->getCache()->multi()
- ->unlink($this->getPrefix() . $key)
- ->exec();
- return $result !== false;
- }
- $this->getCache()->unwatch();
- return false;
+ $old = self::encodeValue($old);
+
+ return $this->evalLua('cad', [$key], [$old]) > 0;
}
public function setTTL($key, $ttl) {
@@ -185,4 +184,25 @@ class Redis extends Cache implements IMemcacheTTL {
public static function isAvailable(): bool {
return \OC::$server->getGetRedisFactory()->isAvailable();
}
+
+ protected function evalLua(string $scriptName, array $keys, array $args) {
+ $keys = array_map(fn ($key) => $this->getPrefix() . $key, $keys);
+ $args = array_merge($keys, $args);
+ $script = self::LUA_SCRIPTS[$scriptName];
+
+ $result = $this->getCache()->evalSha($script[1], $args, count($keys));
+ if (false === $result) {
+ $result = $this->getCache()->eval($script[0], $args, count($keys));
+ }
+
+ return $result;
+ }
+
+ protected static function encodeValue(mixed $value): string {
+ return is_int($value) ? (string) $value : json_encode($value);
+ }
+
+ protected static function decodeValue(string $value): mixed {
+ return is_numeric($value) ? (int) $value : json_decode($value, true);
+ }
}