Skip to content

[HttpKernel] Fix stale-if-error behavior, add tests #35305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 30, 2020

Conversation

mpdude
Copy link
Contributor

@mpdude mpdude commented Jan 10, 2020

Q A
Branch? 3.4
Bug fix? yes
New feature? no
Deprecations? no
Tickets #24248
License MIT
Doc PR

This PR adds the first tests for stale-if-error logic in HttpCache.

It also fixes an observation from #24248: For responses that have been cached as public with an ETag but without a lifetime, in case of an error the stale response will be served forever (= as long as the error persists), even beyond the configured stale-if-error grace period.

Furthermore, it tries to improve compliance with RFC 7234: Stale responses must not be sent (under no condition) if one of

  • no-cache
  • must-revalidate
  • proxy-revalidate or
  • s-maxage (sic) is present.

This can be found in the corresponding chapters of Section 5.2.2 for these directives, but is also summarized in Section 4.2.4 as

A cache MUST NOT generate a stale response if it is prohibited by an explicit in-protocol directive (e.g., by a "no-store" or "no-cache" cache directive, a "must-revalidate" cache-response-directive, or an applicable "s-maxage" or "proxy-revalidate" cache-response-directive; see Section 5.2.2).

Because disabling of stale-if-error for s-maxage responses probably has a big impact on the usefulness of that feature in practice, it has to be enabled explicitly with a new config setting strict_smaxage (defaulting to false).

@mpdude mpdude changed the title [HttpCache] Fix stale-if-error behaviour, add tests [HttpCache] Fix stale-if-error behavior, add tests Jan 10, 2020
@mpdude mpdude changed the title [HttpCache] Fix stale-if-error behavior, add tests [HttpKernel] Fix stale-if-error behavior, add tests Jan 10, 2020
&& \in_array($response->getStatusCode(), [500, 502, 503, 504])
&& !$entry->headers->hasCacheControlDirective('no-cache')
&& !$entry->mustRevalidate()
&& !$entry->headers->hasCacheControlDirective('s-maxage')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get it. If you bypass it when s-maxage is set, that limits the usefulness of the feature drastically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for me.

Also see the last sentence in httpwg/http-core#264 (comment) and verify at https://tools.ietf.org/html/rfc7234#section-5.2.2.9.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's revert that part.

Copy link
Contributor Author

@mpdude mpdude Jan 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean only for smax-age, right? I'm fine with that.

I'll also see if i can get clarifying comments from the RFC editors. Guess the bottom line is that max-age + public != s-maxage (opposed to what I always thought).

@mpdude mpdude force-pushed the stale-if-validate-tests branch from fe0eb52 to d8bbeb1 Compare January 11, 2020 10:35
@mpdude
Copy link
Contributor Author

mpdude commented Jan 11, 2020

@fabpot I made strict s-maxage compliance a config switch, defaulting to turning it off (BC). WDYT?

@fabpot
Copy link
Member

fabpot commented Jan 11, 2020

Not sure about the switch. In general, I tend to avoid making something configurable when it's not needed. I've one additional question: how does Varnish work? I think doing the same as Varnish is the way to go. WDYT?

@mpdude
Copy link
Contributor Author

mpdude commented Jan 11, 2020

Personally, I have never used Varnish because the HttpCache does such a great job. But I will give their Docker image with the default configuration a try and report back.

@nicolas-grekas nicolas-grekas added this to the 3.4 milestone Jan 11, 2020
@mpdude
Copy link
Contributor Author

mpdude commented Jan 13, 2020

TL;DR:

I may be doing something wrong or misinterpreting the results, but it seems Varnish does not do stale-if-error, at least not with the default config and/or triggered by the stale-if-error response header.

It always does background validation, so errors show up on the second-next request only.

My tests were with public, max-age=2, stale-if-error=60, because that – in theory – should allow for serving stale responses.


Here is the setup to reproduce and/or test it, in case someone wants to tinker with it. You can try arbitrary Cache-Control settings from the curl command.

$ cd $(mktemp -d)
$ docker run --rm varnish -V
varnishd (varnish-6.3.1 revision 6e96ff048692235e64565211a38c41432a26c055)
Copyright (c) 2006 Verdens Gang AS
Copyright (c) 2006-2019 Varnish Software AS
$ cat <<'EOF' > index.php
<?php

if (file_exists($_REQUEST['id'])) {
    header('HTTP/1.1 500 Internal server error');
    exit;
}

touch($_REQUEST['id']);
unset($_REQUEST['id']);

$cacheControl = 'Cache-Control: public';

foreach ($_REQUEST as $key => $value) {
    $cacheControl .= ", $key";
    if ($value) {
        $cacheControl .= "=$value";
    }
}

header('HTTP/1.1 200 OK');
header($cacheControl);
EOF
$ php -S 0.0.0.0:8000 index.php &
[1] 8299
PHP 7.2.24-0ubuntu0.18.04.1 Development Server started at Mon Jan 13 07:28:37 2020
Listening on http://0.0.0.0:8000
Document root is /tmp/tmp.PxbY0DOltD
Press Ctrl-C to quit.
$ cat <<'EOF' > default.vcl
vcl 4.0;

