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

Skip to content

Commit 9e9c725

Browse files
mpdudenicolas-grekas
authored andcommitted
[HttpCache] Hit the backend only once after waiting for the cache lock
1 parent 242e4b5 commit 9e9c725

File tree

4 files changed

+96
-12
lines changed

4 files changed

+96
-12
lines changed

HttpCache/CacheWasLockedException.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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\HttpKernel\HttpCache;
13+
14+
/**
15+
* @internal
16+
*/
17+
class CacheWasLockedException extends \Exception
18+
{
19+
}

HttpCache/HttpCache.php

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,13 @@ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_R
219219
$this->record($request, 'reload');
220220
$response = $this->fetch($request, $catch);
221221
} else {
222-
$response = $this->lookup($request, $catch);
222+
$response = null;
223+
do {
224+
try {
225+
$response = $this->lookup($request, $catch);
226+
} catch (CacheWasLockedException) {
227+
}
228+
} while (null === $response);
223229
}
224230

225231
$this->restoreResponseBody($request, $response);
@@ -576,15 +582,7 @@ protected function lock(Request $request, Response $entry): bool
576582

577583
// wait for the lock to be released
578584
if ($this->waitForLock($request)) {
579-
// replace the current entry with the fresh one
580-
$new = $this->lookup($request);
581-
$entry->headers = $new->headers;
582-
$entry->setContent($new->getContent());
583-
$entry->setStatusCode($new->getStatusCode());
584-
$entry->setProtocolVersion($new->getProtocolVersion());
585-
foreach ($new->headers->getCookies() as $cookie) {
586-
$entry->headers->setCookie($cookie);
587-
}
585+
throw new CacheWasLockedException(); // unwind back to handle(), try again
588586
} else {
589587
// backend is slow as hell, send a 503 response (to avoid the dog pile effect)
590588
$entry->setStatusCode(503);

Tests/HttpCache/HttpCacheTest.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\HttpKernel\Event\TerminateEvent;
1919
use Symfony\Component\HttpKernel\HttpCache\Esi;
2020
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
21+
use Symfony\Component\HttpKernel\HttpCache\Store;
2122
use Symfony\Component\HttpKernel\HttpCache\StoreInterface;
2223
use Symfony\Component\HttpKernel\HttpKernelInterface;
2324
use Symfony\Component\HttpKernel\Kernel;
@@ -717,6 +718,7 @@ public function testDegradationWhenCacheLocked()
717718
*/
718719
sleep(10);
719720

721+
$this->store = $this->createStore(); // create another store instance that does not hold the current lock
720722
$this->request('GET', '/');
721723
$this->assertHttpKernelIsNotCalled();
722724
$this->assertEquals(200, $this->response->getStatusCode());
@@ -735,6 +737,64 @@ public function testDegradationWhenCacheLocked()
735737
$this->assertEquals('Old response', $this->response->getContent());
736738
}
737739

740+
public function testHitBackendOnlyOnceWhenCacheWasLocked()
741+
{
742+
// Disable stale-while-revalidate, it circumvents waiting for the lock
743+
$this->cacheConfig['stale_while_revalidate'] = 0;
744+
745+
$this->setNextResponses([
746+
[
747+
'status' => 200,
748+
'body' => 'initial response',
749+
'headers' => [
750+
'Cache-Control' => 'public, no-cache',
751+
'Last-Modified' => 'some while ago',
752+
],
753+
],
754+
[
755+
'status' => 304,
756+
'body' => '',
757+
'headers' => [
758+
'Cache-Control' => 'public, no-cache',
759+
'Last-Modified' => 'some while ago',
760+
],
761+
],
762+
[
763+
'status' => 500,
764+
'body' => 'The backend should not be called twice during revalidation',
765+
'headers' => [],
766+
],
767+
]);
768+
769+
$this->request('GET', '/'); // warm the cache
770+
771+
// Use a store that simulates a cache entry being locked upon first attempt
772+
$this->store = new class(sys_get_temp_dir() . '/http_cache') extends Store {
773+
private bool $hasLock = false;
774+
775+
public function lock(Request $request): bool
776+
{
777+
$hasLock = $this->hasLock;
778+
$this->hasLock = true;
779+
780+
return $hasLock;
781+
}
782+
783+
public function isLocked(Request $request): bool
784+
{
785+
return false;
786+
}
787+
};
788+
789+
$this->request('GET', '/'); // hit the cache with simulated lock/concurrency block
790+
791+
$this->assertEquals(200, $this->response->getStatusCode());
792+
$this->assertEquals('initial response', $this->response->getContent());
793+
794+
$traces = $this->cache->getTraces();
795+
$this->assertSame(['stale', 'valid', 'store'], current($traces));
796+
}
797+
738798
public function testHitsCachedResponseWithSMaxAgeDirective()
739799
{
740800
$time = \DateTimeImmutable::createFromFormat('U', time() - 5);

Tests/HttpCache/HttpCacheTestCase.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ abstract class HttpCacheTestCase extends TestCase
3030
protected $responses;
3131
protected $catch;
3232
protected $esi;
33-
protected Store $store;
33+
protected ?Store $store = null;
3434

3535
protected function setUp(): void
3636
{
@@ -115,7 +115,9 @@ public function request($method, $uri = '/', $server = [], $cookies = [], $esi =
115115

116116
$this->kernel->reset();
117117

118-
$this->store = new Store(sys_get_temp_dir().'/http_cache');
118+
if (! $this->store) {
119+
$this->store = $this->createStore();
120+
}
119121

120122
if (!isset($this->cacheConfig['debug'])) {
121123
$this->cacheConfig['debug'] = true;
@@ -183,4 +185,9 @@ public static function clearDirectory($directory)
183185

184186
closedir($fp);
185187
}
188+
189+
protected function createStore(): Store
190+
{
191+
return new Store(sys_get_temp_dir() . '/http_cache');
192+
}
186193
}

0 commit comments

Comments
 (0)