Skip to content
This repository was archived by the owner on Feb 16, 2026. It is now read-only.

Commit 02402cc

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. This change creates a new first step in the lookup process: - rush match (when coming from the waiting list) - hash lookup (unless coming from the waiting list) - objhead lookup (unless there was a rush hit) If the rush match is a hit, the objhead lock is briefly acquired to release req's reference, to rely solely on the objcore's reference like a normal hit. This shifts the infamous waiting list serialization phenomenon to the vary header match. Knowing that a rushed object is guaranteed to lead to a cache hit allows the rush policy to be applied wholesale, instead of exponentially. Only requests incompatible with the objcore vary header may reenter the waiting list, a scenario no different from the spurious rush wakeups when operating solely on objhead updates. 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 4ed9144 commit 02402cc

5 files changed

Lines changed: 229 additions & 23 deletions

File tree

bin/varnishd/cache/cache.h

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

491491
struct objcore *body_oc;
492492

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

bin/varnishd/cache/cache_hash.c

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,39 @@ hsh_objhead_lookup(struct objhead *oh, struct req *req, struct objcore **ocp,
512512
return (HSH_BUSY);
513513
}
514514

515+
/*---------------------------------------------------------------------
516+
*/
517+
518+
static unsigned
519+
hsh_rush_match(struct req *req)
520+
{
521+
struct objhead *oh;
522+
struct objcore *oc;
523+
const uint8_t *vary;
524+
525+
oc = req->objcore;
526+
CHECK_OBJ_ORNULL(oc, OBJCORE_MAGIC);
527+
528+
if (oc == NULL)
529+
return (0);
530+
531+
AZ(oc->flags & OC_F_BUSY);
532+
if (oc->flags)
533+
return (0);
534+
535+
oh = oc->objhead;
536+
CHECK_OBJ_NOTNULL(oh, OBJHEAD_MAGIC);
537+
538+
if (req->hash_ignore_vary)
539+
return (1);
540+
if (!ObjHasAttr(req->wrk, oc, OA_VARY))
541+
return (1);
542+
543+
vary = ObjGetAttr(req->wrk, oc, OA_VARY, NULL);
544+
AN(vary);
545+
return (VRY_Match(req, vary));
546+
}
547+
515548
/*---------------------------------------------------------------------
516549
*/
517550

@@ -537,22 +570,39 @@ HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
537570
CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
538571
CHECK_OBJ_NOTNULL(wrk->wpriv, WORKER_PRIV_MAGIC);
539572
CHECK_OBJ_NOTNULL(req->http, HTTP_MAGIC);
573+
CHECK_OBJ_ORNULL(req->objcore, OBJCORE_MAGIC);
540574
CHECK_OBJ_ORNULL(req->vcf, VCF_MAGIC);
541575
AN(hash);
542576

543577
hsh_prealloc(wrk);
544578
if (DO_DEBUG(DBG_HASHEDGE))
545579
hsh_testmagic(req->digest);
546580

547-
if (req->hash_objhead != NULL) {
581+
if (hsh_rush_match(req)) {
582+
TAKE_OBJ_NOTNULL(oc, &req->objcore, OBJCORE_MAGIC);
583+
*ocp = oc;
584+
oh = oc->objhead;
585+
AN(hsh_deref_objhead(wrk, &oh));
586+
oc->hits++;
587+
boc_progress = oc->boc == NULL ? -1 : oc->boc->fetched_so_far;
588+
Req_LogHit(wrk, req, oc, boc_progress);
589+
/* NB: since this hit comes from the waiting list instead of
590+
* a regular lookup, grace is not considered. The object is
591+
* fresh in the context of the waiting list, even expired: it
592+
* was successfully just [re]validated by a fetch task.
593+
*/
594+
return (HSH_HIT);
595+
}
596+
597+
if (req->objcore != NULL) {
548598
/*
549599
* This req came off the waiting list, and brings an
550-
* oh refcnt with it.
600+
* oh refcnt and an incompatible oc refcnt with it,
601+
* the latter acquired during rush hour.
551602
*/
552-
CHECK_OBJ_NOTNULL(req->hash_objhead, OBJHEAD_MAGIC);
553-
oh = req->hash_objhead;
603+
oh = req->objcore->objhead;
604+
(void)HSH_DerefObjCore(wrk, &req->objcore);
554605
Lck_Lock(&oh->mtx);
555-
req->hash_objhead = NULL;
556606
} else {
557607
AN(wrk->wpriv->nobjhead);
558608
oh = hash->lookup(wrk, req->digest, &wrk->wpriv->nobjhead);
@@ -625,11 +675,13 @@ HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
625675
VTAILQ_INSERT_TAIL(&oh->waitinglist, req, w_list);
626676

627677
/*
628-
* The objhead reference transfers to the sess, we get it
629-
* back when the sess comes off the waiting list and
630-
* calls us again
678+
* The objhead reference is held by req while it is parked
679+
* on the waiting list. The oh pointer is taken back from
680+
* the objcore that triggers a rush of req off the waiting
681+
* list.
631682
*/
632-
req->hash_objhead = oh;
683+
assert(oh->refcnt > 1);
684+
633685
req->wrk = NULL;
634686
req->waitinglist = 1;
635687

@@ -690,11 +742,10 @@ hsh_rush1(const struct worker *wrk, struct objcore *oc, struct rush *r)
690742
AZ(req->wrk);
691743
VTAILQ_REMOVE(&oh->waitinglist, req, w_list);
692744
VTAILQ_INSERT_TAIL(&r->reqs, req, w_list);
693-
req->waitinglist = 0;
745+
req->objcore = oc;
694746
}
695747

696-
if (i > 0)
697-
assert(oh->refcnt > 1);
748+
oc->refcnt += i;
698749
}
699750

700751
/*---------------------------------------------------------------------

bin/varnishd/cache/cache_req_fsm.c

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

562562
CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
563563
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
564-
AZ(req->objcore);
565564
AZ(req->stale_oc);
566565

567566
AN(req->vcl);
568567

569568
VRY_Prep(req);
570569

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

592595
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)