backend default {
  .host = "localhost:8000";
}
EOF
$ docker run -d --rm -v $(pwd)/default.vcl:/etc/varnish/default.vcl:ro --tmpfs /usr/local/var/varnish:exec -p 8001:80  varnish
d62ec6ff328d332afb23674bc49bbf29c0a407863c0639cae5f7f18f5389af86
$ export ID=$(date +'%s') # use this to run a fresh set of tests // bust Varnish cache
$ curl -IX GET "http://localhost:8001/?id=${ID}&max-age=2&stale-if-error=60"  # first hit
HTTP/1.1 200 OK
Host: localhost:8001
Date: Mon, 13 Jan 2020 07:30:51 GMT
Cache-Control: public, max-age=2, stale-if-error=60
Content-type: text/html; charset=UTF-8
X-Varnish: 5
Age: 0
Via: 1.1 varnish (Varnish/6.3)
Accept-Ranges: bytes
Content-Length: 0
Connection: keep-alive

$ curl -IX GET "http://localhost:8001/?id=${ID}&max-age=2&stale-if-error=60"  # Varnish seems to always deliver the cached response and do background validation
HTTP/1.1 200 OK
Host: localhost:8001
Date: Mon, 13 Jan 2020 07:30:51 GMT
Cache-Control: public, max-age=2, stale-if-error=60
Content-type: text/html; charset=UTF-8
X-Varnish: 8 6
Age: 11
Via: 1.1 varnish (Varnish/6.3)
Accept-Ranges: bytes
Content-Length: 0
Connection: keep-alive

$ curl -IX GET "http://localhost:8001/?id=${ID}&max-age=2&stale-if-error=60"  # Now we see the error
HTTP/1.1 500 Internal server error
Host: localhost:8001
Date: Mon, 13 Jan 2020 07:31:07 GMT
Content-type: text/html; charset=UTF-8
X-Varnish: 32770
Age: 0
Via: 1.1 varnish (Varnish/6.3)
Connection: keep-alive
Transfer-Encoding: chunked

@mpdude
Copy link
Contributor Author

mpdude commented Jan 20, 2020

@jderusse I’ve seen in Twitter that you’re doing non-trivial use cases with Varnish, so maybe you could kindly answer a few Varnish questions here?

First, can you confirm that Varnish does not serve stale-on-error by default? So, if a response with public, max-age=2, stale-if-error=60 becomes stale and the backend then gives status code 500, Varnish does not use the stale cache entry?

@jderusse
Copy link
Member

@mpdude I never played with staled content in varnish.

But, from the documentation varnish handle stale-while-revalidate, but does not deal with stale-if-error. They recommend using a custom VCL script.
This is confirmed in the wiki of the respository referenced by varnish.

This custom VCL returns a stale response when status > 400 and use the delay stored in stale-while-revalidate

I don't think that's a good model

@fabpot
Copy link
Member

fabpot commented Jan 30, 2020

@mpdude Let's move on. I would still remove the switch and be non compliant here.

@mpdude
Copy link
Contributor Author

mpdude commented Jan 30, 2020

@fabpot done: Switch removed, but left some comments in case someone reviews this in the future.

@fabpot fabpot force-pushed the stale-if-validate-tests branch from 5ec434a to ad5f427 Compare January 30, 2020 16:05
@fabpot
Copy link
Member

fabpot commented Jan 30, 2020

Thank you @mpdude.

fabpot added a commit that referenced this pull request Jan 30, 2020
This PR was squashed before being merged into the 3.4 branch (closes #35305).

Discussion
----------

[HttpKernel] Fix stale-if-error behavior, add tests

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | #24248
| License       | MIT
| Doc PR        |

This PR adds the first tests for `stale-if-error` logic in `HttpCache`.

It also fixes an observation from #24248: For responses that have been cached as `public` with an `ETag` but without a lifetime, in case of an error the stale response will be served forever (= as long as the error persists), even beyond the configured `stale-if-error` grace period.

Furthermore, it tries to improve compliance with RFC 7234: Stale responses must not be sent (under no condition) if one of
* `no-cache`
* `must-revalidate`
* `proxy-revalidate` or
* `s-maxage` (sic) is present.

This can be found in the corresponding chapters of Section 5.2.2 for these directives, but is also summarized in [Section 4.2.4](https://tools.ietf.org/html/rfc7234#section-4.2.4) as

 > A cache MUST NOT generate a stale response if it is prohibited by an explicit in-protocol directive (e.g., by a "no-store" or "no-cache" cache directive, a "must-revalidate" cache-response-directive, or an applicable "s-maxage" or "proxy-revalidate" cache-response-directive; see Section 5.2.2).

Because disabling of `stale-if-error` for `s-maxage` responses probably has a big impact on the usefulness of that feature in practice, it has to be enabled explicitly with a new config setting `strict_smaxage` (defaulting to `false`).

Commits
-------

ad5f427 [HttpKernel] Fix stale-if-error behavior, add tests
@fabpot fabpot merged commit ad5f427 into symfony:3.4 Jan 30, 2020
This was referenced Jan 31, 2020
@fabpot fabpot mentioned this pull request Feb 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants