Skip to content

Commit 17a1dac

Browse files
committed
MEDIUM: mux-quic: enforce thread-safety of backend idle conns
Complete QUIC MUX for backend side. Ensure access to idle connections are performed in a thread-safe way. Even if takeover is not yet implemented for this protocol, it is at least necessary to ensure that there won't be any issue with idle connections purging mechanism. This change will also be necessary to ensure that QUIC servers can safely be removed via CLI "del server". This is not yet sufficient as currently server deletion still relies on takeover for idle connections removal. However, this will be adjusted in a future patch to instead use idle connections standard purging mechanism.
1 parent 73fd12e commit 17a1dac

File tree

1 file changed

+114
-5
lines changed

1 file changed

+114
-5
lines changed

src/mux_quic.c

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3377,31 +3377,93 @@ static void qcc_release(struct qcc *qcc)
33773377
TRACE_LEAVE(QMUX_EV_QCC_END);
33783378
}
33793379

3380-
struct task *qcc_io_cb(struct task *t, void *ctx, unsigned int status)
3380+
struct task *qcc_io_cb(struct task *t, void *ctx, unsigned int state)
33813381
{
33823382
struct qcc *qcc = ctx;
3383+
struct connection *conn;
3384+
int conn_in_list;
33833385

3384-
TRACE_ENTER(QMUX_EV_QCC_WAKE, qcc->conn);
3386+
if (state & TASK_F_USR1) {
3387+
/* the tasklet was idling on an idle connection, it might have
3388+
* been stolen, let's be careful!
3389+
*/
3390+
HA_SPIN_LOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
3391+
if (t->context == NULL) {
3392+
/* The connection has been taken over by another thread,
3393+
* we're no longer responsible for it, so just free the
3394+
* tasklet, and do nothing.
3395+
*/
3396+
HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
3397+
tasklet_free((struct tasklet *)t);
3398+
t = NULL;
3399+
TRACE_LEAVE(QMUX_EV_QCC_WAKE);
3400+
return NULL;
3401+
}
3402+
conn = qcc->conn;
3403+
TRACE_ENTER(QMUX_EV_QCC_WAKE, conn);
3404+
3405+
/* Remove the connection from the list, to be sure nobody attempts
3406+
* to use it while we handle the I/O events
3407+
*/
3408+
conn_in_list = conn->flags & (CO_FL_LIST_MASK|CO_FL_SESS_IDLE);
3409+
if (conn_in_list) {
3410+
if (conn->flags & CO_FL_SESS_IDLE)
3411+
session_detach_idle_conn(conn->owner, conn);
3412+
else
3413+
conn_delete_from_tree(conn);
3414+
}
3415+
3416+
HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
3417+
} else {
3418+
/* we're certain the connection was not in an idle list */
3419+
conn = qcc->conn;
3420+
TRACE_ENTER(QMUX_EV_QCC_WAKE, conn);
3421+
conn_in_list = 0;
3422+
}
33853423

33863424
if (!(qcc->wait_event.events & SUB_RETRY_SEND))
33873425
qcc_io_send(qcc);
33883426

33893427
qcc_io_recv(qcc);
33903428

33913429
if (qcc_io_process(qcc)) {
3392-
TRACE_STATE("releasing dead connection", QMUX_EV_QCC_WAKE, qcc->conn);
3430+
TRACE_STATE("releasing dead connection", QMUX_EV_QCC_WAKE, conn);
33933431
goto release;
33943432
}
33953433

33963434
qcc_refresh_timeout(qcc);
33973435

33983436
/* Trigger pacing task is emission should be retried after some delay. */
3399-
if (qcc_is_pacing_active(qcc->conn)) {
3437+
if (qcc_is_pacing_active(conn)) {
34003438
if (tick_isset(qcc->pacing_task->expire))
34013439
task_queue(qcc->pacing_task);
34023440
}
34033441

3404-
TRACE_LEAVE(QMUX_EV_QCC_WAKE, qcc->conn);
3442+
if (conn_in_list) {
3443+
struct server *srv = __objt_server(conn->target);
3444+
3445+
if (srv->cur_admin & SRV_ADMF_MAINT) {
3446+
/* Do not store an idle conn if server in maintenance. */
3447+
goto release;
3448+
}
3449+
3450+
if (conn->flags & CO_FL_SESS_IDLE) {
3451+
if (!session_reinsert_idle_conn(conn->owner, conn)) {
3452+
/* session add conn failure */
3453+
goto release;
3454+
}
3455+
}
3456+
else {
3457+
HA_SPIN_LOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
3458+
_srv_add_idle(srv, conn, conn_in_list == CO_FL_SAFE_LIST);
3459+
HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
3460+
}
3461+
3462+
/* Do not access conn without protection as soon as it is reinserted in idle list. */
3463+
conn = NULL;
3464+
}
3465+
3466+
TRACE_LEAVE(QMUX_EV_QCC_WAKE, conn);
34053467

34063468
return t;
34073469

@@ -3443,16 +3505,40 @@ static struct task *qcc_timeout_task(struct task *t, void *ctx, unsigned int sta
34433505
TRACE_ENTER(QMUX_EV_QCC_WAKE, qcc ? qcc->conn : NULL);
34443506

34453507
if (qcc) {
3508+
/* Make sure nobody stole the connection from us */
3509+
HA_SPIN_LOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
3510+
3511+
/* Somebody already stole the connection from us, so we should
3512+
* not free it, we just have to free the task.
3513+
*/
3514+
if (!t->context) {
3515+
qcc = NULL;
3516+
HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
3517+
goto out;
3518+
}
3519+
34463520
if (!expired) {
3521+
HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
34473522
TRACE_DEVEL("not expired", QMUX_EV_QCC_WAKE, qcc->conn);
34483523
goto requeue;
34493524
}
34503525

34513526
if (!qcc_may_expire(qcc)) {
3527+
HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
34523528
TRACE_DEVEL("cannot expired", QMUX_EV_QCC_WAKE, qcc->conn);
34533529
t->expire = TICK_ETERNITY;
34543530
goto requeue;
34553531
}
3532+
3533+
/* We're about to destroy the connection, so make sure nobody
3534+
* attempts to steal it from us.
3535+
*/
3536+
if (qcc->conn->flags & CO_FL_LIST_MASK)
3537+
conn_delete_from_tree(qcc->conn);
3538+
else if (qcc->conn->flags & CO_FL_SESS_IDLE)
3539+
session_unown_conn(qcc->conn->owner, qcc->conn);
3540+
3541+
HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
34563542
}
34573543

34583544
task_destroy(t);
@@ -3750,6 +3836,10 @@ static int qmux_strm_attach(struct connection *conn, struct sedesc *sd, struct s
37503836
qcs->sd = sd->sc->sedesc;
37513837
qcc->nb_sc++;
37523838

3839+
/* the connection is not idle anymore, let's mark this */
3840+
HA_ATOMIC_AND(&qcc->wait_event.tasklet->state, ~TASK_F_USR1);
3841+
xprt_set_used(qcc->conn, qcc->conn->xprt, qcc->conn->xprt_ctx);
3842+
37533843
TRACE_LEAVE(QMUX_EV_QCS_NEW, conn);
37543844
return 0;
37553845
}
@@ -3806,16 +3896,35 @@ static void qmux_strm_detach(struct sedesc *sd)
38063896
goto release;
38073897
}
38083898

3899+
/* mark that the tasklet may lose its context to another thread and
3900+
* that the handler needs to check it under the idle conns lock.
3901+
*/
3902+
HA_ATOMIC_OR(&qcc->wait_event.tasklet->state, TASK_F_USR1);
3903+
xprt_set_idle(qcc->conn, qcc->conn->xprt, qcc->conn->xprt_ctx);
3904+
38093905
/* Ensure session can keep a new idle connection. */
38103906
if (session_check_idle_conn(sess, conn)) {
38113907
TRACE_DEVEL("idle conn rejected by session", QMUX_EV_STRM_END, conn);
38123908
goto release;
38133909
}
3910+
/* At this point, the connection is inserted into
3911+
* session list and marked as idle, so it may already
3912+
* have been purged from another thread.
3913+
*/
3914+
conn = NULL;
3915+
goto end;
38143916
}
38153917
}
38163918
else {
38173919
if (!qcc->nb_sc) {
38183920
TRACE_DEVEL("prepare for idle connection reuse", QMUX_EV_STRM_END, conn);
3921+
3922+
/* mark that the tasklet may lose its context to another thread and
3923+
* that the handler needs to check it under the idle conns lock.
3924+
*/
3925+
HA_ATOMIC_OR(&qcc->wait_event.tasklet->state, TASK_F_USR1);
3926+
xprt_set_idle(qcc->conn, qcc->conn->xprt, qcc->conn->xprt_ctx);
3927+
38193928
if (!srv_add_to_idle_list(objt_server(conn->target), conn, 1)) {
38203929
/* Idle conn insert failure, gracefully close the connection. */
38213930
TRACE_DEVEL("idle connection cannot be kept on the server", QMUX_EV_STRM_END, conn);

0 commit comments

Comments
 (0)