Skip to content

Commit 0bd65aa

Browse files
committed
hash: A waiting list match is a formal revalidation
Not only does the waiting list operate on an objcore, it also passes a reference to requests before they reembark a worker. This way when an objcore is already present during lookup we can attempt a cache hit without holding the objhead lock. Instead of repurposing the req::hash_objhead field into an equivalent req::hash_objcore, the field is actually removed. In order to signal that a request comes back from its waiting list, the life time of the req::waitinglist flag is extended until cnt_lookup() is reentered. If the rushed objcore matches a request, the lookup can result in a hit without entering the regular lookup critical section. The objhead lock is briefly acquired to release req's reference on the objhead, to rely solely on the objcore's objhead reference like a normal hit. This shifts the infamous waiting list serialization phenomenon to the vary header match. The rush policy is applied wholesale on cacheable objects instead of exponentially. This improves waiting list latency when the outcome is a cache hit, but forces incompatible variants to reenter a lookup and potentially disembark into the waiting list again. Since most spurious rushes at every corner of objhead activity are gone, this change puts all the spurious activity on the incompatible variants alone instead of all objects on more occasions. If a cacheable object was inserted in the cache, but already expired, this behavior enables cache hits. This can be common with multi-tier Varnish setups where one Varnish server may serve a graced object to an other, but true of any origin server that may serve stale yet valid responses. The waiting list enables a proper response-wide no-cache behavior from now on, but the built-in VCL prevents it by default. This is also the first step towards implementing no-cache and private support at the header field granularity.
1 parent 6b0a8c8 commit 0bd65aa

File tree

5 files changed

+234
-23
lines changed

5 files changed

+234
-23
lines changed

bin/varnishd/cache/cache.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,6 @@ struct req {
491491

492492
struct objcore *body_oc;
493493

494-
/* The busy objhead we sleep on */
495-
struct objhead *hash_objhead;
496-
497494
/* Built Vary string == workspace reservation */
498495
uint8_t *vary_b;
499496
uint8_t *vary_e;

bin/varnishd/cache/cache_hash.c

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,41 @@ hsh_insert_busyobj(const struct worker *wrk, struct objhead *oh)
355355
return (oc);
356356
}
357357

358+
/*---------------------------------------------------------------------
359+
*/
360+
361+
static unsigned
362+
hsh_rush_match(struct req *req)
363+
{
364+
struct objhead *oh;
365+
struct objcore *oc;
366+
const uint8_t *vary;
367+
368+
oc = req->objcore;
369+
CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
370+
371+
AZ(oc->flags & OC_F_BUSY);
372+
if (oc->flags & (OC_F_WITHDRAWN|OC_F_HFM|OC_F_HFP|OC_F_CANCEL|
373+
OC_F_PRIVATE|OC_F_FAILED|OC_F_DYING))
374+
375+
return (0);
376+
377+
if (req->vcf != NULL) /* NB: must operate under oh lock. */
378+
return (0);
379+
380+
oh = oc->objhead;
381+
CHECK_OBJ_NOTNULL(oh, OBJHEAD_MAGIC);
382+
383+
if (req->hash_ignore_vary)
384+
return (1);
385+
if (!ObjHasAttr(req->wrk, oc, OA_VARY))
386+
return (1);
387+
388+
vary = ObjGetAttr(req->wrk, oc, OA_VARY, NULL);
389+
AN(vary);
390+
return (VRY_Match(req, vary));
391+
}
392+
358393
/*---------------------------------------------------------------------
359394
*/
360395

@@ -383,22 +418,40 @@ HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
383418
CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
384419
CHECK_OBJ_NOTNULL(wrk->wpriv, WORKER_PRIV_MAGIC);
385420
CHECK_OBJ_NOTNULL(req->http, HTTP_MAGIC);
421+
CHECK_OBJ_ORNULL(req->objcore, OBJCORE_MAGIC);
386422
CHECK_OBJ_ORNULL(req->vcf, VCF_MAGIC);
387423
AN(hash);
388424

389425
hsh_prealloc(wrk);
390426
if (DO_DEBUG(DBG_HASHEDGE))
391427
hsh_testmagic(req->digest);
392428

