8000 [Cache] Prevent stampede at warmup using apcu_entry() for locking · symfony/symfony@57c49de · GitHub
[go: up one dir, main page]

Skip to content

Commit 57c49de

Browse files
[Cache] Prevent stampede at warmup using apcu_entry() for locking
1 parent 7ec3d50 commit 57c49de

File tree

6 files changed

+62
-5
lines changed

6 files changed

+62
-5
lines changed

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

+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,14 @@ public static function createConnection($dsn, array $options = array())
151151
throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn));
152152
}
153153

154+
/**
155+
* {@inheritdoc}
156+
*/
157+
public function get(string $key, callable $callback, float $beta = null)
158+
{
159+
return $this->doGet($this, $key, $callback, $beta ?? 1.0, '' !== $this->namespace ? $this->getId($key) : null);
160+
}
161+
154162
/**
155163
* {@inheritdoc}
156164
*/

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ function (CacheItemInterface $innerItem, array $item) {
8686
public function get(string $key, callable $callback, float $beta = null)
8787
{
8888
if (!$this->pool instanceof CacheInterface) {
89-
return $this->doGet($this, $key, $callback, $beta ?? 1.0);
89+
return $this->doGet($this, $key, $callback, $beta ?? 1.0, $this->namespaceLen ? $this->getId($key) : null);
9090
}
9191

9292
return $this->pool->get($this->getId($key), function ($innerItem) use ($key, $callback) {

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TaggableCacheInterfac
3535
private $setCacheItemTags;
3636
private $getTagsByKey;
3737
private $invalidateTags;
38+
private $getId;
3839
private $tags;
3940
private $knownTagVersions = array();
4041
private $knownTagVersionsTtl;
@@ -105,6 +106,13 @@ function (AdapterInterface $tagsAdapter, array $tags) {
105106
null,
106107
CacheItem::class
107108
);
109+
$this->getId = \Closure::bind(
110+
function (AbstractAdapter $pool, $key) {
111+
return $pool->getId($key);
112+
},
113+
null,
114+
AbstractAdapter::class
115+
);
108116
}
109117

110118
/**
@@ -150,6 +158,22 @@ public function invalidateTags(array $tags)
150158
return $ok;
151159
}
152160

161+
/**
162+
* {@inheritdoc}
163+
*/
164+
public function get(string $key, callable $callback, float $beta = null)
165+
{
166+
if ($this->pool instanceof AbstractAdapter) {
167+
$id = ($this->getId)($this->pool, $key);
168+
} elseif ($this->pool instanceof ProxyAdapter) {
169+
$id = ((array) $this->pool)["\0Symfony\\Component\\Cache\\Adapter\\ProxyAdapter\0namespace"].$key;
170+
} else {
171+
$id = null;
172+
}
173+
174+
return $this->doGet($this, $key, $callback, $beta ?? 1.0, $id !== $key ? $id : null);
175+
}
176+
153177
/**
154178
* {@inheritdoc}
155179
*/

src/Symfony/Component/Cache/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
4.2.0
55
-----
66

7+
* added warmup-time stampede protection using `apcu_entry()` for locking when available
78
* added `CacheInterface` and `TaggableCacheInterface`, providing stampede protection via probabilistic early expiration
89
* throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
910
* deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getStats()` instead

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ public function testGetStats()
7777
$item = $cache->getItem('foo');
7878

7979
$expected = array(
80-
CacheItem::STATS_EXPIRY => 9 + time(),
8180
CacheItem::STATS_CTIME => 1000,
81+
CacheItem::STATS_EXPIRY => 8.5 + time(),
8282
);
83-
$this->assertSame($expected, $item->getStats());
83+
$this->assertEquals($expected, $item->getStats(), 'Item stats should be stored alongside with its value.', .5);
8484
}
8585

8686
public function testDefaultLifeTime()

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function get(string $key, callable $callback, float $beta = null)
3434
return $this->doGet($this, $key, $callback, $beta ?? 1.0);
3535
}
3636

37-
private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, float $beta)
37+
private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, float $beta, string $lockId = null)
3838
{
3939
$t = 0;
4040
$item = $pool->getItem($key);
@@ -64,6 +64,7 @@ private function doGet(CacheItemPoolInterface $pool, string $key, callable $call
6464
}
6565

6666
static $save = null;
67+
static $useApcu = null;
6768

6869
if (null === $save) {
6970
$save = \Closure::bind(
@@ -80,7 +81,30 @@ function (CacheItemPoolInterface $pool, CacheItemInterface $item, $value, float
8081
CacheItem::class
8182
);
8283
}
84+
if (null === $useApcu) {
85+
$useApcu = \function_exists('apcu_entry') && ini_get('apc.enabled') && ('cli' !== \PHP_SAPI || ini_get('apc.enable_cli'));
86+
}
87+
88+
if (null === $lockId || !$useApcu) {
89+
return $save($pool, $item, $callback($item), $t);
90+
}
91+
92+
$t = $t ?: microtime(true);
93+
$lockId = ':'.$lockId;
94+
$isHit = true;
95+
$value = apcu_entry($lockId, function () use ($callback, $item, &$isHit) {
96+
$isHit = false;
97+
98+
return $callback($item);
99+
}, 2);
100+
101+
if (!$isHit) {
102+
$save($pool, $item, $value, $t);
103+
// give concurrent processes 15% of the computation time to retrieve the value from APCu, up to 300ms
104+
usleep(min(300000, 150000 * (microtime(true) - $t)));
105+
apcu_delete($lockId);
106+
}
83107

84-
return $save($pool, $item, $callback($item), $t);
108+
return $value;
85109
}
86110
}

0 commit comments

Comments
 (0)
0