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

Skip to content

Commit d95bf4a

Browse files
committed
Change to have more dedicated signature for save on TagAware adapters1
1 parent 8d0ad97 commit d95bf4a

File tree

5 files changed

+199
-120
lines changed

5 files changed

+199
-120
lines changed

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,59 @@ public static function createConnection($dsn, array $options = [])
137137

138138
throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn));
139139
}
140+
141+
/**
142+
* {@inheritdoc}
143+
*/
144+
public function commit()
145+
{
146+
$ok = true;
147+
$byLifetime = $this->mergeByLifetime;
148+
$byLifetime = $byLifetime($this->deferred, $this->namespace, $expiredIds);
149+
$retry = $this->deferred = [];
150+
151+
if ($expiredIds) {
152+
$this->doDelete($expiredIds);
153+
}
154+
foreach ($byLifetime as $lifetime => $values) {
155+
try {
156+
$e = $this->doSave($values, $lifetime);
157+
} catch (\Exception $e) {
158+
}
159+
if (true === $e || [] === $e) {
160+
continue;
161+
}
162+
if (\is_array($e) || 1 === \count($values)) {
163+
foreach (\is_array($e) ? $e : array_keys($values) as $id) {
164+
$ok = false;
165+
$v = $values[$id];
166+
$type = \is_object($v) ? \get_class($v) : \gettype($v);
167+
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
168+
}
169+
} else {
170+
foreach ($values as $id => $v) {
171+
$retry[$lifetime][] = $id;
172+
}
173+
}
174+
}
175+
176+
// When bulk-save failed, retry each item individually
177+
foreach ($retry as $lifetime => $ids) {
178+
foreach ($ids as $id) {
179+
try {
180+
$v = $byLifetime[$lifetime][$id];
181+
$e = $this->doSave([$id => $v], $lifetime);
182+
} catch (\Exception $e) {
183+
}
184+
if (true === $e || [] === $e) {
185+
continue;
186+
}
187+
$ok = false;
188+
$type = \is_object($v) ? \get_class($v) : \gettype($v);
189+
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
190+
}
191+
}
192+
193+
return $ok;
194+
}
140195
}

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

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@
2222
use Symfony\Component\Cache\Traits\ContractsTrait;
2323
use Symfony\Contracts\Cache\CacheInterface;
2424

