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

Commit 04a125e

Browse files
committed
hash: Transfer waiting list ownership to the objcore
The advantages of the objhead owning the waiting list are that we need less waiting list heads as they are shared by across multiple objcores, we get more frequent opportunities to rush waiting requests, and we avoid one expensive part of the lookup, which is to find an objhead from a request hash. The downsides are that the requirement to always perform a new lookup, which increases contention on the objhead lock, and prevents expired but valid objects to be considered fresh. This is another way to describe the cache-control no-cache directive: a response considered fresh at fetch time, but immediately stale afterwards. Having the waiting list on the objcore means that we reenter the cache lookup with the object candidate from the previous lookup, so we may short-circuit the more expensive objhead lookup when the objcore is compatible with the request rushing off the waiting list. The expensive objhead lookup from the request hash is still avoided, the objcore has a reference to the objhead. This moves 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 on the objhead. The waiting list operations are still guarded by the objhead lock. This is the first step towards implementing no-cache and private support at the header field granularity.
1 parent 90cb873 commit 04a125e

8 files changed

Lines changed: 183 additions & 66 deletions

File tree

bin/varnishd/cache/cache.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ struct objcore {
359359
VTAILQ_ENTRY(objcore) lru_list;
360360
VTAILQ_ENTRY(objcore) ban_list;
361361
VSTAILQ_ENTRY(objcore) exp_list;
362+
VTAILQ_HEAD(, req) waitinglist;
362363
struct ban *ban;
363364
};
364365

