Skip to content
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

expire: Force a cache MISS when req.ttl = 0s #4041

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions bin/varnishd/cache/cache_expire.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ EXP_Ttl(const struct req *req, const struct objcore *oc)
CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);

r = oc->ttl;
if (req != NULL && req->d_ttl > 0. && req->d_ttl < r)
if (req != NULL && !isnan(req->d_ttl) && req->d_ttl < r) {
assert(req->d_ttl >= 0.);
r = req->d_ttl;
}
return (oc->t_origin + r);
}

Expand All @@ -91,8 +93,10 @@ EXP_Ttl_grace(const struct req *req, const struct objcore *oc)
CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);

g = oc->grace;
if (req != NULL && req->d_grace >= 0. && req->d_grace < g)
if (req != NULL && !isnan(req->d_grace) && req->d_grace < g) {
assert(req->d_grace >= 0.);
g = req->d_grace;
}
return (EXP_Ttl(req, oc) + g);
}

Expand Down
4 changes: 2 additions & 2 deletions bin/varnishd/cache/cache_req_fsm.c
Original file line number Diff line number Diff line change
Expand Up @@ -893,8 +893,8 @@ cnt_recv_prep(struct req *req, const char *ci)
VRT_Assign_Backend(&req->director_hint,
VCL_DefaultDirector(req->vcl));

req->d_ttl = -1;
req->d_grace = -1;
req->d_ttl = NAN;
req->d_grace = NAN;
req->disable_esi = 0;
req->hash_always_miss = 0;
req->hash_ignore_busy = 0;
Expand Down
2 changes: 2 additions & 0 deletions bin/varnishd/cache/cache_vrt.c
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,8 @@ VRT_REAL_string(VRT_CTX, VCL_REAL num)
{

CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
if (isnan(num))
return ("NAN");
if (!VRT_REAL_is_valid(num))
VRT_fail(ctx, "REAL overflow converting to string (%e)", num);
return (WS_Printf(ctx->ws, "%.3f", num));
Expand Down
12 changes: 12 additions & 0 deletions bin/varnishd/cache/cache_vrt_var.c
Original file line number Diff line number Diff line change
Expand Up @@ -576,12 +576,24 @@ VRT_r_req_##nm(VRT_CTX) \
return (ctx->req->elem); \
}

#define REQ_VAR_U(nm, elem, val) \
\
VCL_VOID \
VRT_u_req_##nm(VRT_CTX) \
{ \
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); \
CHECK_OBJ_NOTNULL(ctx->req, REQ_MAGIC); \
ctx->req->elem = val; \
}

REQ_VAR_R(backend_hint, director_hint, VCL_BACKEND)

REQ_VAR_L(ttl, d_ttl, VCL_DURATION, if (!(arg>0.0)) arg = 0;)
Copy link
Member

Choose a reason for hiding this comment

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

do we still want to set negative arguments to zero? Could it be a better option to fail?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IMO it's reasonable to expect that req.ttl can be set to a negative duration, we even do so in a test case:

Failing on a negative duration has a good chance of breaking existing VCL.

Copy link
Member

Choose a reason for hiding this comment

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

That is a coverage test.
What are the semantics of a negative duration for req.ttl and req.grace?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The same as beresp.ttl and beresp.grace:

if (a < 0.0) \
a = 0.0; \

Copy link
Member

Choose a reason for hiding this comment

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

I understand that this "has always been like that", but also please remember that for quite some time, varnish had no way of properly failing at runtime (VRT_fail()).

The particular lines you are referring to are from 10 years ago and I am still wondering what a negative ttl/grace is supposed to mean. ttl: already expired before it even went into the cache? If those were the semantics, then we would not be allowed to take the respective objects as "just refreshed". grace: I would think a negative value should move an object into grace mode earlier than ttl.

So, as we come across these questions, we should answer them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I totally agree that "has always been like that" is a weak argument, but I personally prefer to limit breakage if I can. It's not obvious to me what a negative TTL/grace should mean, and I think that question is bigger than the scope of this PR.

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 try to cover this quickly during the next bugwash. I am also fine with continuing to ignore negative values, but I'd like to at least ask for opinions.

Copy link
Member

Choose a reason for hiding this comment

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

I think the point here is to cap the TTL, and once we run out of TTL the object goes in grace mode if applicable, so once we run out of TTL req.grace applies.

This is why in my opinion req.ttl should only limit the ability to look up a fresh object, and that negative values that could be the result of some arithmetic expression should be bumped to zero.

Copy link
Member

Choose a reason for hiding this comment

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

@dridi maybe I do not understand your answer, but why should we not fail immediately if an attempt is made to set any of the timers to a negative value?

Copy link
Member

Choose a reason for hiding this comment

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

If you use an expression instead of a constant to compute the acceptable TTL limit and the result is negative, I think it would be too harsh to fail the transaction.

If your "clever" req.ttl formula can yield something negative, then negative should be considered as allowing no TTL, just like zero.

REQ_VAR_R(ttl, d_ttl, VCL_DURATION)
REQ_VAR_U(ttl, d_ttl, NAN)
REQ_VAR_L(grace, d_grace, VCL_DURATION, if (!(arg>0.0)) arg = 0;)
REQ_VAR_R(grace, d_grace, VCL_DURATION)
REQ_VAR_U(grace, d_grace, NAN)