393-
if (req->hash_objhead != NULL) {
429+
if (req->objcore != NULL && hsh_rush_match(req)) {
430+
TAKE_OBJ_NOTNULL(oc, &req->objcore, OBJCORE_MAGIC);
431+
*ocp = oc;
432+
oh = oc->objhead;
433+
Lck_Lock(&oh->mtx);
434+
oc->hits++;
435+
boc_progress = oc->boc == NULL ? -1 : oc->boc->fetched_so_far;
436+
AN(hsh_deref_objhead_unlock(wrk, &oh, NULL));
437+
Req_LogHit(wrk, req, oc, boc_progress);
438+
/* NB: since this hit comes from the waiting list instead of
439+
* a regular lookup, grace is not considered. The object is
440+
* fresh in the context of the waiting list, even expired: it
441+
* was successfully just [re]validated by a fetch task.
442+
*/
443+
return (HSH_HIT);
444+
}
445+
446+
if (req->objcore != NULL) {
394447
/*
395448
* This req came off the waiting list, and brings an
396-
* oh refcnt with it.
449+
* oh refcnt and an incompatible oc refcnt with it,
450+
* the latter acquired during rush hour.
397451
*/
398-
CHECK_OBJ_NOTNULL(req->hash_objhead, OBJHEAD_MAGIC);
399-
oh = req->hash_objhead;
452+
oh = req->objcore->objhead;
453+
(void)HSH_DerefObjCore(wrk, &req->objcore);
400454
Lck_Lock(&oh->mtx);
401-
req->hash_objhead = NULL;
402455
} else {
403456
AN(wrk->wpriv->nobjhead);
404457
oh = hash->lookup(wrk, req->digest, &wrk->wpriv->nobjhead);
@@ -581,11 +634,12 @@ HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
581634
AZ(req->hash_ignore_busy);
582635

583636
/*
584-
* The objhead reference transfers to the sess, we get it
585-
* back when the sess comes off the waiting list and
586-
* calls us again
637+
* The objhead reference is held by req while it is parked on the
638+
* waiting list. The oh pointer is taken back from the objcore that
639+
* triggers a rush of req off the waiting list.
587640
*/
588-
req->hash_objhead = oh;
641+
assert(oh->refcnt > 1);
642+
589643
req->wrk = NULL;
590644
req->waitinglist = 1;
591645

@@ -640,8 +694,10 @@ hsh_rush1(const struct worker *wrk, struct objcore *oc, struct rush *r)
640694
AZ(req->wrk);
641695
VTAILQ_REMOVE(&oh->waitinglist, req, w_list);
642696
VTAILQ_INSERT_TAIL(&r->reqs, req, w_list);
643-
req->waitinglist = 0;
697+
req->objcore = oc;
644698
}
699+
700+
oc->refcnt += i;
645701
}
646702

647703
/*---------------------------------------------------------------------
@@ -899,8 +955,8 @@ HSH_Withdraw(struct worker *wrk, struct objcore **ocp)
899955
assert(oc->refcnt == 1);
900956
assert(oh->refcnt > 0);
901957
oc->flags = OC_F_WITHDRAWN;
902-
hsh_rush1(wrk, oc, &rush);
903-
AZ(HSH_DerefObjCoreUnlock(wrk, &oc));
958+
hsh_rush1(wrk, oc, &rush); /* grabs up to 1 oc ref */
959+
assert(HSH_DerefObjCoreUnlock(wrk, &oc) <= 1);
904960

905961
hsh_rush2(wrk, &rush);
906962
}

