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

Skip to content

Commit b14057c

Browse files
mpdudefabpot
authored andcommitted
Refactor stale-while-revalidate code in HttpCache, add a (first?) test for it
1 parent 6b4cfd6 commit b14057c

File tree

3 files changed

+103
-36
lines changed

3 files changed

+103
-36
lines changed

src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -549,49 +549,39 @@ protected function lock(Request $request, Response $entry)
549549
// try to acquire a lock to call the backend
550550
$lock = $this->store->lock($request);
551551

552+
if (true === $lock) {
553+
// we have the lock, call the backend
554+
return false;
555+
}
556+
552557
// there is already another process calling the backend
553-
if (true !== $lock) {
554-
// check if we can serve the stale entry
555-
if (null === $age = $entry->headers->getCacheControlDirective('stale-while-revalidate')) {
556-
$age = $this->options['stale_while_revalidate'];
557-
}
558558

559-
if (abs($entry->getTtl()) < $age) {
560-
$this->record($request, 'stale-while-revalidate');
559+
// May we serve a stale response?
560+
if ($this->mayServeStaleWhileRevalidate($entry)) {
561+
$this->record($request, 'stale-while-revalidate');
561562

562-
// server the stale response while there is a revalidation
563-
return true;
564-
}
565-
566-
// wait for the lock to be released
567-
$wait = 0;
568-
while ($this->store->isLocked($request) && $wait < 5000000) {
569-
usleep(50000);
570-
$wait += 50000;
571-
}
563+
return true;
564+
}
572565

573-
if ($wait < 5000000) {
574-
// replace the current entry with the fresh one
575-
$new = $this->lookup($request);
576-
$entry->headers = $new->headers;
577-
$entry->setContent($new->getContent());
578-
$entry->setStatusCode($new->getStatusCode());
579-
$entry->setProtocolVersion($new->getProtocolVersion());
580-
foreach ($new->headers->getCookies() as $cookie) {
581-
$entry->headers->setCookie($cookie);
582-
}
583-
} else {
584-
// backend is slow as hell, send a 503 response (to avoid the dog pile effect)
585-
$entry->setStatusCode(503);
586-
$entry->setContent('503 Service Unavailable');
587-
$entry->headers->set('Retry-After', 10);
566+
// wait for the lock to be released
567+
if ($this->waitForLock($request)) {
568+
// replace the current entry with the fresh one
569+
$new = $this->lookup($request);
570+
$entry->headers = $new->headers;
571+
$entry->setContent($new->getContent());
572+
$entry->setStatusCode($new->getStatusCode());
573+
$entry->setProtocolVersion($new->getProtocolVersion());
574+
foreach ($new->headers->getCookies() as $cookie) {
575+
$entry->headers->setCookie($cookie);
588576
}
589-
590-
return true;
577+
} else {
578+
// backend is slow as hell, send a 503 response (to avoid the dog pile effect)
579+
$entry->setStatusCode(503);
580+
$entry->setContent('503 Service Unavailable');
581+
$entry->headers->set('Retry-After', 10);
591582
}
592583

593-
// we have the lock, call the backend
594-
return false;
584+
return true;
595585
}
596586

597587
/**
@@ -710,4 +700,41 @@ private function record(Request $request, $event)
710700
}
711701
$this->traces[$request->getMethod().' '.$path][] = $event;
712702
}
703+
704+
/**
705+
* Checks whether the given (cached) response may be served as "stale" when a revalidation
706+
* is currently in progress.
707+
*
708+
* @param Response $entry
709+
*
710+
* @return bool True when the stale response may be served, false otherwise.
711+
*/
712+
private function mayServeStaleWhileRevalidate(Response $entry)
713+
{
714+
$timeout = $entry->headers->getCacheControlDirective('stale-while-revalidate');
715+
716+
if ($timeout === null) {
717+
$timeout = $this->options['stale_while_revalidate'];
718+
}
719+
720+
return abs($entry->getTtl()) < $timeout;
721+
}
722+
723+
/**
724+
* Waits for the store to release a locked entry.
725+
*
726+
* @param Request $request The request to wait for
727+
*
728+
* @return bool True if the lock was released before the internal timeout was hit; false if the wait timeout was exceeded.
729+
*/
730+
private function waitForLock(Request $request)
731+
{
732+
$wait = 0;
733+
while ($this->store->isLocked($request) && $wait < 5000000) {
734+
usleep(50000);
735+
$wait += 50000;
736+
}
737+
738+
return $wait < 5000000;
739+
}
713740
}

src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,42 @@ public function testHitsCachedResponseWithMaxAgeDirective()
562562
$this->assertEquals('Hello World', $this->response->getContent());
563563
}
564564

565+
public function testDegradationWhenCacheLocked()
566+
{
567+
$this->cacheConfig['stale_while_revalidate'] = 10;
568+
569+
// The prescence of Last-Modified makes this cacheable (because Response::isValidateable() then).
570+
$this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=5', 'Last-Modified' => 'some while ago'), 'Old response');
571+
$this->request('GET', '/'); // warm the cache
572+
573+
// Now, lock the cache
574+
$concurrentRequest = Request::create('/', 'GET');
575+
$this->store->lock($concurrentRequest);
576+
577+
/*
578+
* After 10s, the cached response has become stale. Yet, we're still within the "stale_while_revalidate"
579+
* timeout so we may serve the stale response.
580+
*/
581+
sleep(10);
582+
583+
$this->request('GET', '/');
584+
$this->assertHttpKernelIsNotCalled();
585+
$this->assertEquals(200, $this->response->getStatusCode());
586+
$this->assertTraceContains('stale-while-revalidate');
587+
$this->assertEquals('Old response', $this->response->getContent());
588+
589+
/*
590+
* Another 10s later, stale_while_revalidate is over. Resort to serving the old response, but
591+
* do so with a "server unavailable" message.
592+
*/
593+
sleep(10);
594+
595+
$this->request('GET', '/');
596+
$this->assertHttpKernelIsNotCalled();
597+
$this->assertEquals(503, $this->response->getStatusCode());
598+
$this->assertEquals('Old response', $this->response->getContent());
599+
}
600+
565601
public function testHitsCachedResponseWithSMaxAgeDirective()
566602
{
567603
$time = \DateTime::createFromFormat('U', time() - 5);

src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ class HttpCacheTestCase extends TestCase
2929
protected $responses;
3030
protected $catch;
3131
protected $esi;
32+
33+
/**
34+
* @var Store
35+
*/
3236
protected $store;
3337

3438
protected function setUp()

0 commit comments

Comments
 (0)