From 03ae4dfbb2d86fc421e16a0095b84c7192efbd20 Mon Sep 17 00:00:00 2001 From: Denis Kuzmenchuk Date: Tue, 14 Dec 2021 18:50:54 +0300 Subject: [PATCH 1/2] [8.x] Fix possible out of memory error when deleting values by reference key from cache in Redis driver --- src/Illuminate/Cache/RedisTaggedCache.php | 21 +++++++++++++++++---- tests/Cache/CacheTaggedCacheTest.php | 8 ++++---- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Cache/RedisTaggedCache.php b/src/Illuminate/Cache/RedisTaggedCache.php index 6a683c10c36..21f367df17d 100644 --- a/src/Illuminate/Cache/RedisTaggedCache.php +++ b/src/Illuminate/Cache/RedisTaggedCache.php @@ -178,13 +178,26 @@ protected function deleteKeysByReference($reference) */ protected function deleteValues($referenceKey) { - $values = array_unique($this->store->connection()->smembers($referenceKey)); + $cursor = $defaultCursorValue = '0'; + + do { + [$cursor, $valuesChunk] = $this->store->connection()->sscan( + $referenceKey, $cursor, ['match' => '*', 'count' => 1000] + ); + + // PhpRedis client returns false if set does not exist or empty. Destruction on false stores null + // in each variable. If valuesChunk is null, it means that there is no results from sscan command. + // Iteration over set could be finished. + if ($valuesChunk === null) { + break; + } + + $valuesChunk = array_unique($valuesChunk); - if (count($values) > 0) { - foreach (array_chunk($values, 1000) as $valuesChunk) { + if (count($valuesChunk) > 0) { $this->store->connection()->del(...$valuesChunk); } - } + } while (((string) $cursor) !== $defaultCursorValue); } /** diff --git a/tests/Cache/CacheTaggedCacheTest.php b/tests/Cache/CacheTaggedCacheTest.php index 05baebd209a..b2493694d13 100644 --- a/tests/Cache/CacheTaggedCacheTest.php +++ b/tests/Cache/CacheTaggedCacheTest.php @@ -267,16 +267,16 @@ public function testRedisCacheTagsCanBeFlushed() $store->shouldReceive('connection')->andReturn($conn = m::mock(stdClass::class)); // Forever tag keys - $conn->shouldReceive('smembers')->once()->with('prefix:foo:forever_ref')->andReturn(['key1', 'key2']); - $conn->shouldReceive('smembers')->once()->with('prefix:bar:forever_ref')->andReturn(['key3']); + $conn->shouldReceive('sscan')->once()->with('prefix:foo:forever_ref', '0', ['match' => '*', 'count' => 1000])->andReturn(['0', ['key1', 'key2']]); + $conn->shouldReceive('sscan')->once()->with('prefix:bar:forever_ref', '0', ['match' => '*', 'count' => 1000])->andReturn(['0', ['key3']]); $conn->shouldReceive('del')->once()->with('key1', 'key2'); $conn->shouldReceive('del')->once()->with('key3'); $conn->shouldReceive('del')->once()->with('prefix:foo:forever_ref'); $conn->shouldReceive('del')->once()->with('prefix:bar:forever_ref'); // Standard tag keys - $conn->shouldReceive('smembers')->once()->with('prefix:foo:standard_ref')->andReturn(['key4', 'key5']); - $conn->shouldReceive('smembers')->once()->with('prefix:bar:standard_ref')->andReturn(['key6']); + $conn->shouldReceive('sscan')->once()->with('prefix:foo:standard_ref', '0', ['match' => '*', 'count' => 1000])->andReturn(['0', ['key4', 'key5']]); + $conn->shouldReceive('sscan')->once()->with('prefix:bar:standard_ref', '0', ['match' => '*', 'count' => 1000])->andReturn(['0', ['key6']]); $conn->shouldReceive('del')->once()->with('key4', 'key5'); $conn->shouldReceive('del')->once()->with('key6'); $conn->shouldReceive('del')->once()->with('prefix:foo:standard_ref'); From 14401a511a3c70b42ad648eeaed8e183399323c2 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 15 Dec 2021 08:12:52 -0600 Subject: [PATCH 2/2] formatting --- src/Illuminate/Cache/RedisTaggedCache.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Cache/RedisTaggedCache.php b/src/Illuminate/Cache/RedisTaggedCache.php index 21f367df17d..7863dbc0a60 100644 --- a/src/Illuminate/Cache/RedisTaggedCache.php +++ b/src/Illuminate/Cache/RedisTaggedCache.php @@ -185,10 +185,10 @@ protected function deleteValues($referenceKey) $referenceKey, $cursor, ['match' => '*', 'count' => 1000] ); - // PhpRedis client returns false if set does not exist or empty. Destruction on false stores null - // in each variable. If valuesChunk is null, it means that there is no results from sscan command. - // Iteration over set could be finished. - if ($valuesChunk === null) { + // PhpRedis client returns false if set does not exist or empty. Array destruction + // on false stores null in each variable. If valuesChunk is null, it means that + // there were not results from the previously executed "sscan" Redis command. + if (is_null($valuesChunk)) { break; }