bin/varnishd/cache/cache_req_fsm.c

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -556,20 +556,23 @@ cnt_lookup(struct worker *wrk, struct req *req)
556556
{
557557
struct objcore *oc, *busy;
558558
enum lookup_e lr;
559-
int had_objhead = 0;
559+
int had_objcore = 0;
560560

561561
CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
562562
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
563-
AZ(req->objcore);
564563
AZ(req->stale_oc);
565564

566565
AN(req->vcl);
567566

568567
VRY_Prep(req);
569568

570-
AZ(req->objcore);
571-
if (req->hash_objhead)
572-
had_objhead = 1;
569+
if (req->waitinglist) {
570+
CHECK_OBJ_NOTNULL(req->objcore, OBJCORE_MAGIC);
571+
req->waitinglist = 0;
572+
had_objcore = 1;
573+
} else
574+
AZ(req->objcore);
575+
573576
wrk->strangelove = 0;
574577
lr = HSH_Lookup(req, &oc, &busy);
575578
if (lr == HSH_BUSY) {
@@ -585,7 +588,7 @@ cnt_lookup(struct worker *wrk, struct req *req)
585588
if ((unsigned)wrk->strangelove >= cache_param->vary_notice)
586589
VSLb(req->vsl, SLT_Notice, "vsl: High number of variants (%d)",
587590
wrk->strangelove);
588-
if (had_objhead)
591+
if (had_objcore)
589592
VSLb_ts_req(req, "Waitinglist", W_TIM_real(wrk));
590593

591594
if (req->vcf != NULL) {

bin/varnishd/cache/cache_vary.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,7 @@ vry_cmp(const uint8_t *v1, const uint8_t *v2)
224224
void
225225
VRY_Prep(struct req *req)
226226
{
227-
if (req->hash_objhead == NULL) {
228-
/* Not a waiting list return */
227+
if (!req->waitinglist) {
229228
AZ(req->vary_b);
230229
AZ(req->vary_e);
231230
(void)WS_ReserveAll(req->ws);

bin/varnishtest/tests/c00125.vtc

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
varnishtest "successful expired waiting list hit"
2+
3+
barrier b1 cond 2
4+
barrier b2 cond 2
5+
barrier b3 cond 2
6+
barrier b4 cond 2
7+
8+
9+
server s1 {
10+
rxreq
11+
expect req.http.user-agent == c1
12+
expect req.http.bgfetch == false
13+
barrier b1 sync
14+
barrier b2 sync
15+
txresp -hdr "Cache-Control: max-age=60" -hdr "Age: 120"
16+
17+
rxreq
18+
expect req.http.user-agent == c3
19+
expect req.http.bgfetch == true
20+
txresp
21+
22+
# The no-cache case only works with a complicit VCL, for now.
23+
rxreq
24+
expect req.http.user-agent == c4
25+
expect req.http.bgfetch == false
26+
barrier b3 sync
27+
barrier b4 sync
28+
txresp -hdr "Cache-Control: no-cache"
29+
30+
rxreq
31+
expect req.http.user-agent == c6
32+
expect req.http.bgfetch == false
33+
txresp -hdr "Cache-Control: no-cache"
34+
} -start
35+
36+
varnish v1 -cliok "param.set default_grace 1h"
37+
varnish v1 -cliok "param.set thread_pools 1"
38+
varnish v1 -cliok "param.set debug +syncvsl,+waitinglist"
39+
varnish v1 -vcl+backend {
40+
sub vcl_backend_fetch {
41+
set bereq.http.bgfetch = bereq.is_bgfetch;
42+
}
43+
sub vcl_beresp_stale {
44+
# We just validated a stale object, do not mark it as
45+
# uncacheable. The object remains available for grace
46+
# hits and background fetches.
47+
return;
48+
}
49+
sub vcl_beresp_control {
50+
if (beresp.http.cache-control == "no-cache") {
51+
# Keep beresp.uncacheable clear.
52+
return;
53+
}
54+
}
55+
sub vcl_deliver {
56+
set resp.http.obj-hits = obj.hits;
57+
set resp.http.obj-ttl = obj.ttl;
58+
}
59+
} -start
60+
61+
client c1 {
62+
txreq -url "/stale-hit"
63+
rxresp
64+
expect resp.status == 200
65+
expect resp.http.x-varnish == 1001
66+
expect resp.http.obj-hits == 0
67+
expect resp.http.obj-ttl < 0
68+
} -start
69+
70+
barrier b1 sync
71+
72+
client c2 {
73+
txreq -url "/stale-hit"
74+
rxresp
75+
expect resp.status == 200
76+
expect resp.http.x-varnish == "1004 1002"
77+
expect resp.http.obj-hits == 1
78+
expect resp.http.obj-ttl < 0
79+
} -start
80+
81+
varnish v1 -expect busy_sleep == 1
82+
barrier b2 sync
83+
84+
client c1 -wait
85+
client c2 -wait
86+
87+
varnish v1 -vsl_catchup
88+
89+
varnish v1 -expect cache_miss == 1
90+
varnish v1 -expect cache_hit == 1
91+
varnish v1 -expect cache_hit_grace == 0
92+
varnish v1 -expect s_bgfetch == 0
93+
94+
client c3 {
95+
txreq -url "/stale-hit"
96+
rxresp
97+
expect resp.status == 200
98+
expect resp.http.x-varnish == "1006 1002"
99+
expect resp.http.obj-hits == 2
100+
expect resp.http.obj-ttl < 0
101+
} -run
102+
103+
varnish v1 -vsl_catchup
104+
105+
varnish v1 -expect cache_miss == 1
106+
varnish v1 -expect cache_hit == 2
107+
varnish v1 -expect cache_hit_grace == 1
108+
varnish v1 -expect s_bgfetch == 1
109+
110+
# The only way for a plain no-cache to be hit is to have a non-zero keep.
111+
varnish v1 -cliok "param.set default_ttl 0"
112+
varnish v1 -cliok "param.set default_grace 0"
113+
varnish v1 -cliok "param.set default_keep 1h"
114+
115+
client c4 {
116+
txreq -url "/no-cache-hit"
117+
rxresp
118+
expect resp.status == 200
119+
expect resp.http.x-varnish == 1009
120+
expect resp.http.obj-hits == 0
121+
expect resp.http.obj-ttl <= 0
122+
} -start
123+
124+
barrier b3 sync
125+
126+
client c5 {
127+
txreq -url "/no-cache-hit"
128+
rxresp
129+
expect resp.status == 200
130+
expect resp.http.x-varnish == "1012 1010"
131+
expect resp.http.obj-hits == 1
132+
expect resp.http.obj-ttl <= 0
133+
} -start
134+
135+
varnish v1 -expect busy_sleep == 2
136+
barrier b4 sync
137+
138+
client c4 -wait
139+
client c5 -wait
140+
141+
varnish v1 -vsl_catchup
142+
143+
varnish v1 -expect cache_miss == 2
144+
varnish v1 -expect cache_hit == 3
145+
varnish v1 -expect cache_hit_grace == 1
146+
varnish v1 -expect s_bgfetch == 1
147+
148+
# No hit when not on the waiting list
149+
client c6 {
150+
txreq -url "/no-cache-hit"
151+
rxresp
152+
expect resp.status == 200
153+
expect resp.http.x-varnish == 1014
154+
expect resp.http.obj-hits == 0
155+
expect resp.http.obj-ttl <= 0
156+
} -run

0 commit comments

Comments
 (0)