Skip to content

Commit 38b6666

Browse files
Merge branch '6.4' into 7.2
* 6.4: Silence E_DEPRECATED and E_USER_DEPRECATED [HttpCache] Hit the backend only once after waiting for the cache lock fix compatibility with Symfony 7.4
2 parents 36cae13 + 9e9c725 commit 38b6666

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 <fabien@symfony.com>
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
@@ -210,7 +210,13 @@ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_R
210210
$this->record($request, 'reload');
211211
$response = $this->fetch($request, $catch);
212212
} else {
213-
$response = $this->lookup($request, $catch);
213+
$response = null;
214+
do {
215+
try {
216+
$response = $this->lookup($request, $catch);
217+
} catch (CacheWasLockedException) {
218+
}
219+
} while (null === $response);
214220
}
215221

216222
$this->restoreResponseBody($request, $response);
@@ -560,15 +566,7 @@ protected function lock(Request $request, Response $entry): bool
560566

561567
// wait for the lock to be released
562568
if ($this->waitForLock($request)) {
563-
// replace the current entry with the fresh one
564-
$new = $this->lookup($request);
565-
$entry->headers = $new->headers;
566-
$entry->setContent($new->getContent());
567-
$entry->setStatusCode($new->getStatusCode());
568-
$entry->setProtocolVersion($new->getProtocolVersion());
569-
foreach ($new->headers->getCookies() as $cookie) {
570-
$entry->headers->setCookie($cookie);
571-
}
569+
throw new CacheWasLockedException(); // unwind back to handle(), try again
572570
} else {
573571
// backend is slow as hell, send a 503 response (to avoid the dog pile effect)
574572
$entry->setStatusCode(503);

Tests/HttpCache/HttpCacheTest.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\HttpKernel\Event\TerminateEvent;
1818
use Symfony\Component\HttpKernel\HttpCache\Esi;
1919
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
20+
use Symfony\Component\HttpKernel\HttpCache\Store;
2021
use Symfony\Component\HttpKernel\HttpCache\StoreInterface;
2122
use Symfony\Component\HttpKernel\HttpKernelInterface;
2223
use Symfony\Component\HttpKernel\Kernel;
@@ -662,6 +663,7 @@ public function testDegradationWhenCacheLocked()
662663
*/
663664
sleep(10);
664665

666+
$this->store = $this->createStore(); // create another store instance that does not hold the current lock
665667
$this->request('GET', '/');
666668
$this->assertHttpKernelIsNotCalled();
667669
$this->assertEquals(200, $this->response->getStatusCode());
@@ -680,6 +682,64 @@ public function testDegradationWhenCacheLocked()
680682
$this->assertEquals('Old response', $this->response->getContent());
681683
}
682684

685+
public function testHitBackendOnlyOnceWhenCacheWasLocked()
686+
{
687+
// Disable stale-while-revalidate, it circumvents waiting for the lock
688+
$this->cacheConfig['stale_while_revalidate'] = 0;
689+
690+
$this->setNextResponses([
691+
[
692+
'status' => 200,
693+
'body' => 'initial response',
694+
'headers' => [
695+
'Cache-Control' => 'public, no-cache',
696+
'Last-Modified' => 'some while ago',
697+
],
698+
],
699+
[
700+
'status' => 304,
701+
'body' => '',
702+
'headers' => [
703+
'Cache-Control' => 'public, no-cache',
704+
'Last-Modified' => 'some while ago',
705+
],
706+
],
707+
[
708+
'status' => 500,
709+
'body' => 'The backend should not be called twice during revalidation',
710+
'headers' => [],
711+
],
712+
]);
713+
714+
$this->request('GET', '/'); // warm the cache
715+
716+
// Use a store that simulates a cache entry being locked upon first attempt
717+
$this->store = new class(sys_get_temp_dir() . '/http_cache') extends Store {
718+
private bool $hasLock = false;
719+
720+
public function lock(Request $request): bool
721+
{
722+
$hasLock = $this->hasLock;
723+
$this->hasLock = true;
724+
725+
return $hasLock;
726+
}
727+
728+
public function isLocked(Request $request): bool
729+
{
730+
return false;
731+
}
732+
};
733+
734+
$this->request('GET', '/'); // hit the cache with simulated lock/concurrency block
735+
736+
$this->assertEquals(200, $this->response->getStatusCode());
737+
$this->assertEquals('initial response', $this->response->getContent());
738+
739+
$traces = $this->cache->getTraces();
740+
$this->assertSame(['stale', 'valid', 'store'], current($traces));
741+
}
742+
683743
public function testHitsCachedResponseWithSMaxAgeDirective()
684744
{
685745
$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)