Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cache_vcl: Do the temperature dance for backend creation races #4205

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions bin/varnishd/cache/cache_vcl.c
Original file line number Diff line number Diff line change
Expand Up @@ -436,16 +436,18 @@ VCL_IterDirector(struct cli *cli, const char *pat,
}

static void
vcl_BackendEvent(const struct vcl *vcl, enum vcl_event_e e)
vcl_BackendEvent(const struct vcl *vcl, struct director_head *head,
enum vcl_event_e e)
{
struct vcldir *vdir;

ASSERT_CLI();
CHECK_OBJ_NOTNULL(vcl, VCL_MAGIC);
AZ(vcl->busy);

// XXX should we go unlocked? #4199 #4142
Lck_Lock(&vcl_mtx);
VTAILQ_FOREACH(vdir, &vcl->director_list, list)
VTAILQ_FOREACH(vdir, head, list)
VDI_Event(vdir->dir, e);
Lck_Unlock(&vcl_mtx);
}
Expand Down Expand Up @@ -585,6 +587,7 @@ vcl_print_refs(const struct vcl *vcl)
static int
vcl_set_state(struct vcl *vcl, const char *state, struct vsb **msg)
{
struct director_head colddirs, warmdirs;
struct vsb *nomsg = NULL;
int i = 0;

Expand All @@ -606,7 +609,7 @@ vcl_set_state(struct vcl *vcl, const char *state, struct vsb **msg)
vcl->temp = VTAILQ_EMPTY(&vcl->ref_list) ?
VCL_TEMP_COLD : VCL_TEMP_COOLING;
Lck_Unlock(&vcl_mtx);
vcl_BackendEvent(vcl, VCL_EVENT_COLD);
vcl_BackendEvent(vcl, &vcl->director_list, VCL_EVENT_COLD);
AZ(vcl_send_event(vcl, VCL_EVENT_COLD, msg));
AZ(*msg);
}
Expand All @@ -629,17 +632,44 @@ vcl_set_state(struct vcl *vcl, const char *state, struct vsb **msg)
i = -1;
}
else {
/* only send the WARM event to directors created before
* the WARM event. This assumes no deletes, see #4142
*/

VTAILQ_INIT(&colddirs);
VTAILQ_INIT(&warmdirs);

Lck_Lock(&vcl_mtx);
VTAILQ_SWAP(&vcl->director_list, &colddirs, vcldir, list);
vcl->temp = VCL_TEMP_WARM;
Lck_Unlock(&vcl_mtx);
Comment on lines 642 to 645
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe to empty the vcl->director_list at any point? If a VMOD makes a VRT_DelDirector() call for a director that existed before this list swap, the VTAILQ_REMOVE() in vcldir_retire() does nothing and we get a use-after-free when swapping the list back:

Lck_Lock(&vcl_mtx);
temp = vdir->vcl->temp;
VTAILQ_REMOVE(&vdir->vcl->director_list, vdir, list);
Lck_Unlock(&vcl_mtx);

Maybe I am missing a step in the temperature dance that would make this scenario impossible, but I think it at least breaks the assumption that once a director is added to the director_list it will be there until you remove it (and its refcount reaches 0).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @AlveElde, you are right that this is brittle.

Because VTAILQ_REMOVE(head, elm, field) only touches head if elm is last in the list, this would only affect the case where the last director added before the swap is removed after the concat, but yes, it is another race. Less relevant, but still a race.

This is not the only place where removing directors is a problem, which is why I suggested a solution in #4142.

Maybe it's best to bring that one in first and then update this PR accordingly.


i = vcl_send_event(vcl, VCL_EVENT_WARM, msg);
if (i == 0) {
vcl_BackendEvent(vcl, VCL_EVENT_WARM);
vcl_BackendEvent(vcl, &colddirs, VCL_EVENT_WARM);
Lck_Lock(&vcl_mtx);
VTAILQ_CONCAT(&vcl->director_list, &colddirs, list);
Lck_Unlock(&vcl_mtx);
assert(VTAILQ_EMPTY(&colddirs));
break;
}
AZ(vcl_send_event(vcl, VCL_EVENT_COLD, &nomsg));
AZ(nomsg);

Lck_Lock(&vcl_mtx);
VTAILQ_SWAP(&vcl->director_list, &warmdirs, vcldir, list);
assert(VTAILQ_EMPTY(&vcl->director_list));
VTAILQ_SWAP(&vcl->director_list, &colddirs, vcldir, list);
vcl->temp = VCL_TEMP_COLD;
Lck_Unlock(&vcl_mtx);

vcl_BackendEvent(vcl, &warmdirs, VCL_EVENT_COLD);
Lck_Lock(&vcl_mtx);
VTAILQ_CONCAT(&vcl->director_list, &warmdirs, list);
Lck_Unlock(&vcl_mtx);

assert(VTAILQ_EMPTY(&colddirs));
assert(VTAILQ_EMPTY(&warmdirs));
}
break;
default:
Expand Down
4 changes: 2 additions & 2 deletions bin/varnishd/cache/cache_vcl.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct vfilter;
struct vcltemp;

VTAILQ_HEAD(vfilter_head, vfilter);

VTAILQ_HEAD(director_head, vcldir);

struct vcl {
unsigned magic;
Expand All @@ -50,7 +50,7 @@ struct vcl {
unsigned busy;
unsigned discard;
const struct vcltemp *temp;
VTAILQ_HEAD(,vcldir) director_list;
struct director_head director_list;
VTAILQ_HEAD(,vclref) ref_list;
int nrefs;
struct vcl *label;
Expand Down