From d39a7acbf83354f5799a15243db4f87c4c6a3613 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 12 Jun 2025 14:21:06 +0200 Subject: [PATCH 1/3] fix compatibility with Symfony 7.4 --- src/Symfony/Component/Runtime/SymfonyRuntime.php | 6 +++++- src/Symfony/Component/Runtime/Tests/phpt/application.php | 6 +++++- src/Symfony/Component/Runtime/Tests/phpt/command_list.php | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Runtime/SymfonyRuntime.php b/src/Symfony/Component/Runtime/SymfonyRuntime.php index b8ba83980bc43..28918155f4412 100644 --- a/src/Symfony/Component/Runtime/SymfonyRuntime.php +++ b/src/Symfony/Component/Runtime/SymfonyRuntime.php @@ -144,7 +144,11 @@ public function getRunner(?object $application): RunnerInterface if (!$application->getName() || !$console->has($application->getName())) { $application->setName($_SERVER['argv'][0]); - $console->add($application); + if (method_exists($console, 'addCommand')) { + $console->addCommand($application); + } else { + $console->add($application); + } } $console->setDefaultCommand($application->getName(), true); diff --git a/src/Symfony/Component/Runtime/Tests/phpt/application.php b/src/Symfony/Component/Runtime/Tests/phpt/application.php index ca2de555edfb7..b51947c2afaf1 100644 --- a/src/Symfony/Component/Runtime/Tests/phpt/application.php +++ b/src/Symfony/Component/Runtime/Tests/phpt/application.php @@ -25,7 +25,11 @@ }); $app = new Application(); - $app->add($command); + if (method_exists($app, 'addCommand')) { + $app->addCommand($command); + } else { + $app->add($command); + } $app->setDefaultCommand('go', true); return $app; diff --git a/src/Symfony/Component/Runtime/Tests/phpt/command_list.php b/src/Symfony/Component/Runtime/Tests/phpt/command_list.php index 929b4401e86b9..aa40eda627151 100644 --- a/src/Symfony/Component/Runtime/Tests/phpt/command_list.php +++ b/src/Symfony/Component/Runtime/Tests/phpt/command_list.php @@ -23,7 +23,11 @@ $command->setName('my_command'); [$cmd, $args] = $runtime->getResolver(require __DIR__.'/command.php')->resolve(); - $app->add($cmd(...$args)); + if (method_exists($app, 'addCommand')) { + $app->addCommand($cmd(...$args)); + } else { + $app->add($cmd(...$args)); + } return $app; }; From df064b0369b1e084402bd52170a393627c21c48c Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 21 May 2025 14:18:44 +0200 Subject: [PATCH 2/3] [HttpCache] Hit the backend only once after waiting for the cache lock --- .../HttpCache/CacheWasLockedException.php | 19 ++++++ .../HttpKernel/HttpCache/HttpCache.php | 18 +++--- .../Tests/HttpCache/HttpCacheTest.php | 60 +++++++++++++++++++ .../Tests/HttpCache/HttpCacheTestCase.php | 11 +++- 4 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 src/Symfony/Component/HttpKernel/HttpCache/CacheWasLockedException.php diff --git a/src/Symfony/Component/HttpKernel/HttpCache/CacheWasLockedException.php b/src/Symfony/Component/HttpKernel/HttpCache/CacheWasLockedException.php new file mode 100644 index 0000000000000..f13946ad71a68 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/HttpCache/CacheWasLockedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +/** + * @internal + */ +class CacheWasLockedException extends \Exception +{ +} diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index 3b484e5c3e1ec..bce0e99b5eca3 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -219,7 +219,13 @@ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_R $this->record($request, 'reload'); $response = $this->fetch($request, $catch); } else { - $response = $this->lookup($request, $catch); + $response = null; + do { + try { + $response = $this->lookup($request, $catch); + } catch (CacheWasLockedException) { + } + } while (null === $response); } $this->restoreResponseBody($request, $response); @@ -576,15 +582,7 @@ protected function lock(Request $request, Response $entry): bool // wait for the lock to be released if ($this->waitForLock($request)) { - // replace the current entry with the fresh one - $new = $this->lookup($request); - $entry->headers = $new->headers; - $entry->setContent($new->getContent()); - $entry->setStatusCode($new->getStatusCode()); - $entry->setProtocolVersion($new->getProtocolVersion()); - foreach ($new->headers->getCookies() as $cookie) { - $entry->headers->setCookie($cookie); - } + throw new CacheWasLockedException(); // unwind back to handle(), try again } else { // backend is slow as hell, send a 503 response (to avoid the dog pile effect) $entry->setStatusCode(503); diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php index a72c08b8723a2..39f00a0139a25 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php @@ -18,6 +18,7 @@ use Symfony\Component\HttpKernel\Event\TerminateEvent; use Symfony\Component\HttpKernel\HttpCache\Esi; use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\Store; use Symfony\Component\HttpKernel\HttpCache\StoreInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Kernel; @@ -717,6 +718,7 @@ public function testDegradationWhenCacheLocked() */ sleep(10); + $this->store = $this->createStore(); // create another store instance that does not hold the current lock $this->request('GET', '/'); $this->assertHttpKernelIsNotCalled(); $this->assertEquals(200, $this->response->getStatusCode()); @@ -735,6 +737,64 @@ public function testDegradationWhenCacheLocked() $this->assertEquals('Old response', $this->response->getContent()); } + public function testHitBackendOnlyOnceWhenCacheWasLocked() + { + // Disable stale-while-revalidate, it circumvents waiting for the lock + $this->cacheConfig['stale_while_revalidate'] = 0; + + $this->setNextResponses([ + [ + 'status' => 200, + 'body' => 'initial response', + 'headers' => [ + 'Cache-Control' => 'public, no-cache', + 'Last-Modified' => 'some while ago', + ], + ], + [ + 'status' => 304, + 'body' => '', + 'headers' => [ + 'Cache-Control' => 'public, no-cache', + 'Last-Modified' => 'some while ago', + ], + ], + [ + 'status' => 500, + 'body' => 'The backend should not be called twice during revalidation', + 'headers' => [], + ], + ]); + + $this->request('GET', '/'); // warm the cache + + // Use a store that simulates a cache entry being locked upon first attempt + $this->store = new class(sys_get_temp_dir() . '/http_cache') extends Store { + private bool $hasLock = false; + + public function lock(Request $request): bool + { + $hasLock = $this->hasLock; + $this->hasLock = true; + + return $hasLock; + } + + public function isLocked(Request $request): bool + { + return false; + } + }; + + $this->request('GET', '/'); // hit the cache with simulated lock/concurrency block + + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('initial response', $this->response->getContent()); + + $traces = $this->cache->getTraces(); + $this->assertSame(['stale', 'valid', 'store'], current($traces)); + } + public function testHitsCachedResponseWithSMaxAgeDirective() { $time = \DateTimeImmutable::createFromFormat('U', time() - 5); diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php index 26a29f16b2b75..88f6bed56f4cf 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php @@ -30,7 +30,7 @@ abstract class HttpCacheTestCase extends TestCase protected $responses; protected $catch; protected $esi; - protected Store $store; + protected ?Store $store = null; protected function setUp(): void { @@ -115,7 +115,9 @@ public function request($method, $uri = '/', $server = [], $cookies = [], $esi = $this->kernel->reset(); - $this->store = new Store(sys_get_temp_dir().'/http_cache'); + if (! $this->store) { + $this->store = $this->createStore(); + } if (!isset($this->cacheConfig['debug'])) { $this->cacheConfig['debug'] = true; @@ -183,4 +185,9 @@ public static function clearDirectory($directory) closedir($fp); } + + protected function createStore(): Store + { + return new Store(sys_get_temp_dir() . '/http_cache'); + } } From f21b2f4df21c52a17bcb8f26c623f55271b8d951 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 13 Jun 2025 09:15:29 +0200 Subject: [PATCH 3/3] Silence E_DEPRECATED and E_USER_DEPRECATED --- src/Symfony/Component/ErrorHandler/Debug.php | 2 +- src/Symfony/Component/Runtime/Internal/BasicErrorHandler.php | 2 +- src/Symfony/Component/Runtime/Internal/SymfonyErrorHandler.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/ErrorHandler/Debug.php b/src/Symfony/Component/ErrorHandler/Debug.php index d54a38c4cac12..b090040d024b4 100644 --- a/src/Symfony/Component/ErrorHandler/Debug.php +++ b/src/Symfony/Component/ErrorHandler/Debug.php @@ -20,7 +20,7 @@ class Debug { public static function enable(): ErrorHandler { - error_reporting(-1); + error_reporting(\E_ALL & ~\E_DEPRECATED & ~\E_USER_DEPRECATED); if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { ini_set('display_errors', 0); diff --git a/src/Symfony/Component/Runtime/Internal/BasicErrorHandler.php b/src/Symfony/Component/Runtime/Internal/BasicErrorHandler.php index a252814570f2e..c0c290e686800 100644 --- a/src/Symfony/Component/Runtime/Internal/BasicErrorHandler.php +++ b/src/Symfony/Component/Runtime/Internal/BasicErrorHandler.php @@ -20,7 +20,7 @@ class BasicErrorHandler { public static function register(bool $debug): void { - error_reporting(-1); + error_reporting(\E_ALL & ~\E_DEPRECATED & ~\E_USER_DEPRECATED); if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { ini_set('display_errors', $debug); diff --git a/src/Symfony/Component/Runtime/Internal/SymfonyErrorHandler.php b/src/Symfony/Component/Runtime/Internal/SymfonyErrorHandler.php index 0dfc7de0ca7a0..47c67605b0430 100644 --- a/src/Symfony/Component/Runtime/Internal/SymfonyErrorHandler.php +++ b/src/Symfony/Component/Runtime/Internal/SymfonyErrorHandler.php @@ -30,7 +30,7 @@ public static function register(bool $debug): void return; } - error_reporting(-1); + error_reporting(\E_ALL & ~\E_DEPRECATED & ~\E_USER_DEPRECATED); if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { ini_set('display_errors', $debug);