Skip to content

Commit 0ed6b72

Browse files
committed
LEverage Redis eval caching
1 parent 1ed5e0c commit 0ed6b72

File tree

3 files changed

+88
-38
lines changed

3 files changed

+88
-38
lines changed

src/Symfony/Component/Lock/Store/RedisStore.php

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
use Symfony\Component\Lock\Exception\LockConflictedException;
2020
use Symfony\Component\Lock\Exception\LockStorageException;
2121
use Symfony\Component\Lock\Key;
22-
use Symfony\Component\Lock\PersistingStoreInterface;
2322
use Symfony\Component\Lock\SharedLockStoreInterface;
2423

2524
/**
@@ -257,37 +256,46 @@ public function exists(Key $key)
257256
*/
258257
private function evaluate(string $script, string $resource, array $args)
259258
{
260-
if (
261-
$this->redis instanceof \Redis ||
262-
$this->redis instanceof \RedisCluster ||
263-
$this->redis instanceof RedisProxy ||
264-
$this->redis instanceof RedisClusterProxy
265-
) {
266-
$this->redis->clearLastError();
267-
$result = $this->redis->eval($script, array_merge([$resource], $args), 1);
268-
if (null !== $err = $this->redis->getLastError()) {
269-
throw new LockStorageException($err);
270-
}
271-
272-
return $result;
273-
}
259+
$sha = sha1($script);
274260

275261
if ($this->redis instanceof \RedisArray) {
276262
$client = $this->redis->_instance($this->redis->_target($resource));
263+
} else {
264+
$client = $this->redis;
265+
}
266+
267+
if (
268+
$client instanceof \Redis ||
269+
$client instanceof \RedisCluster ||
270+
$client instanceof RedisProxy ||
271+
$client instanceof RedisClusterProxy
272+
) {
277273
$client->clearLastError();
278-
$result = $client->eval($script, array_merge([$resource], $args), 1);
279-
if (null !== $err = $client->getLastError()) {
274+
$result = $client->evalSha($sha, array_merge([$resource], $args), 1);
275+
$err = $client->getLastError();
276+
if (false === $result && 0 === strpos($err, 'NOSCRIPT')) {
277+
$client->clearLastError();
278+
$result = $client->eval($script, array_merge([$resource], $args), 1);
279+
$err = $client->getLastError();
280+
}
281+
282+
if (null !== $err) {
280283
throw new LockStorageException($err);
281284
}
282285

283286
return $result;
284-
285-
return $this->redis->_instance($this->redis->_target($resource))->eval($script, array_merge([$resource], $args), 1);
286287
}
287288

288-
if ($this->redis instanceof \Predis\ClientInterface) {
289+
if ($client instanceof \Predis\ClientInterface) {
290+
try {
291+
return $client->evalSha($sha, 1, $resource, ...$args);
292+
} catch (ServerException $e) {
293+
if (0 !== strpos($e->getMessage(), 'NOSCRIPT')) {
294+
throw new LockStorageException($e->getMessage(), $e->getCode(), $e);
295+
}
296+
}
289297
try {
290-
return $this->redis->eval(...array_merge([$script, 1, $resource], $args));
298+
return $client->eval($script, 1, $resource, ...$args);
291299
} catch (ServerException $e) {
292300
throw new LockStorageException($e->getMessage(), $e->getCode(), $e);
293301
}

src/Symfony/Component/Semaphore/Exception/SemaphoreAcquiringException.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
*/
2222
class SemaphoreAcquiringException extends \RuntimeException implements ExceptionInterface
2323
{
24-
public function __construct(Key $key, string $message)
24+
public function __construct(Key $key, string $message, \Throwable $e = null)
2525
{
26-
parent::__construct(sprintf('The semaphore "%s" could not be acquired: %s.', $key, $message));
26+
parent::__construct(sprintf('The semaphore "%s" could not be acquired: %s.', $key, $message), 0, $e);
2727
}
2828
}

src/Symfony/Component/Semaphore/Store/RedisStore.php

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
namespace Symfony\Component\Semaphore\Store;
1313

14+
use Predis\Response\ServerException;
1415
use Symfony\Component\Cache\Traits\RedisClusterProxy;
1516
use Symfony\Component\Cache\Traits\RedisProxy;
1617
use Symfony\Component\Semaphore\Exception\InvalidArgumentException;
18+
use Symfony\Component\Semaphore\Exception\RuntimeException;
1719
use Symfony\Component\Semaphore\Exception\SemaphoreAcquiringException;
1820
use Symfony\Component\Semaphore\Exception\SemaphoreExpiredException;
1921
use Symfony\Component\Semaphore\Key;
@@ -60,8 +62,12 @@ public function save(Key $key, float $ttlInSecond)
6062
$key->getWeight(),
6163
];
6264

63-
if (!$this->evaluate($script, sprintf('{%s}', $key), $args)) {
64-
throw new SemaphoreAcquiringException($key, 'the script return false');
65+
try {
66+
if (!$this->evaluate($script, sprintf('{%s}', $key), $args)) {
67+
throw new SemaphoreAcquiringException($key, 'the script return false');
68+
}
69+
} catch (\Exception $e) {
70+
throw new SemaphoreAcquiringException($key, 'the script failed', $e);
6571
}
6672
}
6773

@@ -76,7 +82,11 @@ public function putOffExpiration(Key $key, float $ttlInSecond)
7682

7783
$script = file_get_contents(__DIR__.'/Resources/redis_put_off_expiration.lua');
7884

79-
$ret = $this->evaluate($script, sprintf('{%s}', $key), [time() + $ttlInSecond, $this->getUniqueToken($key)]);
85+
try {
86+
$ret = $this->evaluate($script, sprintf('{%s}', $key), [time() + $ttlInSecond, $this->getUniqueToken($key)]);
87+
} catch (\Exception $e) {
88+
throw new SemaphoreAcquiringException($key, 'the script failed', $e);
89+
}
8090

8191
// Occurs when redis has been reset
8292
if (false === $ret) {
@@ -96,7 +106,11 @@ public function delete(Key $key)
96106
{
97107
$script = file_get_contents(__DIR__.'/Resources/redis_delete.lua');
98108

99-
$this->evaluate($script, sprintf('{%s}', $key), [$this->getUniqueToken($key)]);
109+
try {
110+
$this->evaluate($script, sprintf('{%s}', $key), [$this->getUniqueToken($key)]);
111+
} catch (\Exception $e) {
112+
throw new SemaphoreAcquiringException($key, 'the script failed', $e);
113+
}
100114
}
101115

102116
/**
@@ -114,21 +128,49 @@ public function exists(Key $key): bool
114128
*/
115129
private function evaluate(string $script, string $resource, array $args)
116130
{
117-
if (
118-
$this->redis instanceof \Redis ||
119-
$this->redis instanceof \RedisCluster ||
120-
$this->redis instanceof RedisProxy ||
121-
$this->redis instanceof RedisClusterProxy
122-
) {
123-
return $this->redis->eval($script, array_merge([$resource], $args), 1);
124-
}
131+
$sha = sha1($script);
125132

126133
if ($this->redis instanceof \RedisArray) {
127-
return $this->redis->_instance($this->redis->_target($resource))->eval($script, array_merge([$resource], $args), 1);
134+
$client = $this->redis->_instance($this->redis->_target($resource));
135+
} else {
136+
$client = $this->redis;
137+
}
138+
139+
if (
140+
$client instanceof \Redis ||
141+
$client instanceof \RedisCluster ||
142+
$client instanceof RedisProxy ||
143+
$client instanceof RedisClusterProxy
144+
) {
145+
$client->clearLastError();
146+
$result = $client->evalSha($sha, array_merge([$resource], $args), 1);
147+
$err = $client->getLastError();
148+
if (false === $result && 0 === strpos($err, 'NOSCRIPT')) {
149+
$client->clearLastError();
150+
$result = $client->eval($script, array_merge([$resource], $args), 1);
151+
$err = $client->getLastError();
152+
}
153+
154+
if (null !== $err) {
155+
throw new RuntimeException($err);
156+
}
157+
158+
return $result;
128159
}
129160

130-
if ($this->redis instanceof \Predis\ClientInterface) {
131-
return $this->redis->eval(...array_merge([$script, 1, $resource], $args));
161+
if ($client instanceof \Predis\ClientInterface) {
162+
try {
163+
return $client->evalSha($sha, 1, $resource, ...$args);
164+
} catch (ServerException $e) {
165+
if (0 !== strpos($e->getMessage(), 'NOSCRIPT')) {
166+
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
167+
}
168+
}
169+
try {
170+
return $client->eval($script, 1, $resource, ...$args);
171+
} catch (ServerException $e) {
172+
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
173+
}
132174
}
133175

134176
throw new InvalidArgumentException(sprintf('"%s()" expects being initialized with a Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, \is_object($this->redis) ? \get_class($this->redis) : \gettype($this->redis)));

0 commit comments

Comments
 (0)