@@ -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