From d68ce3cc39a6f99f9ae4724b308a04e775ebf8bb Mon Sep 17 00:00:00 2001 From: Razvan Aurariu <38325118+rzv-me@users.noreply.github.com> Date: Tue, 4 Mar 2025 18:52:27 +0200 Subject: [PATCH 1/2] Fix CacheSequenceResolver for Redis with Compression & Serialization Code is now compatible with Compression and Serialization enabled for Redis. The implementation is the same as Laravel used for RateLimiting using Redis. [src/Illuminate/Cache/RateLimiter.php](https://github.com/laravel/framework/blob/5d477f9a4080c1cdd43edd05ec4df61c738e0f8d/src/Illuminate/Cache/RateLimiter.php#L161) Without this change, I get this error when trying to create a snowflake ID: ``` InvalidArgumentException Sequences must be an integer between 0 and 4095 (got -1). ``` --- .../CacheSequenceResolver.php | 68 +++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/src/SequenceResolvers/CacheSequenceResolver.php b/src/SequenceResolvers/CacheSequenceResolver.php index 210bd10..ebc2719 100644 --- a/src/SequenceResolvers/CacheSequenceResolver.php +++ b/src/SequenceResolvers/CacheSequenceResolver.php @@ -3,7 +3,10 @@ namespace Glhd\Bits\SequenceResolvers; use Glhd\Bits\Contracts\ResolvesSequences; +use Illuminate\Cache\RedisStore; use Illuminate\Contracts\Cache\Repository; +use Illuminate\Redis\Connections\PhpRedisConnection; +use Redis; class CacheSequenceResolver implements ResolvesSequences { @@ -11,13 +14,70 @@ public function __construct( protected Repository $cache ) { } - + public function next(int $timestamp): int { $key = "glhd-bits-seq:{$timestamp}"; - - $this->cache->add($key, 0, now()->addSeconds(10)); - + + $this->withoutSerializationOrCompression( + fn() => $this->cache->add($key, 0, now()->addSeconds(10)) + ); + return $this->cache->increment($key) - 1; } + + protected function withoutSerializationOrCompression(callable $callback) + { + $store = $this->cache->getStore(); + + if (! $store instanceof RedisStore) { + return $callback(); + } + + $connection = $store->connection(); + + if (! $connection instanceof PhpRedisConnection) { + return $callback(); + } + + $client = $connection->client(); + + $oldSerializer = null; + + if ($this->serialized($client)) { + $oldSerializer = $client->getOption($client::OPT_SERIALIZER); + $client->setOption($client::OPT_SERIALIZER, $client::SERIALIZER_NONE); + } + + $oldCompressor = null; + + if ($this->compressed($client)) { + $oldCompressor = $client->getOption($client::OPT_COMPRESSION); + $client->setOption($client::OPT_COMPRESSION, $client::COMPRESSION_NONE); + } + + try { + return $callback(); + } finally { + if ($oldSerializer !== null) { + $client->setOption($client::OPT_SERIALIZER, $oldSerializer); + } + + if ($oldCompressor !== null) { + $client->setOption($client::OPT_COMPRESSION, $oldCompressor); + } + } + } + + public function serialized($client): bool + { + return defined('Redis::OPT_SERIALIZER') && + $client->getOption(Redis::OPT_SERIALIZER) !== Redis::SERIALIZER_NONE; + } + + public function compressed($client): bool + { + return defined('Redis::OPT_COMPRESSION') && + $client->getOption(Redis::OPT_COMPRESSION) !== Redis::COMPRESSION_NONE; + } } From 2dc107fdf8bd9974317727300c0fb7970f3b8cfa Mon Sep 17 00:00:00 2001 From: Chris Morrell Date: Tue, 4 Mar 2025 17:09:25 -0500 Subject: [PATCH 2/2] Code style --- .../CacheSequenceResolver.php | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/SequenceResolvers/CacheSequenceResolver.php b/src/SequenceResolvers/CacheSequenceResolver.php index ebc2719..8b5d676 100644 --- a/src/SequenceResolvers/CacheSequenceResolver.php +++ b/src/SequenceResolvers/CacheSequenceResolver.php @@ -11,7 +11,7 @@ class CacheSequenceResolver implements ResolvesSequences { public function __construct( - protected Repository $cache + protected Repository $cache, ) { } @@ -20,7 +20,7 @@ public function next(int $timestamp): int $key = "glhd-bits-seq:{$timestamp}"; $this->withoutSerializationOrCompression( - fn() => $this->cache->add($key, 0, now()->addSeconds(10)) + fn() => $this->cache->add($key, 0, now()->addSeconds(10)), ); return $this->cache->increment($key) - 1; @@ -28,6 +28,10 @@ public function next(int $timestamp): int protected function withoutSerializationOrCompression(callable $callback) { + // This is a copied from `RateLimiter::withoutSerializationOrCompression` and + // `PacksPhpRedisValues::withoutSerializationOrCompression` for backwards-compatibility + // reasons (the feature wasn't added until Laravel 11.41.0). + $store = $this->cache->getStore(); if (! $store instanceof RedisStore) { @@ -42,42 +46,40 @@ protected function withoutSerializationOrCompression(callable $callback) $client = $connection->client(); - $oldSerializer = null; - + $old_serializer = null; if ($this->serialized($client)) { - $oldSerializer = $client->getOption($client::OPT_SERIALIZER); + $old_serializer = $client->getOption($client::OPT_SERIALIZER); $client->setOption($client::OPT_SERIALIZER, $client::SERIALIZER_NONE); } - $oldCompressor = null; - + $old_compressor = null; if ($this->compressed($client)) { - $oldCompressor = $client->getOption($client::OPT_COMPRESSION); + $old_compressor = $client->getOption($client::OPT_COMPRESSION); $client->setOption($client::OPT_COMPRESSION, $client::COMPRESSION_NONE); } try { return $callback(); } finally { - if ($oldSerializer !== null) { - $client->setOption($client::OPT_SERIALIZER, $oldSerializer); + if (null !== $old_serializer) { + $client->setOption($client::OPT_SERIALIZER, $old_serializer); } - - if ($oldCompressor !== null) { - $client->setOption($client::OPT_COMPRESSION, $oldCompressor); + + if (null !== $old_compressor) { + $client->setOption($client::OPT_COMPRESSION, $old_compressor); } } } - public function serialized($client): bool + public function serialized(Redis $client): bool { - return defined('Redis::OPT_SERIALIZER') && - $client->getOption(Redis::OPT_SERIALIZER) !== Redis::SERIALIZER_NONE; + return defined('Redis::OPT_SERIALIZER') + && $client->getOption(Redis::OPT_SERIALIZER) !== Redis::SERIALIZER_NONE; } - public function compressed($client): bool + public function compressed(Redis $client): bool { - return defined('Redis::OPT_COMPRESSION') && - $client->getOption(Redis::OPT_COMPRESSION) !== Redis::COMPRESSION_NONE; + return defined('Redis::OPT_COMPRESSION') + && $client->getOption(Redis::OPT_COMPRESSION) !== Redis::COMPRESSION_NONE; } }