diff --git a/src/Symfony/Component/Lock/CHANGELOG.md b/src/Symfony/Component/Lock/CHANGELOG.md index 385fffbcbaea..df19f0a54d7c 100644 --- a/src/Symfony/Component/Lock/CHANGELOG.md +++ b/src/Symfony/Component/Lock/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 7.2 --- + * RedisStore uses `EVALSHA` over `EVAL` when evaluating LUA scripts * Add `NullStore` 7.0 diff --git a/src/Symfony/Component/Lock/Store/RedisStore.php b/src/Symfony/Component/Lock/Store/RedisStore.php index edbc89343f30..a7bf7fec29de 100644 --- a/src/Symfony/Component/Lock/Store/RedisStore.php +++ b/src/Symfony/Component/Lock/Store/RedisStore.php @@ -29,6 +29,8 @@ class RedisStore implements SharedLockStoreInterface { use ExpiringStoreTrait; + private const NO_SCRIPT_ERROR_MESSAGE = 'NOSCRIPT No matching script. Please use EVAL.'; + private bool $supportTime; /** @@ -226,11 +228,32 @@ public function exists(Key $key): bool private function evaluate(string $script, string $resource, array $args): mixed { + $scriptSha = sha1($script); + if ($this->redis instanceof \Redis || $this->redis instanceof Relay || $this->redis instanceof \RedisCluster) { $this->redis->clearLastError(); - $result = $this->redis->eval($script, array_merge([$resource], $args), 1); - if (null !== $err = $this->redis->getLastError()) { - throw new LockStorageException($err); + + $result = $this->redis->evalSha($scriptSha, array_merge([$resource], $args), 1); + if (self::NO_SCRIPT_ERROR_MESSAGE === $err = $this->redis->getLastError()) { + $this->redis->clearLastError(); + + if ($this->redis instanceof \RedisCluster) { + foreach ($this->redis->_masters() as $master) { + $this->redis->script($master, 'LOAD', $script); + } + } else { + $this->redis->script('LOAD', $script); + } + + if (null !== $err = $this->redis->getLastError()) { + throw new LockStorageException($err); + } + + $result = $this->redis->evalSha($scriptSha, array_merge([$resource], $args), 1); + + if (null !== $err = $this->redis->getLastError()) { + throw new LockStorageException($err); + } } return $result; @@ -239,9 +262,21 @@ private function evaluate(string $script, string $resource, array $args): mixed if ($this->redis instanceof \RedisArray) { $client = $this->redis->_instance($this->redis->_target($resource)); $client->clearLastError(); - $result = $client->eval($script, array_merge([$resource], $args), 1); - if (null !== $err = $client->getLastError()) { - throw new LockStorageException($err); + $result = $client->evalSha($scriptSha, array_merge([$resource], $args), 1); + if (self::NO_SCRIPT_ERROR_MESSAGE === $err = $client->getLastError()) { + $client->clearLastError(); + + $client->script('LOAD', $script); + + if (null !== $err = $client->getLastError()) { + throw new LockStorageException($err); + } + + $result = $client->evalSha($scriptSha, array_merge([$resource], $args), 1); + + if (null !== $err = $client->getLastError()) { + throw new LockStorageException($err); + } } return $result; @@ -250,7 +285,22 @@ private function evaluate(string $script, string $resource, array $args): mixed \assert($this->redis instanceof \Predis\ClientInterface); try { - return $this->redis->eval(...array_merge([$script, 1, $resource], $args)); + return $this->redis->evalSha($scriptSha, 1, $resource, ...$args); + } catch (ServerException $e) { + // Fallthrough only if we need to load the script + if (self::NO_SCRIPT_ERROR_MESSAGE !== $e->getMessage()) { + throw new LockStorageException($e->getMessage(), $e->getCode(), $e); + } + } + + try { + $this->redis->script('LOAD', $script); + } catch (ServerException $e) { + throw new LockStorageException($e->getMessage(), $e->getCode(), $e); + } + + try { + return $this->redis->evalSha($scriptSha, 1, $resource, ...$args); } catch (ServerException $e) { throw new LockStorageException($e->getMessage(), $e->getCode(), $e); }
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: