Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b666d2e

Browse files
[Cache] serialize tags separately from values in AbstractTagAwareAdapter
1 parent 6e7f325 commit b666d2e

File tree

6 files changed

+180
-10
lines changed

6 files changed

+180
-10
lines changed

src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ abstract protected function doDelete(array $ids, array $tagData = []): bool;
149149
*/
150150
abstract protected function doInvalidate(array $tagIds): bool;
151151

152+
/**
153+
* Returns the tags bound to the provided ids.
154+
*/
155+
protected function doFetchTags(array $ids): iterable
156+
{
157+
foreach ($this->doFetch($ids) as $id => $value) {
158+
yield $id => \is_array($value) && \is_array($value['tags'] ?? null) ? $value['tags'] : [];
159+
}
160+
}
161+
152162
/**
153163
* {@inheritdoc}
154164
*
@@ -233,8 +243,8 @@ public function deleteItems(array $keys)
233243
}
234244

235245
try {
236-
foreach ($this->doFetch($ids) as $id => $value) {
237-
foreach ($value['tags'] ?? [] as $tag) {
246+
foreach ($this->doFetchTags($ids) as $id => $tags) {
247+
foreach ($tags as $tag) {
238248
$tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id;
239249
}
240250
}

src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
1515
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
16+
use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
1617
use Symfony\Component\Cache\PruneableInterface;
1718
use Symfony\Component\Cache\Traits\FilesystemTrait;
1819

@@ -37,7 +38,7 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune
3738

3839
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
3940
{
40-
$this->marshaller = $marshaller ?? new DefaultMarshaller();
41+
$this->marshaller = new TagAwareMarshaller($marshaller ?? new DefaultMarshaller());
4142
parent::__construct('', $defaultLifetime);
4243
$this->init($namespace, $directory);
4344
}
@@ -130,6 +131,40 @@ protected function doSave(array $values, ?int $lifetime, array $addTagData = [],
130131
return $failed;
131132
}
132133

134+
/**
135+
* {@inheritdoc}
136+
*/
137+
protected function doFetchTags(array $ids): iterable
138+
{
139+
foreach ($ids as $id) {
140+
$file = $this->getFile($id);
141+
if (!file_exists($file) || !$h = @fopen($file, 'rb')) {
142+
continue;
143+
}
144+
145+
$meta = explode("\n", fread($h, 4096), 3)[2] ?? '';
146+
147+
// detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
148+
if (13 < \strlen($meta) && "\x9D" === $meta[0] && "\0" === $meta[5] && "\x5F" === $meta[9]) {
149+
$meta[9] = "\0";
150+
$tagLen = unpack('Nlen', $meta, 9)['len'];
151+
$meta = substr($meta, 13, $tagLen);
152+
153+
if (0 < $tagLen -= \strlen($meta)) {
154+
$meta .= fread($h, $tagLen);
155+
}
156+
157+
try {
158+
yield $id => '' === $meta ? [] : $this->marshaller->unmarshall($meta);
159+
} catch (\Exception $e) {
160+
yield $id => [];
161+
}
162+
}
163+
164+
fclose($h);
165+
}
166+
}
167+
133168
/**
134169
* {@inheritdoc}
135170
*/

src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
use Predis\Connection\Aggregate\PredisCluster;
1616
use Predis\Response\Status;
1717
use Symfony\Component\Cache\Exception\InvalidArgumentException;
18+
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
1819
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
20+
use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
1921
use Symfony\Component\Cache\Traits\RedisTrait;
2022

2123
/**
@@ -67,7 +69,7 @@ public function __construct($redisClient, string $namespace = '', int $defaultLi
6769
throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, \get_class($redisClient->getConnection())));
6870
}
6971

70-
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
72+
$this->init($redisClient, $namespace, $defaultLifetime, new TagAwareMarshaller($marshaller ?? new DefaultMarshaller()));
7173
}
7274

7375
/**
@@ -119,6 +121,42 @@ protected function doSave(array $values, ?int $lifetime, array $addTagData = [],
119121
return $failed;
120122
}
121123

124+
/**
125+
* {@inheritdoc}
126+
*/
127+
protected function doFetchTags(array $ids): iterable
128+
{
129+
$lua = <<<'EOLUA'
130+
local v = redis.call('GET', KEYS[1])
131+
132+
if not v or v:len() <= 13 or v:byte(1) ~= 0x9D or v:byte(6) ~= 0 or v:byte(10) ~= 0x5F then
133+
return ''
134+
end
135+
136+
return v:sub(14, 13 + v:byte(13) + v:byte(12) * 256 + v:byte(11) * 65536)
137+
EOLUA;
138+
139+
if ($this->redis instanceof \Predis\ClientInterface) {
140+
$evalArgs = [$lua, 1, &$id];
141+
} else {
142+
$evalArgs = [$lua, [&$id], 1];
143+
}
144+
145+
$results = $this->pipeline(function () use ($ids, &$id, $evalArgs) {
146+
foreach ($ids as $id) {
147+
yield 'eval' => $evalArgs;
148+
}
149+
});
150+
151+
foreach ($results as $id => $result) {
152+
try {
153+
yield $id => !\is_string($result) || '' === $result ? [] : $this->marshaller->unmarshall($result);
154+
} catch (\Exception $e) {
155+
yield $id => [];
156+
}
157+
}
158+
}
159+
122160
/**
123161
* {@inheritdoc}
124162
*/
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Marshaller;
13+
14+
/**
15+
* A marshaller optimized for data structures generated by AbstractTagAwareAdapter.
16+
*
17+
* @author Nicolas Grekas <[email protected]>
18+
*/
19+
class TagAwareMarshaller implements MarshallerInterface
20+
{
21+
private $marshaller;
22+
23+
public function __construct(MarshallerInterface $marshaller)
24+
{
25+
$this->marshaller = $marshaller;
26+
}
27+
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
public function marshall(array $values, ?array &$failed): array
32+
{
33+
$failed = $notSerialized = $serialized = [];
34+
35+
foreach ($values as $id => $value) {
36+
if (\is_array($value) && \is_array($value['tags'] ?? null) && \array_key_exists('value', $value) && \count($value) === 2 + (\is_string($value['meta'] ?? null) && 8 === \strlen($value['meta']))) {
37+
// if the value is an array with keys "tags", "value" and "meta", use a compact serialization format
38+
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F allow detecting this format quickly in unmarshall()
39+
40+
$v = $this->marshaller->marshall($value, $f);
41+
42+
if ($f) {
43+
$f = [];
44+
} else {
45+
if ([] === $value['tags']) {
46+
$v['tags'] = '';
47+
}
48+
49+
$serialized[$id] = "\x9D".($value['meta'] ?? "\0\0\0\0\0\0\0\0").pack('N', \strlen($v['tags'])).$v['tags'].$v['value'];
50+
$serialized[$id][9] = "\x5F";
51+
continue;
52+
}
53+
}
54+
55+
// other arbitratry values are serialized using the decorated marshaller below
56+
$notSerialized[$id] = $value;
57+
}
58+
59+
return $notSerialized ? $serialized + $this->marshaller->marshall($notSerialized, $failed) : $serialized;
60+
}
61+
62+
/**
63+
* {@inheritdoc}
64+
*/
65+
public function unmarshall(string $value)
66+
{
67+
// detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
68+
if (13 >= \strlen($value) || "\x9D" !== $value[0] || "\0" !== $value[5] || "\x5F" !== $value[9]) {
69+
return $this->marshaller->unmarshall($value);
70+
}
71+
72+
// data consists of value, tags and metadata which we need to unpack
73+
$meta = substr($value, 1, 12);
74+
$meta[8] = "\0";
75+
$tagLen = unpack('Nlen', $meta, 8)['len'];
76+
$meta = substr($meta, 0, 8);
77+
78+
return [
79+
'value' => $this->marshaller->unmarshall(substr($value, 13 + $tagLen)),
80+
'tags' => $tagLen ? $this->marshaller->unmarshall(substr($value, 13, $tagLen)) : [],
81+
'meta' => "\0\0\0\0\0\0\0\0" === $meta ? null : $meta,
82+
];
83+
}
84+
}

src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ public function getItem($key)
5151
foreach ($this->doFetch([$id]) as $value) {
5252
$isHit = true;
5353
}
54+
55+
return $f($key, $value, $isHit);
5456
} catch (\Exception $e) {
5557
CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
5658
}
5759

58-
return $f($key, $value, $isHit);
60+
return $f($key, null, false);
5961
}
6062

6163
/**

src/Symfony/Component/Cache/Traits/RedisTrait.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -445,25 +445,26 @@ private function pipeline(\Closure $generator, $redis = null): \Generator
445445
$results = [];
446446
foreach ($generator() as $command => $args) {
447447
$results[] = $redis->{$command}(...$args);
448-
$ids[] = $args[0];
448+
$ids[] = 'eval' === $command ? ($redis instanceof \Predis\ClientInterface ? $args[2] : $args[1][0]) : $args[0];
449449
}
450450
} elseif ($redis instanceof \Predis\ClientInterface) {
451451
$results = $redis->pipeline(static function ($redis) use ($generator, &$ids) {
452452
foreach ($generator() as $command => $args) {
453453
$redis->{$command}(...$args);
454-
$ids[] = $args[0];
454+
$ids[] = 'eval' === $command ? $args[2] : $args[0];
455455
}
456456
});
457457
} elseif ($redis instanceof \RedisArray) {
458458
$connections = $results = $ids = [];
459459
foreach ($generator() as $command => $args) {
460-
if (!isset($connections[$h = $redis->_target($args[0])])) {
460+
$id = 'eval' === $command ? $args[1][0] : $args[0];
461+
if (!isset($connections[$h = $redis->_target($id)])) {
461462
$connections[$h] = [$redis->_instance($h), -1];
462463
$connections[$h][0]->multi(\Redis::PIPELINE);
463464
}
464465
$connections[$h][0]->{$command}(...$args);
465466
$results[] = [$h, ++$connections[$h][1]];
466-
$ids[] = $args[0];
467+
$ids[] = $id;
467468
}
468469
foreach ($connections as $h => $c) {
469470
$connections[$h] = $c[0]->exec();
@@ -475,7 +476,7 @@ private function pipeline(\Closure $generator, $redis = null): \Generator
475476
$redis->multi(\Redis::PIPELINE);
476477
foreach ($generator() as $command => $args) {
477478
$redis->{$command}(...$args);
478-
$ids[] = $args[0];
479+
$ids[] = 'eval' === $command ? $args[1][0] : $args[0];
479480
}
480481
$results = $redis->exec();
481482
}

0 commit comments

Comments
 (0)