TRACE_LEAVE(QMUX_EV_QCC_END);
}
-struct task *qcc_io_cb(struct task *t, void *ctx, unsigned int status)
+struct task *qcc_io_cb(struct task *t, void *ctx, unsigned int state)
{
struct qcc *qcc = ctx;
+ struct connection *conn;
+ int conn_in_list;
- TRACE_ENTER(QMUX_EV_QCC_WAKE, qcc->conn);
+ if (state & TASK_F_USR1) {
+ /* the tasklet was idling on an idle connection, it might have
+ * been stolen, let's be careful!
+ */
+ HA_SPIN_LOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
+ if (t->context == NULL) {
+ /* The connection has been taken over by another thread,
+ * we're no longer responsible for it, so just free the
+ * tasklet, and do nothing.
+ */
+ HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
+ tasklet_free((struct tasklet *)t);
+ t = NULL;
+ TRACE_LEAVE(QMUX_EV_QCC_WAKE);
+ return NULL;
+ }
+ conn = qcc->conn;
+ TRACE_ENTER(QMUX_EV_QCC_WAKE, conn);
+
+ /* Remove the connection from the list, to be sure nobody attempts
+ * to use it while we handle the I/O events
+ */
+ conn_in_list = conn->flags & (CO_FL_LIST_MASK|CO_FL_SESS_IDLE);
+ if (conn_in_list) {
+ if (conn->flags & CO_FL_SESS_IDLE)
+ session_detach_idle_conn(conn->owner, conn);
+ else
+ conn_delete_from_tree(conn);
+ }
+
+ HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
+ } else {
+ /* we're certain the connection was not in an idle list */
+ conn = qcc->conn;
+ TRACE_ENTER(QMUX_EV_QCC_WAKE, conn);
+ conn_in_list = 0;
+ }
if (!(qcc->wait_event.events & SUB_RETRY_SEND))
qcc_io_send(qcc);
qcc_io_recv(qcc);
if (qcc_io_process(qcc)) {
- TRACE_STATE("releasing dead connection", QMUX_EV_QCC_WAKE, qcc->conn);
+ TRACE_STATE("releasing dead connection", QMUX_EV_QCC_WAKE, conn);
goto release;
}
qcc_refresh_timeout(qcc);
/* Trigger pacing task is emission should be retried after some delay. */
- if (qcc_is_pacing_active(qcc->conn)) {
+ if (qcc_is_pacing_active(conn)) {
if (tick_isset(qcc->pacing_task->expire))
task_queue(qcc->pacing_task);
}
- TRACE_LEAVE(QMUX_EV_QCC_WAKE, qcc->conn);
+ if (conn_in_list) {
+ struct server *srv = __objt_server(conn->target);
+
+ if (srv->cur_admin & SRV_ADMF_MAINT) {
+ /* Do not store an idle conn if server in maintenance. */
+ goto release;
+ }
+
+ if (conn->flags & CO_FL_SESS_IDLE) {
+ if (!session_reinsert_idle_conn(conn->owner, conn)) {
+ /* session add conn failure */
+ goto release;
+ }
+ }
+ else {
+ HA_SPIN_LOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
+ _srv_add_idle(srv, conn, conn_in_list == CO_FL_SAFE_LIST);
+ HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
+ }
+
+ /* Do not access conn without protection as soon as it is reinserted in idle list. */
+ conn = NULL;
+ }
+
+ TRACE_LEAVE(QMUX_EV_QCC_WAKE, conn);
return t;
TRACE_ENTER(QMUX_EV_QCC_WAKE, qcc ? qcc->conn : NULL);
if (qcc) {
+ /* Make sure nobody stole the connection from us */
+ HA_SPIN_LOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
+
+ /* Somebody already stole the connection from us, so we should
+ * not free it, we just have to free the task.
+ */
+ if (!t->context) {
+ qcc = NULL;
+ HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
+ goto out;
+ }
+
if (!expired) {
+ HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
TRACE_DEVEL("not expired", QMUX_EV_QCC_WAKE, qcc->conn);
goto requeue;
}
if (!qcc_may_expire(qcc)) {
+ HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
TRACE_DEVEL("cannot expired", QMUX_EV_QCC_WAKE, qcc->conn);
t->expire = TICK_ETERNITY;
goto requeue;
}
+
+ /* We're about to destroy the connection, so make sure nobody
+ * attempts to steal it from us.
+ */
+ if (qcc->conn->flags & CO_FL_LIST_MASK)
+ conn_delete_from_tree(qcc->conn);
+ else if (qcc->conn->flags & CO_FL_SESS_IDLE)
+ session_unown_conn(qcc->conn->owner, qcc->conn);
+
+ HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
}
task_destroy(t);
qcs->sd = sd->sc->sedesc;
qcc->nb_sc++;
+ /* the connection is not idle anymore, let's mark this */
+ HA_ATOMIC_AND(&qcc->wait_event.tasklet->state, ~TASK_F_USR1);
+ xprt_set_used(qcc->conn, qcc->conn->xprt, qcc->conn->xprt_ctx);
+
TRACE_LEAVE(QMUX_EV_QCS_NEW, conn);
return 0;
}
goto release;
}
+ /* mark that the tasklet may lose its context to another thread and
+ * that the handler needs to check it under the idle conns lock.
+ */
+ HA_ATOMIC_OR(&qcc->wait_event.tasklet->state, TASK_F_USR1);
+ xprt_set_idle(qcc->conn, qcc->conn->xprt, qcc->conn->xprt_ctx);
+
/* Ensure session can keep a new idle connection. */
if (session_check_idle_conn(sess, conn)) {
TRACE_DEVEL("idle conn rejected by session", QMUX_EV_STRM_END, conn);
goto release;
}
+ /* At this point, the connection is inserted into
+ * session list and marked as idle, so it may already
+ * have been purged from another thread.
+ */
+ conn = NULL;
+ goto end;
}
}
else {
if (!qcc->nb_sc) {
TRACE_DEVEL("prepare for idle connection reuse", QMUX_EV_STRM_END, conn);
+
+ /* mark that the tasklet may lose its context to another thread and
+ * that the handler needs to check it under the idle conns lock.
+ */
+ HA_ATOMIC_OR(&qcc->wait_event.tasklet->state, TASK_F_USR1);
+ xprt_set_idle(qcc->conn, qcc->conn->xprt, qcc->conn->xprt_ctx);
+
if (!srv_add_to_idle_list(objt_server(conn->target), conn, 1)) {
/* Idle conn insert failure, gracefully close the connection. */
TRACE_DEVEL("idle connection cannot be kept on the server", QMUX_EV_STRM_END, conn);