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

Commit 000c62d

Browse files
committed
cache_req_fsm: Keep object cores referenced during the task
Context: Only while we hold a reference to an object are we allowed to access any attributes from it. With restarts, we might want to keep references to attributes of objects from previous restart iterations. Before this patch, this could lead to use-after-dereference, which was "fixed" by always copying object variables into the request's workspace. But that is wasteful, so we should rather make sure that we keep references until we are done with the task and not copy (which is the next commit). An argument had been made in the past that keeping object references until the end of the task would increase their lifetime by too much, but restarts in VCL really should be done within milliseconds in most cases - and if keeping references _is_ an actual problem in specific situations, those can be avoided by either not restarting or rolling back also. In general, until now we have charged all Varnish-Cache users with the cost for specific use cases, but we should rather only charge the specific use cases instead. Implementation: stash_oc() allocates basically a Variable Length Array (VLA) on the workspace with a fallback to heap. The fallback exists to not complicate the calling code with additional error handling. For each invocation, stash_oc() copies the passed objcore to one of max_restarts + 1 slots and clears the original location as HSH_DerefObjCore() would. Alternatives considered: * Dynamically allocating space for each objcore pointer was dismissed due to the substantial overhead. * Using a TASK_PRIV was tried and dismissed because this would require setting up a VRT_CTX or pulling the VRT_CTX out one level to the core of the FSM, which was intrusive and increased complexity substantially.
1 parent 5936324 commit 000c62d

3 files changed

Lines changed: 73 additions & 2 deletions

File tree

bin/varnishd/cache/cache.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ struct cli;
117117
struct http_conn;
118118
struct listen_sock;
119119
struct mempool;
120+
struct ocstash;
120121
struct objcore;
121122
struct objhead;
122123
struct pool;
@@ -548,6 +549,8 @@ struct req {
548549
struct vrt_privs privs[1];
549550

550551
struct vcf *vcf;
552+
553+
struct ocstash *ocstash;
551554
};
552555

553556
#define IS_TOPREQ(req) ((req)->top->topreq == (req))

bin/varnishd/cache/cache_req_fsm.c

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040

4141
#include "config.h"
4242

43+
#include <stdlib.h>
44+
4345
#include "cache_varnishd.h"
4446
#include "cache_filter.h"
4547
#include "cache_objhead.h"
@@ -78,6 +80,68 @@
7880
REQ_STEPS
7981
#undef REQ_STEP
8082

83+
/*--------------------------------------------------------------------
84+
* Remember additional objcores to deref when the task ends
85+
*/
86+
87+
struct ocstash {
88+
unsigned magic;
89+
#define OCSTASH_MAGIC 0xe361b34d
90+
unsigned malloced;
91+
unsigned n;
92+
unsigned l;
93+
struct objcore * ocs[] v_counted_by_(l);
94+
};
95+
96+
static void
97+
ocstash_fini(struct worker *wrk, struct ocstash **stashp)
98+
{
99+
struct ocstash *stash;
100+
unsigned u;
101+
102+
AN(stashp);
103+
if (*stashp == NULL)
104+
return;
105+
TAKE_OBJ_NOTNULL(stash, stashp, OCSTASH_MAGIC);
106+
assert(stash->n <= stash->l);
107+
for (u = 0; u < stash->n; u++)
108+
(void)HSH_DerefObjCore(wrk, &stash->ocs[u], HSH_RUSH_POLICY);
109+
if (stash->malloced)
110+
free(stash);
111+
}
112+
113+
static inline size_t
114+
stash_sz(unsigned cap)
115+
{
116+
return (offsetof(struct ocstash, ocs) + sizeof(struct objcore *) * cap);
117+
}
118+
119+
// never fails unless malloc() fails
120+
static void
121+
stash_oc(struct ocstash **stashp, struct objcore **ocp, struct ws *ws, unsigned l)
122+
{
123+
struct ocstash *stash;
124+
125+
AN(stashp);
126+
AN(ocp);
127+
128+
stash = *stashp;
129+
if (stash == NULL) {
130+
stash = WS_Alloc(ws, (unsigned)stash_sz(l));
131+
if (stash == NULL)
132+
stash = malloc(stash_sz(l));
133+
AN(stash);
134+
memset(stash, 0, stash_sz(l));
135+
stash->magic = OCSTASH_MAGIC;
136+
stash->l = l;
137+
*stashp = stash;
138+
}
139+
CHECK_OBJ(stash, OCSTASH_MAGIC);
140+
assert(stash->n < stash->l);
141+
TAKE_OBJ_NOTNULL(stash->ocs[stash->n], ocp, OBJCORE_MAGIC);
142+
stash->n++;
143+
}
144+
81145
/*--------------------------------------------------------------------
82146
* Handle "Expect:" and "Connection:" on incoming request
83147
*/
@@ -240,7 +304,7 @@ cnt_deliver(struct worker *wrk, struct req *req)
240304

241305
if (wrk->vpi->handling != VCL_RET_DELIVER) {
242306
HSH_Cancel(wrk, req->objcore, NULL);
243-
(void)HSH_DerefObjCore(wrk, &req->objcore, HSH_RUSH_POLICY);
307+
stash_oc(&req->ocstash, &req->objcore, req->ws, req->max_restarts + 1);
244308
http_Teardown(req->resp);
245309

246310
switch (wrk->vpi->handling) {
@@ -286,6 +350,8 @@ cnt_vclfail(struct worker *wrk, struct req *req)
286350
AZ(req->objcore);
287351
AZ(req->stale_oc);
288352

353+
ocstash_fini(wrk, &req->ocstash);
354+
289355
INIT_OBJ(ctx, VRT_CTX_MAGIC);
290356
VCL_Req2Ctx(ctx, req);
291357

@@ -557,6 +623,7 @@ cnt_fetch(struct worker *wrk, struct req *req)
557623
if (req->objcore->flags & OC_F_FAILED) {
558624
req->err_code = 503;
559625
req->req_step = R_STP_SYNTH;
626+
// VCL did not get to see this object, no need to stash
560627
(void)HSH_DerefObjCore(wrk, &req->objcore, 1);
561628
AZ(req->objcore);
562629
return (REQ_FSM_MORE);
@@ -1213,6 +1280,7 @@ CNT_Request(struct req *req)
12131280
}
12141281
wrk->vsl = NULL;
12151282
if (nxt == REQ_FSM_DONE) {
1283+
ocstash_fini(wrk, &req->ocstash);
12161284
INIT_OBJ(ctx, VRT_CTX_MAGIC);
12171285
VCL_Req2Ctx(ctx, req);
12181286
if (IS_TOPREQ(req)) {

vmod/tests/std_c00001.vtc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ varnish v1 -vcl+backend {
299299
set req.http.response = "123";
300300
set req.http.response2 = "Another response";
301301
if (req.url == "/9") {
302-
vtc.workspace_alloc(client, -200);
302+
vtc.workspace_alloc(client, -256);
303303
} else if (req.url == "/10") {
304304
vtc.workspace_alloc(client, -10);
305305
std.rollback(req);

0 commit comments

Comments
 (0)