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

Commit 411add2

Browse files
committed
hash: Treat a waiting list match as a revalidation
If a fetch succeeded and an object was inserted as cacheable, it means that the VCL did not interpret the backend response as revalidated and shareable with other clients on the waiting list. After changing the rush to operate based on objcore flags instead of the call site deciding how many clients to wake up, the same objcore reference is now passed to requests before they reembark a worker. This way when an objcore is already present during lookup we can attempt a cache hit directly on the objcore that triggered the rush, removing a degree of uncertainty in the waiting list behavior. 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. But to avoid infinite rush loops when a return(restart) is involved, the flag is turned into a req::waitinglist_gen counter to prevent objcore rushes from affecting requests entering the waiting list after they began for a given objcore. 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, and generally perform hit-related operations. This change brings back the exponential rush of cacheable objects briefly neutered. This shifts the infamous waiting list serialization phenomenon to the vary header match. 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 another, 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. fgen
1 parent 604459b commit 411add2

7 files changed

Lines changed: 250 additions & 31 deletions

File tree

bin/varnishd/cache/cache.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ struct objcore {
354354
uint16_t oa_present;
355355

356356
unsigned timer_idx; // XXX 4Gobj limit
357+
unsigned waitinglist_gen;
357358
vtim_real last_lru;
358359
VTAILQ_ENTRY(objcore) hsh_list;
359360
VTAILQ_ENTRY(objcore) lru_list;
@@ -468,6 +469,7 @@ struct req {
468469
stream_close_t doclose;
469470
unsigned restarts;
470471
unsigned esi_level;
472+
unsigned waitinglist_gen;
471473

472474
/* Delivery mode */
473475
unsigned res_mode;
@@ -494,9 +496,6 @@ struct req {
494496

495497
struct objcore *body_oc;
496498

497-
/* The busy objhead we sleep on */
498-
struct objhead *hash_objhead;
499-
500499
/* Built Vary string == workspace reservation */
501500
uint8_t *vary_b;
502501
uint8_t *vary_e;

bin/varnishd/cache/cache_hash.c

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ hsh_newobjhead(void)
101101
ALLOC_OBJ(oh, OBJHEAD_MAGIC);
102102
XXXAN(oh);
103103
oh->refcnt = 1;
104+
oh->waitinglist_gen = 1;
104105
VTAILQ_INIT(&oh->objcs);
105106
VTAILQ_INIT(&oh->waitinglist);
106107
Lck_New(&oh->mtx, lck_objhdr);
@@ -355,6 +356,41 @@ hsh_insert_busyobj(const struct worker *wrk, struct objhead *oh)
355356
return (oc);
356357
}
357358

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

@@ -383,22 +419,40 @@ HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
383419
CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
384420
CHECK_OBJ_NOTNULL(wrk->wpriv, WORKER_PRIV_MAGIC);
385421
CHECK_OBJ_NOTNULL(req->http, HTTP_MAGIC);
422+
CHECK_OBJ_ORNULL(req->objcore, OBJCORE_MAGIC);
386423
CHECK_OBJ_ORNULL(req->vcf, VCF_MAGIC);
387424
AN(hash);
388425

389426
hsh_prealloc(wrk);
390427
if (DO_DEBUG(DBG_HASHEDGE))
391428
hsh_testmagic(req->digest);
392429

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

583637
/*
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
638+
* The objhead reference is held by req while it is parked on the
639+
* waiting list. The oh pointer is taken back from the objcore that
640+
* triggers a rush of req off the waiting list.
587641
*/
588-
req->hash_objhead = oh;
642+
assert(oh->refcnt > 1);
643+
589644
req->wrk = NULL;
590-
req->waitinglist = 1;
645+
req->waitinglist_gen = oh->waitinglist_gen;
591646

592647
if (DO_DEBUG(DBG_WAITINGLIST))
593648
VSLb(req->vsl, SLT_Debug, "on waiting list <%p>", oh);
@@ -623,25 +678,32 @@ hsh_rush1(const struct worker *wrk, struct objcore *oc, struct rush *r)
623678

624679
AZ(oc->flags & OC_F_BUSY);
625680
AZ(oc->flags & OC_F_PRIVATE);
681+
max = cache_param->rush_exponent;
626682
if (oc->flags & (OC_F_WITHDRAWN|OC_F_FAILED))
627683
max = 1;
628-
else if (oc->flags & (OC_F_HFM|OC_F_HFP|OC_F_CANCEL|OC_F_DYING))
629-
max = cache_param->rush_exponent;
630-
else
631-
max = INT_MAX; /* XXX: temp */
632684
assert(max > 0);
633685

686+
if (oc->waitinglist_gen == 0) {
687+
oc->waitinglist_gen = oh->waitinglist_gen;
688+
oh->waitinglist_gen++;
689+
}
690+
634691
for (i = 0; i < max; i++) {
635692
req = VTAILQ_FIRST(&oh->waitinglist);
693+
CHECK_OBJ_ORNULL(req, REQ_MAGIC);
636694
if (req == NULL)
637695
break;
638-
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
639-
wrk->stats->busy_wakeup++;
696+
if (req->waitinglist_gen > oc->waitinglist_gen)
697+
continue;
698+
640699
AZ(req->wrk);
641700
VTAILQ_REMOVE(&oh->waitinglist, req, w_list);
642701
VTAILQ_INSERT_TAIL(&r->reqs, req, w_list);
643-
req->waitinglist = 0;
702+
req->objcore = oc;
644703
}
704+
705+
oc->refcnt += i;
706+
wrk->stats->busy_wakeup += i;
645707
}
646708

647709
/*---------------------------------------------------------------------
@@ -902,8 +964,8 @@ HSH_Withdraw(struct worker *wrk, struct objcore **ocp)
902964
assert(oc->refcnt == 1);
903965
assert(oh->refcnt > 0);
904966
oc->flags = OC_F_WITHDRAWN;
905-
hsh_rush1(wrk, oc, &rush);
906-
AZ(HSH_DerefObjCoreUnlock(wrk, &oc));
967+
hsh_rush1(wrk, oc, &rush); /* grabs up to 1 oc ref */
968+
assert(HSH_DerefObjCoreUnlock(wrk, &oc) <= 1);
907969

908970
hsh_rush2(wrk, &rush);
909971
}

bin/varnishd/cache/cache_objhead.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ struct objhead {
4040
struct lock mtx;
4141
VTAILQ_HEAD(,objcore) objcs;
4242
uint8_t digest[DIGEST_LEN];
43+
unsigned waitinglist_gen;
4344
VTAILQ_HEAD(, req) waitinglist;
4445

4546
/*----------------------------------------------------

bin/varnishd/cache/cache_req_fsm.c

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

560560
CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
561561
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
562-
AZ(req->objcore);
563562
AZ(req->stale_oc);
564563

565564
AN(req->vcl);
566565

567566
VRY_Prep(req);
568567

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

590593
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_gen == 0) {
229228
AZ(req->vary_b);
230229
AZ(req->vary_e);
231230
(void)WS_ReserveAll(req->ws);

0 commit comments

Comments
 (0)