Refactor stale-while-revalidate code in HttpCache, add a (first?) test for it
This commit is contained in:
parent
6b4cfd6a25
commit
b14057c88a
@ -549,28 +549,22 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
// try to acquire a lock to call the backend
|
||||
$lock = $this->store->lock($request);
|
||||
|
||||
// there is already another process calling the backend
|
||||
if (true !== $lock) {
|
||||
// check if we can serve the stale entry
|
||||
if (null === $age = $entry->headers->getCacheControlDirective('stale-while-revalidate')) {
|
||||
$age = $this->options['stale_while_revalidate'];
|
||||
if (true === $lock) {
|
||||
// we have the lock, call the backend
|
||||
return false;
|
||||
}
|
||||
|
||||
if (abs($entry->getTtl()) < $age) {
|
||||
// there is already another process calling the backend
|
||||
|
||||
// May we serve a stale response?
|
||||
if ($this->mayServeStaleWhileRevalidate($entry)) {
|
||||
$this->record($request, 'stale-while-revalidate');
|
||||
|
||||
// server the stale response while there is a revalidation
|
||||
return true;
|
||||
}
|
||||
|
||||
// wait for the lock to be released
|
||||
$wait = 0;
|
||||
while ($this->store->isLocked($request) && $wait < 5000000) {
|
||||
usleep(50000);
|
||||
$wait += 50000;
|
||||
}
|
||||
|
||||
if ($wait < 5000000) {
|
||||
if ($this->waitForLock($request)) {
|
||||
// replace the current entry with the fresh one
|
||||
$new = $this->lookup($request);
|
||||
$entry->headers = $new->headers;
|
||||
@ -590,10 +584,6 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
// we have the lock, call the backend
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the Response to the cache.
|
||||
*
|
||||
@ -710,4 +700,41 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
}
|
||||
$this->traces[$request->getMethod().' '.$path][] = $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given (cached) response may be served as "stale" when a revalidation
|
||||
* is currently in progress.
|
||||
*
|
||||
* @param Response $entry
|
||||
*
|
||||
* @return bool True when the stale response may be served, false otherwise.
|
||||
*/
|
||||
private function mayServeStaleWhileRevalidate(Response $entry)
|
||||
{
|
||||
$timeout = $entry->headers->getCacheControlDirective('stale-while-revalidate');
|
||||
|
||||
if ($timeout === null) {
|
||||
$timeout = $this->options['stale_while_revalidate'];
|
||||
}
|
||||
|
||||
return abs($entry->getTtl()) < $timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the store to release a locked entry.
|
||||
*
|
||||
* @param Request $request The request to wait for
|
||||
*
|
||||
* @return bool True if the lock was released before the internal timeout was hit; false if the wait timeout was exceeded.
|
||||
*/
|
||||
private function waitForLock(Request $request)
|
||||
{
|
||||
$wait = 0;
|
||||
while ($this->store->isLocked($request) && $wait < 5000000) {
|
||||
usleep(50000);
|
||||
$wait += 50000;
|
||||
}
|
||||
|
||||
return $wait < 5000000;
|
||||
}
|
||||
}
|
||||
|
@ -562,6 +562,42 @@ class HttpCacheTest extends HttpCacheTestCase
|
||||
$this->assertEquals('Hello World', $this->response->getContent());
|
||||
}
|
||||
|
||||
public function testDegradationWhenCacheLocked()
|
||||
{
|
||||
$this->cacheConfig['stale_while_revalidate'] = 10;
|
||||
|
||||
// The prescence of Last-Modified makes this cacheable (because Response::isValidateable() then).
|
||||
$this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=5', 'Last-Modified' => 'some while ago'), 'Old response');
|
||||
$this->request('GET', '/'); // warm the cache
|
||||
|
||||
// Now, lock the cache
|
||||
$concurrentRequest = Request::create('/', 'GET');
|
||||
$this->store->lock($concurrentRequest);
|
||||
|
||||
/*
|
||||
* After 10s, the cached response has become stale. Yet, we're still within the "stale_while_revalidate"
|
||||
* timeout so we may serve the stale response.
|
||||
*/
|
||||
sleep(10);
|
||||
|
||||
$this->request('GET', '/');
|
||||
$this->assertHttpKernelIsNotCalled();
|
||||
$this->assertEquals(200, $this->response->getStatusCode());
|
||||
$this->assertTraceContains('stale-while-revalidate');
|
||||
$this->assertEquals('Old response', $this->response->getContent());
|
||||
|
||||
/*
|
||||
* Another 10s later, stale_while_revalidate is over. Resort to serving the old response, but
|
||||
* do so with a "server unavailable" message.
|
||||
*/
|
||||
sleep(10);
|
||||
|
||||
$this->request('GET', '/');
|
||||
$this->assertHttpKernelIsNotCalled();
|
||||
$this->assertEquals(503, $this->response->getStatusCode());
|
||||
$this->assertEquals('Old response', $this->response->getContent());
|
||||
}
|
||||
|
||||
public function testHitsCachedResponseWithSMaxAgeDirective()
|
||||
{
|
||||
$time = \DateTime::createFromFormat('U', time() - 5);
|
||||
|
@ -29,6 +29,10 @@ class HttpCacheTestCase extends TestCase
|
||||
protected $responses;
|
||||
protected $catch;
|
||||
protected $esi;
|
||||
|
||||
/**
|
||||
* @var Store
|
||||
*/
|
||||
protected $store;
|
||||
|
||||
protected function setUp()
|
||||
|
Reference in New Issue
Block a user