@@ -490,8 +491,8 @@ struct req {
490491

491492
struct objcore *body_oc;
492493

493-
/* The busy objhead we sleep on */
494-
struct objhead *hash_objhead;
494+
/* The busy objcore we sleep on */
495+
struct objcore *hash_oc;
495496

496497
/* Built Vary string == workspace reservation */
497498
uint8_t *vary_b;

bin/varnishd/cache/cache_fetch.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,7 @@ vbf_stp_error(struct worker *wrk, struct busyobj *bo)
923923

924924
stale = bo->stale_oc;
925925
oc->t_origin = now;
926-
if (!VTAILQ_EMPTY(&oc->objhead->waitinglist)) {
926+
if (!VTAILQ_EMPTY(&oc->waitinglist)) {
927927
/*
928928
* If there is a waitinglist, it means that there is no
929929
* grace-able object, so cache the error return for a

bin/varnishd/cache/cache_hash.c

Lines changed: 98 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,11 @@ struct rush {
7575
static const struct hash_slinger *hash;
7676
static struct objhead *private_oh;
7777

78-
static void hsh_rush1(const struct worker *, struct objhead *,
78+
static void hsh_rush1(const struct worker *, struct objcore *,
7979
struct rush *, int);
8080
static void hsh_rush2(struct worker *, struct rush *);
8181
static int hsh_deref_objhead(struct worker *wrk, struct objhead **poh);
82-
static int hsh_deref_objhead_unlock(struct worker *wrk, struct objhead **poh,
83-
int);
82+
static int hsh_deref_objhead_unlock(struct worker *wrk, struct objhead **poh);
8483

8584
/*---------------------------------------------------------------------*/
8685

@@ -102,7 +101,6 @@ hsh_newobjhead(void)
102101
XXXAN(oh);
103102
oh->refcnt = 1;
104103
VTAILQ_INIT(&oh->objcs);
105-
VTAILQ_INIT(&oh->waitinglist);
106104
Lck_New(&oh->mtx, lck_objhdr);
107105
return (oh);
108106
}
@@ -182,7 +180,6 @@ HSH_DeleteObjHead(const struct worker *wrk, struct objhead *oh)
182180

183181
AZ(oh->refcnt);
184182
assert(VTAILQ_EMPTY(&oh->objcs));
185-
assert(VTAILQ_EMPTY(&oh->waitinglist));
186183
Lck_Delete(&oh->mtx);
187184
wrk->stats->n_objecthead--;
188185
FREE_OBJ(oh);
@@ -323,8 +320,8 @@ HSH_Insert(struct worker *wrk, const void *digest, struct objcore *oc,
323320
VTAILQ_REMOVE(&oh->objcs, oc, hsh_list);
324321
VTAILQ_INSERT_HEAD(&oh->objcs, oc, hsh_list);
325322
oc->flags &= ~OC_F_BUSY;
326-
if (!VTAILQ_EMPTY(&oh->waitinglist))
327-
hsh_rush1(wrk, oh, &rush, HSH_RUSH_POLICY);
323+
if (!VTAILQ_EMPTY(&oc->waitinglist))
324+
hsh_rush1(wrk, oc, &rush, HSH_RUSH_POLICY);
328325
Lck_Unlock(&oh->mtx);
329326
hsh_rush2(wrk, &rush);
330327

@@ -358,16 +355,47 @@ hsh_insert_busyobj(const struct worker *wrk, struct objhead *oh)
358355
/*---------------------------------------------------------------------
359356
*/
360357

358+
static int
359+
hsh_is_rush_hit(struct req *req)
360+
{
361+
struct objcore *oc;
362+
const uint8_t *vary;
363+
364+
oc = req->hash_oc;
365+
CHECK_OBJ_ORNULL(oc, OBJCORE_MAGIC);
366+
367+
if (oc == NULL)
368+
return (0);
369+
370+
AZ(oc->flags & OC_F_BUSY);
371+
if (oc->flags != 0)
372+
return (0);
373+
374+
/* The objcore was serviceable, all the requests on the waiting list
375+
* were rushed at once.
376+
*/
377+
assert(VTAILQ_EMPTY(&oc->waitinglist));
378+
379+
if (req->hash_ignore_vary)
380+
return (1);
381+
if (!ObjHasAttr(req->wrk, oc, OA_VARY))
382+
return (1);
383+
384+
vary = ObjGetAttr(req->wrk, oc, OA_VARY, NULL);
385+
AN(vary);
386+
return (VRY_Match(req, vary));
387+
}
388+
361389
enum lookup_e
362390
HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
363391
{
364392
struct worker *wrk;
365393
struct objhead *oh;
366394
struct objcore *oc;
367395
struct objcore *exp_oc;
396+
struct objcore *busy_oc;
368397
const struct vcf_return *vr;
369398
vtim_real exp_t_origin;
370-
int busy_found;
371399
const uint8_t *vary;
372400
intmax_t boc_progress;
373401
unsigned xid = 0;
@@ -390,15 +418,30 @@ HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
390418
if (DO_DEBUG(DBG_HASHEDGE))
391419
hsh_testmagic(req->digest);
392420

393-
if (req->hash_objhead != NULL) {
421+
if (hsh_is_rush_hit(req)) {
422+
TAKE_OBJ_NOTNULL(oc, &req->hash_oc, OBJCORE_MAGIC);
423+
*ocp = oc;
424+
oc->hits++;
425+
boc_progress = oc->boc == NULL ? -1 : oc->boc->fetched_so_far;
426+
Req_LogHit(wrk, req, oc, boc_progress);
427+
/* NB: since this hit comes from the waiting list instead of
428+
* a regular lookup, grace is not considered. The object is
429+
* fresh in the context of the waiting list, even expired: it
430+
* was successfully [re]validated by a fetch task.
431+
*/
432+
return (HSH_HIT);
433+
}
434+
435+
if (req->hash_oc != NULL) {
394436
/*
395437
* This req came off the waiting list, and brings an
396-
* oh refcnt with it.
438+
* incompatible hash_oc refcnt with it.
397439
*/
398-
CHECK_OBJ_NOTNULL(req->hash_objhead, OBJHEAD_MAGIC);
399-
oh = req->hash_objhead;
440+
CHECK_OBJ_NOTNULL(req->hash_oc->objhead, OBJHEAD_MAGIC);
441+
oh = req->hash_oc->objhead;
442+
/* XXX: can we avoid grabbing the oh lock twice here? */
443+
(void)HSH_DerefObjCore(wrk, &req->hash_oc, HSH_RUSH_POLICY);
400444
Lck_Lock(&oh->mtx);
401-
req->hash_objhead = NULL;
402445
} else {
403446
AN(wrk->wpriv->nobjhead);
404447
oh = hash->lookup(wrk, req->digest, &wrk->wpriv->nobjhead);
@@ -417,7 +460,7 @@ HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
417460
}
418461

419462
assert(oh->refcnt > 0);
420-
busy_found = 0;
463+
busy_oc = NULL;
421464
exp_oc = NULL;
422465
exp_t_origin = 0.0;
423466
VTAILQ_FOREACH(oc, &oh->objcs, hsh_list) {
@@ -434,17 +477,19 @@ HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
434477

435478
CHECK_OBJ_ORNULL(oc->boc, BOC_MAGIC);
436479
if (oc->flags & OC_F_BUSY) {
480+
if (busy_oc != NULL)
481+
continue;
437482
if (req->hash_ignore_busy)
438483
continue;
439484

440-
if (oc->boc && oc->boc->vary != NULL &&
485+
if (oc->boc != NULL && oc->boc->vary != NULL &&
441486
!req->hash_ignore_vary &&
442487
!VRY_Match(req, oc->boc->vary)) {
443488
wrk->strangelove++;
444489
continue;
445490
}
446491

447-
busy_found = 1;
492+
busy_oc = oc;
448493
continue;
449494
}
450495

@@ -501,7 +546,7 @@ HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
501546
if (oc != NULL && oc->flags & OC_F_HFP) {
502547
xid = VXID(ObjGetXID(wrk, oc));
503548
dttl = EXP_Dttl(req, oc);
504-
AN(hsh_deref_objhead_unlock(wrk, &oh, HSH_RUSH_POLICY));
549+
AN(hsh_deref_objhead_unlock(wrk, &oh));
505550
wrk->stats->cache_hitpass++;
506551
VSLb(req->vsl, SLT_HitPass, "%u %.6f", xid, dttl);
507552
return (HSH_HITPASS);
@@ -521,7 +566,7 @@ HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
521566
}
522567
oc->hits++;
523568
boc_progress = oc->boc == NULL ? -1 : oc->boc->fetched_so_far;
524-
AN(hsh_deref_objhead_unlock(wrk, &oh, HSH_RUSH_POLICY));
569+
AN(hsh_deref_objhead_unlock(wrk, &oh));
525570
Req_LogHit(wrk, req, oc, boc_progress);
526571
return (HSH_HIT);
527572
}
@@ -547,7 +592,7 @@ HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
547592
else
548593
boc_progress = -1;
549594

550-
if (!busy_found) {
595+
if (busy_oc == NULL) {
551596
*bocp = hsh_insert_busyobj(wrk, oh);
552597

553598
if (exp_oc != NULL) {
@@ -564,33 +609,38 @@ HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
564609
return (HSH_MISS);
565610
}
566611

567-
AN(busy_found);
612+
CHECK_OBJ_NOTNULL(busy_oc, OBJCORE_MAGIC);
613+
568614
if (exp_oc != NULL && EXP_Ttl_grace(req, exp_oc) >= req->t_req) {
569615
/* we do not wait on the busy object if in grace */
570616
exp_oc->refcnt++;
571617
*ocp = exp_oc;
572618
exp_oc->hits++;
573-
AN(hsh_deref_objhead_unlock(wrk, &oh, 0));
619+
AN(hsh_deref_objhead_unlock(wrk, &oh));
574620
Req_LogHit(wrk, req, exp_oc, boc_progress);
575621
return (HSH_GRACE);
576622
}
577623

578624
/* There are one or more busy objects, wait for them */
579-
VTAILQ_INSERT_TAIL(&oh->waitinglist, req, w_list);
625+
VTAILQ_INSERT_TAIL(&busy_oc->waitinglist, req, w_list);
580626

581627
AZ(req->hash_ignore_busy);
582628

583629
/*
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
630+
* The objcore reference transfers to the req, we get it
631+
* back when the req comes off the waiting list and
632+
* calls us again.
587633
*/
588-
req->hash_objhead = oh;
634+
assert(busy_oc->refcnt > 0);
635+
busy_oc->refcnt++;
636+
637+
AZ(req->hash_oc);
638+
req->hash_oc = busy_oc;
589639
req->wrk = NULL;
590640
req->waitinglist = 1;
591641

592642
if (DO_DEBUG(DBG_WAITINGLIST))
593-
VSLb(req->vsl, SLT_Debug, "on waiting list <%p>", oh);
643+
VSLb(req->vsl, SLT_Debug, "on waiting list <%p>", busy_oc);
594644

595645
Lck_Unlock(&oh->mtx);
596646

@@ -603,30 +653,35 @@ HSH_Lookup(struct req *req, struct objcore **ocp, struct objcore **bocp)
603653
*/
604654

605655
static void
606-
hsh_rush1(const struct worker *wrk, struct objhead *oh, struct rush *r, int max)
656+
hsh_rush1(const struct worker *wrk, struct objcore *oc, struct rush *r, int max)
607657
{
608658
int i;
609659
struct req *req;
610660

611661
if (max == 0)
612662
return;
613-
if (max == HSH_RUSH_POLICY)
614-
max = cache_param->rush_exponent;
663+
if (max == HSH_RUSH_POLICY) {
664+
AZ(oc->flags & OC_F_BUSY);
665+
if (oc->flags != 0)
666+
max = cache_param->rush_exponent;
667+
else
668+
max = INT_MAX;
669+
}
615670
assert(max > 0);
616671

617672
CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
618-
CHECK_OBJ_NOTNULL(oh, OBJHEAD_MAGIC);
673+
CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
619674
CHECK_OBJ_NOTNULL(r, RUSH_MAGIC);
620675
VTAILQ_INIT(&r->reqs);
621-
Lck_AssertHeld(&oh->mtx);
676+
Lck_AssertHeld(&oc->objhead->mtx);
622677
for (i = 0; i < max; i++) {
623-
req = VTAILQ_FIRST(&oh->waitinglist);
678+
req = VTAILQ_FIRST(&oc->waitinglist);
624679
if (req == NULL)
625680
break;
626681
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
627682
wrk->stats->busy_wakeup++;
628683
AZ(req->wrk);
629-
VTAILQ_REMOVE(&oh->waitinglist, req, w_list);
684+
VTAILQ_REMOVE(&oc->waitinglist, req, w_list);
630685
VTAILQ_INSERT_TAIL(&r->reqs, req, w_list);
631686
req->waitinglist = 0;
632687
}
@@ -887,9 +942,9 @@ HSH_Unbusy(struct worker *wrk, struct objcore *oc)
887942
VTAILQ_REMOVE(&oh->objcs, oc, hsh_list);
888943
VTAILQ_INSERT_HEAD(&oh->objcs, oc, hsh_list);
889944
oc->flags &= ~OC_F_BUSY;
890-
if (!VTAILQ_EMPTY(&oh->waitinglist)) {
891-
assert(oh->refcnt > 1);
892-
hsh_rush1(wrk, oh, &rush, HSH_RUSH_POLICY);
945+
if (!VTAILQ_EMPTY(&oc->waitinglist)) {
946+
assert(oc->refcnt > 1);
947+
hsh_rush1(wrk, oc, &rush, HSH_RUSH_POLICY);
893948
}
894949
Lck_Unlock(&oh->mtx);
895950
EXP_Insert(wrk, oc); /* Does nothing unless EXP_RefNewObjcore was
@@ -1041,9 +1096,9 @@ HSH_DerefObjCore(struct worker *wrk, struct objcore **ocp, int rushmax)
10411096
r = --oc->refcnt;
10421097
if (!r)
10431098
VTAILQ_REMOVE(&oh->objcs, oc, hsh_list);
1044-
if (!VTAILQ_EMPTY(&oh->waitinglist)) {
1045-
assert(oh->refcnt > 1);
1046-
hsh_rush1(wrk, oh, &rush, rushmax);
1099+
if (!VTAILQ_EMPTY(&oc->waitinglist)) {
1100+
assert(oc->refcnt > 0);
1101+
hsh_rush1(wrk, oc, &rush, rushmax);
10471102
}
10481103
Lck_Unlock(&oh->mtx);
10491104
hsh_rush2(wrk, &rush);
@@ -1066,38 +1121,24 @@ HSH_DerefObjCore(struct worker *wrk, struct objcore **ocp, int rushmax)
10661121
}
10671122

10681123
static int
1069-
hsh_deref_objhead_unlock(struct worker *wrk, struct objhead **poh, int max)
1124+
hsh_deref_objhead_unlock(struct worker *wrk, struct objhead **poh)
10701125
{
10711126
struct objhead *oh;
1072-
struct rush rush;
1073-
int r;
10741127

10751128
CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
10761129
TAKE_OBJ_NOTNULL(oh, poh, OBJHEAD_MAGIC);
10771130

10781131
Lck_AssertHeld(&oh->mtx);
10791132

10801133
if (oh == private_oh) {
1081-
assert(VTAILQ_EMPTY(&oh->waitinglist));
10821134
assert(oh->refcnt > 1);
10831135
oh->refcnt--;
10841136
Lck_Unlock(&oh->mtx);
10851137
return (1);
10861138
}
10871139

1088-
INIT_OBJ(&rush, RUSH_MAGIC);
1089-
if (!VTAILQ_EMPTY(&oh->waitinglist)) {
1090-
assert(oh->refcnt > 1);
1091-
hsh_rush1(wrk, oh, &rush, max);
1092-
}
1093-
1094-
if (oh->refcnt == 1)
1095-
assert(VTAILQ_EMPTY(&oh->waitinglist));
1096-
10971140
assert(oh->refcnt > 0);
1098-
r = hash->deref(wrk, oh); /* Unlocks oh->mtx */
1099-
hsh_rush2(wrk, &rush);
1100-
return (r);
1141+
return (hash->deref(wrk, oh)); /* Unlocks oh->mtx */
11011142
}
11021143

11031144
static int
@@ -1109,7 +1150,7 @@ hsh_deref_objhead(struct worker *wrk, struct objhead **poh)
11091150
TAKE_OBJ_NOTNULL(oh, poh, OBJHEAD_MAGIC);
11101151

11111152
Lck_Lock(&oh->mtx);
1112-
return (hsh_deref_objhead_unlock(wrk, &oh, 0));
1153+
return (hsh_deref_objhead_unlock(wrk, &oh));
11131154
}
11141155

11151156
void

bin/varnishd/cache/cache_obj.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ ObjNew(const struct worker *wrk)
142142
wrk->stats->n_objectcore++;
143143
oc->last_lru = NAN;
144144
oc->flags = OC_F_BUSY;
145+
VTAILQ_INIT(&oc->waitinglist);
145146

146147
oc->boc = obj_newboc();
147148

@@ -160,6 +161,7 @@ ObjDestroy(const struct worker *wrk, struct objcore **p)
160161

161162
CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
162163
TAKE_OBJ_NOTNULL(oc, p, OBJCORE_MAGIC);
164+
assert(VTAILQ_EMPTY(&oc->waitinglist));
163165
if (oc->boc != NULL)
164166
obj_deleteboc(&oc->boc);
165167
FREE_OBJ(oc);

0 commit comments

Comments
 (0)