VCL_VOID
VRT_l_req_backend_hint(VRT_CTX, VCL_BACKEND be)
Expand Down
4 changes: 2 additions & 2 deletions bin/varnishtest/tests/b00064.vtc
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ client c2 {
expect resp.status == 200
expect resp.body == "1"
expect resp.http.X-grace == "60.000"
expect resp.http.X-req-grace < 0.
expect resp.http.X-req-grace == "NAN"
expect resp.http.X-was-bgfetch == <undef>
} -run

Expand Down Expand Up @@ -118,7 +118,7 @@ client c4 {
# We should get what c1 got in the very beginning
expect resp.body == "1"
expect resp.http.X-grace == "60.000"
expect resp.http.X-req-grace < 0.
expect resp.http.X-req-grace == "NAN"
expect resp.http.X-was-bgfetch == <undef>
} -start

Expand Down
100 changes: 100 additions & 0 deletions bin/varnishtest/tests/b00092.vtc
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
varnishtest "req.ttl = 0s forces cache MISS with request coalescing"

barrier b1 cond 3 -cyclic
barrier b2 sock 2 -cyclic

server s1 {
rxreq
txresp -nolen -hdr "Content-Length: 3" -hdr "version: 1"
barrier b1 sync
send "foo"

rxreq
txresp -nolen -hdr "Content-Length: 3" -hdr "version: 2"
barrier b1 sync
send "bar"
} -start

varnish v1 -vcl+backend {
import std;
import vtc;

sub vcl_recv {
if (req.http.ttl) {
set req.ttl = std.duration(req.http.ttl, 0s);
}

// Make sure both client tasks are started before the fetch is
// initiated to guarantee that one ends up on the waitinglist.
if (req.http.sync) {
vtc.barrier_sync("${b2_sock}");
}
}
} -start

# c1 and c2 send a request and sync on the barrier only after having received
# headers. This ensures that the two requests are coalesced.

client c1 {
txreq -hdr "sync: yes" -hdr "ttl: 0s"
rxresphdrs
expect resp.status == 200
expect resp.http.version == "1"
barrier b1 sync
rxrespbody
expect resp.body == "foo"
} -start

client c2 {
txreq -hdr "sync: yes" -hdr "ttl: 0s"
rxresphdrs
expect resp.status == 200
expect resp.http.version == "1"
barrier b1 sync
rxrespbody
expect resp.body == "foo"
} -start

client c1 -wait
client c2 -wait

# c3 checks that a positive or unset req.ttl gets the cached object.

client c3 {
txreq -hdr "ttl: 10s"
rxresp
expect resp.status == 200
expect resp.http.version == "1"
expect resp.body == "foo"

txreq
rxresp
expect resp.status == 200
expect resp.http.version == "1"
expect resp.body == "foo"
} -run

# c4 and c5 force a cache MISS, where the two requests are again coalesced.

client c4 {
txreq -hdr "sync: yes" -hdr "ttl: 0s"
rxresphdrs
expect resp.status == 200
expect resp.http.version == "2"
barrier b1 sync
rxrespbody
expect resp.body == "bar"
} -start

client c5 {
txreq -hdr "sync: yes" -hdr "ttl: 0s"
rxresphdrs
expect resp.status == 200
expect resp.http.version == "2"
barrier b1 sync
rxrespbody
expect resp.body == "bar"
} -start

client c4 -wait
client c5 -wait
37 changes: 37 additions & 0 deletions bin/varnishtest/tests/b00093.vtc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
varnishtest "unset req.ttl and req.grace"

server s1 {
rxreq
expect req.http.ttl-initial == "NAN"
expect req.http.grace-initial == "NAN"
expect req.http.ttl-set == 0.000
expect req.http.grace-set == 0.000
expect req.http.ttl-unset == "NAN"
expect req.http.grace-unset == "NAN"
txresp
} -start

varnish v1 -vcl+backend {
sub vcl_recv {
set req.http.ttl-initial = req.ttl;
set req.http.grace-initial = req.grace;

set req.ttl = 0s;
set req.grace = 0s;

set req.http.ttl-set = req.ttl;
set req.http.grace-set = req.grace;

unset req.ttl;
unset req.grace;

set req.http.ttl-unset = req.ttl;
set req.http.grace-unset = req.grace;
}
} -start

client c1 -repeat 2 {
txreq
rxresp
expect resp.status == 200
} -run
10 changes: 9 additions & 1 deletion doc/sphinx/reference/vcl_var.rst
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,14 @@ req.grace

Writable from: client

Unsettable from: client

Upper limit on the object grace.

During lookup the minimum of req.grace and the object's stored
grace value will be used as the object's grace.
grace value will be used as the object's grace. Setting req.grace
to 0s prevents hits on stale objects. Negative values are treated
as 0s.


.. _req.hash:
Expand Down Expand Up @@ -536,8 +539,13 @@ req.ttl

Writable from: client

Unsettable from: client

Upper limit on the object age for cache lookups to return hit.
Setting ``req.ttl = 0s`` forces a cache MISS, but unlike
``req.hash_always_miss = true``, the request may go on the
waitinglist for an ongoing fetch. Negative values are treated
as 0s.


.. _req.url:
Expand Down