25+
/**
26+
* Abstract for native TagAware adapters.
27+
*
28+
* To keep info on tags, the tags are both serialized as part of cache value. And provided as tag ids
29+
* to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate().
30+
*
31+
* @author Nicolas Grekas <[email protected]>
32+
* @author André Rømcke <[email protected]>
33+
*/
2534
abstract class AbstractTagAwareAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
2635
{
2736
use AbstractAdapterTrait;
@@ -62,7 +71,7 @@ static function ($key, $value, $isHit) use ($defaultLifetime) {
6271
$getId = \Closure::fromCallable([$this, 'getId']);
6372
$tagPrefix = self::TAG_PREFIX;
6473
$this->mergeByLifetime = \Closure::bind(
65-
static function ($deferred, $namespace, &$expiredIds) use ($getId, $tagPrefix) {
74+
static function ($deferred, &$expiredIds) use ($getId, $tagPrefix) {
6675
$byLifetime = [];
6776
$now = microtime(true);
6877
$expiredIds = [];
@@ -109,6 +118,95 @@ static function ($deferred, $namespace, &$expiredIds) use ($getId, $tagPrefix) {
109118
);
110119
}
111120

121+
/**
122+
* {@inheritdoc}
123+
*/
124+
public function commit()
125+
{
126+
$ok = true;
127+
$byLifetime = $this->mergeByLifetime;
128+
$byLifetime = $byLifetime($this->deferred, $expiredIds);
129+
$retry = $this->deferred = [];
130+
131+
if ($expiredIds) {
132+
// Tags are not cleaned up in this case, however that is done on invalidateTags().
133+
$this->doDelete($expiredIds);
134+
}
135+
foreach ($byLifetime as $lifetime => $values) {
136+
try {
137+
$values = $this->extractTagData($values, $addTagData, $removeTagData);
138+
$e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
139+
} catch (\Exception $e) {
140+
}
141+
if (true === $e || [] === $e) {
142+
continue;
143+
}
144+
if (\is_array($e) || 1 === \count($values)) {
145+
foreach (\is_array($e) ? $e : array_keys($values) as $id) {
146+
$ok = false;
147+
$v = $values[$id];
148+
$type = \is_object($v) ? \get_class($v) : \gettype($v);
149+
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
150+
}
151+
} else {
152+
foreach ($values as $id => $v) {
153+
$retry[$lifetime][] = $id;
154+
}
155+
}
156+
}
157+
158+
// When bulk-save failed, retry each item individually
159+
foreach ($retry as $lifetime => $ids) {
160+
foreach ($ids as $id) {
161+
try {
162+
$v = $byLifetime[$lifetime][$id];
163+
$values = $this->extractTagData([$id => $v], $addTagData, $removeTagData);
164+
$e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
165+
} catch (\Exception $e) {
166+
}
167+
if (true === $e || [] === $e) {
168+
continue;
169+
}
170+
$ok = false;
171+
$type = \is_object($v) ? \get_class($v) : \gettype($v);
172+
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
173+
}
174+
}
175+
176+
return $ok;
177+
}
178+
179+
private function extractTagData(array $values, &$addTagData, &$removeTagData): array
180+
{
181+
$addTagData = [];
182+
$removeTagData = [];
183+
foreach ($values as $id => $value) {
184+
foreach ($value['tag-operations']['add'] as $tag => $tagId) {
185+
$addTagData[$tagId][] = $id;
186+
}
187+
188+
foreach ($value['tag-operations']['remove'] as $tag => $tagId) {
189+
$removeTagData[$tagId][] = $id;
190+
}
191+
192+
unset($values[$id]['tag-operations']);
193+
}
194+
195+
return $values;
196+
}
197+
198+
/**
199+
* Persists several cache items immediately.
200+
*
201+
* @param array $values The values to cache, indexed by their cache identifier
202+
* @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning
203+
* @param array $addTagData Hash where key is tag id, and array value is list of cache id's to add to tag.
204+
* @param array $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag.
205+
*
206+
* @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not
207+
*/
208+
abstract protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array;
209+
112210
/**
113211
* {@inheritdoc}
114212
*
@@ -167,7 +265,7 @@ public function deleteItems(array $keys)
167265
*
168266
* @return bool True if the items were successfully removed, false otherwise
169267
*/
170-
abstract protected function doDelete(array $ids, array $tagData = []);
268+
abstract protected function doDelete(array $ids, array $tagData = []): bool;
171269

172270
/**
173271
* {@inheritdoc}

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

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@
2626
* Stores tag/id information as symlinks to the cache files they refer to, in order to:
2727
* - skip loading tags info on reads
2828
* - be able to iterate cache to clear on-demand when invalidation by tags
29+
*
30+
* @author Nicolas Grekas <[email protected]>
31+
* @author André Rømcke <[email protected]>
2932
*/
3033
class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface
3134
{
3235
use FilesystemTrait {
33-
doSave as saveCache;
34-
doDelete as deleteCache;
36+
doSave as doSaveCache;
37+
doDelete as doDeleteCache;
3538
}
3639

3740
/**
@@ -52,48 +55,37 @@ public function __construct(string $namespace = '', int $defaultLifetime = 0, st
5255
}
5356

5457
/**
55-
* This method overrides {@see \Symfony\Component\Cache\Traits\FilesystemTrait::doSave}.
56-
*
57-
* It needs to be overridden due to store / remove tag information on save.
58-
*
5958
* {@inheritdoc}
6059
*/
61-
protected function doSave(array $values, $lifetime)
60+
protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array
6261
{
63-
// Extract and remove tag operations form values
64-
$tagOperations = ['add' => [], 'remove' => []];
65-
foreach ($values as $id => $value) {
66-
$tagOperations['add'][$id] = $value['tag-operations']['add'];
67-
$tagOperations['remove'][$id] = $value['tag-operations']['remove'];
68-
unset($value['tag-operations']);
69-
}
70-
71-
$failed = $this->saveCache($values, $lifetime);
62+
$failed = $this->doSaveCache($values, $lifetime);
7263

7364
$fs = $this->getFilesystem();
7465
// Add Tags as symlinks
75-
foreach ($tagOperations['add'] as $id => $tagIds) {
76-
if (!empty($failed) && \in_array($id, $failed)) {
77-
continue;
78-
}
66+
foreach ($addTagData as $tagId => $ids) {
67+
$tagFolder = $this->getTagFolder($tagId);
68+
foreach ($ids as $id) {
69+
if (!empty($failed) && \in_array($id, $failed)) {
70+
continue;
71+
}
7972

80-
$file = $this->getFile($id);
81-
$itemFileName = $this->getItemLinkFileName($id);
82-
foreach ($tagIds as $tagId) {
83-
$fs->symlink($file, $this->getTagFolder($tagId).$itemFileName);
73+
$file = $this->getFile($id);
74+
$itemFileName = $this->getItemLinkFileName($id);
75+
$fs->symlink($file, $tagFolder.$itemFileName);
8476
}
8577
}
8678

8779
// Unlink removed Tags
8880
$files = [];
89-
foreach ($tagOperations['remove'] as $id => $tagIds) {
90-
if (!empty($failed) && \in_array($id, $failed)) {
91-
continue;
92-
}
81+
foreach ($removeTagData as $tagId => $ids) {
82+
$tagFolder = $this->getTagFolder($tagId);
83+
foreach ($ids as $id) {
84+
if (!empty($failed) && \in_array($id, $failed)) {
85+
continue;
86+
}
9387

94-
$itemFileName = $this->getItemLinkFileName($id);
95-
foreach ($tagIds as $tagId) {
96-
$files[] = $this->getTagFolder($tagId).$itemFileName;
88+
$files[] = $tagFolder.$this->getItemLinkFileName($id);
9789
}
9890
}
9991
$fs->remove($files);
@@ -104,9 +96,9 @@ protected function doSave(array $values, $lifetime)
10496
/**
10597
* {@inheritdoc}
10698
*/
107-
protected function doDelete(array $ids, array $tagData = [])
99+
protected function doDelete(array $ids, array $tagData = []): bool
108100
{
109-
$ok = $this->deleteCache($ids);
101+
$ok = $this->doDeleteCache($ids);
110102

111103
// Remove tags
112104
$files = [];

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

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,18 @@
2323
/**
2424
* Class RedisTagAwareAdapter, stores tag <> id relationship as a Set so we don't need to fetch tags on get* operations.
2525
*
26+
* @see https://redis.io/topics/data-types#sets
27+
*
2628
* Requirements/Limitations:
27-
* - Redis 3.2+ (sPOP)
29+
* - Redis 3.2+ (https://redis.io/commands/spop)
2830
* - PHP Redis 3.1.3+ (sPOP) or Predis
2931
* - Redis configured with any `volatile-*` eviction policy, or `noeviction` if you are sure to never fill up memory
3032
* - This is to guarantee that tags ("relations") survives cache items so we can reliably invalidate on them,
3133
* which is archived by always storing cache with a expiry, while Set is without expiry (non-volatile).
3234
* - Max 2 billion keys per tag, so if you use a "all" items tag for expiry, that limits you to 2 billion items
35+
*
36+
* @author Nicolas Grekas <[email protected]>
37+
* @author André Rømcke <[email protected]>
3338
*/
3439
class RedisTagAwareAdapter extends AbstractTagAwareAdapter implements TagAwareAdapterInterface
3540
{
@@ -70,35 +75,17 @@ public function __construct($redisClient, string $namespace = '', int $defaultLi
7075
}
7176

7277
/**
73-
* This method overrides @see \Symfony\Component\Cache\Traits\RedisTrait::doSave.
74-
*
75-
* Method is overridden in order to store tag info in same pipeline as cache values.
76-
*
7778
* {@inheritdoc}
7879
*/
79-
protected function doSave(array $values, $lifetime)
80+
protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $delTagData = []): array
8081
{
81-
// Extract tag operations
82-
$tagOperations = ['sAdd' => [], 'sRem' => []];
83-
foreach ($values as $id => $value) {
84-
foreach ($value['tag-operations']['add'] as $tag => $tagId) {
85-
$tagOperations['sAdd'][$tagId][] = $id;
86-
}
87-
88-
foreach ($value['tag-operations']['remove'] as $tag => $tagId) {
89-
$tagOperations['sRem'][$tagId][] = $id;
90-
}
91-
92-
unset($value['tag-operations']);
93-
}
94-
95-
// serilize values
82+
// serialize values
9683
if (!$serialized = $this->marshaller->marshall($values, $failed)) {
9784
return $failed;
9885
}
9986

10087
// While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op
101-
$results = $this->pipeline(static function () use ($serialized, $lifetime, $tagOperations) {
88+
$results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData) {
10289
// Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one
10390
foreach ($serialized as $id => $value) {
10491
yield 'setEx' => [
@@ -109,10 +96,12 @@ protected function doSave(array $values, $lifetime)
10996
}
11097

11198
// Add and Remove Tags
112-
foreach ($tagOperations as $command => $tagSet) {
113-
foreach ($tagSet as $tagId => $ids) {
114-
yield $command => array_merge([$tagId], $ids);
115-
}
99+
foreach ($addTagData as $tagId => $ids) {
100+
yield 'sAdd' => array_merge([$tagId], $ids);
101+
}
102+
103+
foreach ($delTagData as $tagId => $ids) {
104+
yield 'sRem' => array_merge([$tagId], $ids);
116105
}
117106
});
118107

@@ -133,7 +122,7 @@ protected function doSave(array $values, $lifetime)
133122
/**
134123
* {@inheritdoc}
135124
*/
136-
protected function doDelete(array $ids, array $tagData = [])
125+
protected function doDelete(array $ids, array $tagData = []): bool
137126
{
138127
if (!$ids) {
139128
return true;
@@ -149,8 +138,8 @@ protected function doDelete(array $ids, array $tagData = [])
149138
yield 'del' => $ids;
150139
}
151140

152-
foreach ($tagData as $tagId => $idMap) {
153-
yield 'sRem' => array_merge([$tagId], $idMap);
141+
foreach ($tagData as $tagId => $idList) {
142+
yield 'sRem' => array_merge([$tagId], $idList);
154143
}
155144
})->rewind();
156145

@@ -171,7 +160,7 @@ public function doInvalidate(array $tagIds): bool
171160
}
172161
});
173162

174-
// Flatten generator result from pipleline, ignore keys (tag ids)
163+
// Flatten generator result from pipeline, ignore keys (tag ids)
175164
$ids = array_unique(array_merge(...iterator_to_array($tagIdSets, false)));
176165

177166
// Delete cache in chunks to avoid overloading the connection

0 commit comments

Comments
 (0)