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

Skip to content

Commit 61af0e3

Browse files
bug #27007 [Cache] TagAwareAdapterInterface::invalidateTags() should commit deferred items (nicolas-grekas)
This PR was merged into the 3.4 branch. Discussion ---------- [Cache] TagAwareAdapterInterface::invalidateTags() should commit deferred items | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - I noticed that we have a race condition window when invalidating tags. This PR fixes it by making `invalidateTags()` commit deferred items before actually invalidating tags on the back end. Deferred items are stored with the future version of the invalidated tags so that they can be fetched by concurrent processes and considered valid before and after the invalidation is committed. Commits ------- 212c469 [Cache] TagAwareAdapterInterface::invalidateTags() should commit deferred items
2 parents e1ca89b + 212c469 commit 61af0e3

File tree

2 files changed

+97
-42
lines changed

2 files changed

+97
-42
lines changed

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

Lines changed: 81 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,14 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
3333
private $getTagsByKey;
3434
private $invalidateTags;
3535
private $tags;
36+
private $knownTagVersions = array();
37+
private $knownTagVersionsTtl;
3638

37-
public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null)
39+
public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, $knownTagVersionsTtl = 0.15)
3840
{
3941
$this->pool = $itemsPool;
4042
$this->tags = $tagsPool ?: $itemsPool;
43+
$this->knownTagVersionsTtl = $knownTagVersionsTtl;
4144
$this->createCacheItem = \Closure::bind(
4245
function ($key, $value, CacheItem $protoItem) {
4346
$item = new CacheItem();
@@ -87,8 +90,7 @@ function ($deferred) {
8790
);
8891
$this->invalidateTags = \Closure::bind(
8992
function (AdapterInterface $tagsAdapter, array $tags) {
90-
foreach ($tagsAdapter->getItems($tags) as $v) {
91-
$v->set(1 + (int) $v->get());
93+
foreach ($tags as $v) {
9294
$v->defaultLifetime = 0;
9395
$v->expiry = null;
9496
$tagsAdapter->saveDeferred($v);
@@ -106,14 +108,42 @@ function (AdapterInterface $tagsAdapter, array $tags) {
106108
*/
107109
public function invalidateTags(array $tags)
108110
{
109-
foreach ($tags as $k => $tag) {
110-
if ('' !== $tag && \is_string($tag)) {
111-
$tags[$k] = $tag.static::TAGS_PREFIX;
111+
$ok = true;
112+
$tagsByKey = array();
113+
$invalidatedTags = array();
114+
foreach ($tags as $tag) {
115+
CacheItem::validateKey($tag);
116+
$invalidatedTags[$tag] = 0;
117+
}
118+
119+
if ($this->deferred) {
120+
$items = $this->deferred;
121+
foreach ($items as $key => $item) {
122+
if (!$this->pool->saveDeferred($item)) {
123+
unset($this->deferred[$key]);
124+
$ok = false;
125+
}
112126
}
127+
128+
$f = $this->getTagsByKey;
129+
$tagsByKey = $f($items);
130+
$this->deferred = array();
131+
}
132+
133+
$tagVersions = $this->getTagVersions($tagsByKey, $invalidatedTags);
134+
$f = $this->createCacheItem;
135+
136+
foreach ($tagsByKey as $key => $tags) {
137+
$this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key]));
138+
}
139+
$ok = $this->pool->commit() && $ok;
140+
141+
if ($invalidatedTags) {
142+
$f = $this->invalidateTags;
143+
$ok = $f($this->tags, $invalidatedTags) && $ok;
113144
}
114-
$f = $this->invalidateTags;
115145

116-
return $f($this->tags, $tags);
146+
return $ok;
117147
}
118148

119149
/**
@@ -132,7 +162,7 @@ public function hasItem($key)
132162
}
133163

134164
foreach ($this->getTagVersions(array($itemTags)) as $tag => $version) {
135-
if ($itemTags[$tag] !== $version) {
165+
if ($itemTags[$tag] !== $version && 1 !== $itemTags[$tag] - $version) {
136166
return false;
137167
}
138168
}
@@ -241,29 +271,7 @@ public function saveDeferred(CacheItemInterface $item)
241271
*/
242272
public function commit()
243273
{
244-
$ok = true;
245-
246-
if ($this->deferred) {
247-
$items = $this->deferred;
248-
foreach ($items as $key => $item) {
249-
if (!$this->pool->saveDeferred($item)) {
250-
unset($this->deferred[$key]);
251-
$ok = false;
252-
}
253-
}
254-
255-
$f = $this->getTagsByKey;
256-
$tagsByKey = $f($items);
257-
$this->deferred = array();
258-
$tagVersions = $this->getTagVersions($tagsByKey);
259-
$f = $this->createCacheItem;
260-
261-
foreach ($tagsByKey as $key => $tags) {
262-
$this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key]));
263-
}
264-
}
265-
266-
return $this->pool->commit() && $ok;
274+
return $this->invalidateTags(array());
267275
}
268276

269277
public function __destruct()
@@ -294,7 +302,7 @@ private function generateItems($items, array $tagKeys)
294302

295303
foreach ($itemTags as $key => $tags) {
296304
foreach ($tags as $tag => $version) {
297-
if ($tagVersions[$tag] !== $version) {
305+
if ($tagVersions[$tag] !== $version && 1 !== $version - $tagVersions[$tag]) {
298306
unset($itemTags[$key]);
299307
continue 2;
300308
}
@@ -310,23 +318,54 @@ private function generateItems($items, array $tagKeys)
310318
}
311319
}
312320

313-
private function getTagVersions(array $tagsByKey)
321+
private function getTagVersions(array $tagsByKey, array &$invalidatedTags = array())
314322
{
315-
$tagVersions = array();
323+
$tagVersions = $invalidatedTags;
316324

317325
foreach ($tagsByKey as $tags) {
318326
$tagVersions += $tags;
319327
}
320328

321-
if ($tagVersions) {
322-
$tags = array();
323-
foreach ($tagVersions as $tag => $version) {
324-
$tagVersions[$tag] = $tag.static::TAGS_PREFIX;
325-
$tags[$tag.static::TAGS_PREFIX] = $tag;
329+
if (!$tagVersions) {
330+
return array();
331+
}
332+
333+
if (!$fetchTagVersions = 1 !== \func_num_args()) {
334+
foreach ($tagsByKey as $tags) {
335+
foreach ($tags as $tag => $version) {
336+
if ($tagVersions[$tag] > $version) {
337+
$tagVersions[$tag] = $version;
338+
}
339+
}
340+
}
341+
}
342+
343+
$now = microtime(true);
344+
$tags = array();
345+
foreach ($tagVersions as $tag => $version) {
346+
$tags[$tag.static::TAGS_PREFIX] = $tag;
347+
if ($fetchTagVersions || !isset($this->knownTagVersions[$tag])) {
348+
continue;
349+
}
350+
$version -= $this->knownTagVersions[$tag][1];
351+
if ((0 !== $version && 1 !== $version) || $this->knownTagVersionsTtl > $now - $this->knownTagVersions[$tag][0]) {
352+
// reuse previously fetched tag versions up to the ttl, unless we are storing items or a potential miss arises
353+
$fetchTagVersions = true;
354+
} else {
355+
$this->knownTagVersions[$tag][1] += $version;
326356
}
327-
foreach ($this->tags->getItems($tagVersions) as $tag => $version) {
328-
$tagVersions[$tags[$tag]] = $version->get() ?: 0;
357+
}
358+
359+
if (!$fetchTagVersions) {
360+
return $tagVersions;
361+
}
362+
363+
foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) {
364+
$tagVersions[$tag = $tags[$tag]] = $version->get() ?: 0;
365+
if (isset($invalidatedTags[$tag])) {
366+
$invalidatedTags[$tag] = $version->set(++$tagVersions[$tag]);
329367
}
368+
$this->knownTagVersions[$tag] = array($now, $tagVersions[$tag]);
330369
}
331370

332371
return $tagVersions;

src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ public function testInvalidateTags()
7171
$this->assertTrue($pool->getItem('foo')->isHit());
7272
}
7373

74+
public function testInvalidateCommits()
75+
{
76+
$pool1 = $this->createCachePool();
77+
78+
$foo = $pool1->getItem('foo');
79+
$foo->tag('tag');
80+
81+
$pool1->saveDeferred($foo->set('foo'));
82+
$pool1->invalidateTags(array('tag'));
83+
84+
$pool2 = $this->createCachePool();
85+
$foo = $pool2->getItem('foo');
86+
87+
$this->assertTrue($foo->isHit());
88+
}
89+
7490
public function testTagsAreCleanedOnSave()
7591
{
7692
$pool = $this->createCachePool();

0 commit comments

Comments
 (0)