[PATCH v5 2/5] IB/isert: drain the QP without holding isert_conn->mutex
From: Yehyeong Lee
Date: Tue Nov 03 2026 - 09:10:29 EST
isert_disconnected_handler() drains the QP with isert_conn->mutex held.
The drain waits for the flush completions of the send and receive queues,
and those are reaped by ib_cq_poll_work() on ib-comp-wq, where
isert_login_recv_done() takes isert_conn->mutex before it looks at the
connection state. If a login receive completes while the handler holds
the mutex, the completion handler blocks on the mutex and the drain blocks
on the completion that handler would have reaped.
Return from isert_conn_terminate() whether this caller moved the
connection to ISER_CONN_TERMINATING, and drain after dropping
isert_conn->mutex. Only the owner of isert_conn->node drains, so two
callers cannot drain the same QP; the send and receive queues reserve room
for one drain each, and a second concurrent drain hits the WARN_ONCE in
__ib_drain_sq() and returns without draining.
Two test-only delays open the window, one in isert_login_recv_done() and
one before the drain. Three runs of 7.4-rc1 plus patch 1 of this series
printed ten hung task warnings each, eight, five and five of them waiting
in ib_drain_qp(). Three runs with this patch printed none, and
PROVE_LOCKING reported no splat in two runs a side.
INFO: task kworker/0:1:10 blocked for more than 10 seconds.
Not tainted 7.4.0-rc1-NS1-00002-g7b7ecb49489e #121
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:kworker/0:1 state:D stack:26248 pid:10 tgid:10 ppid:2 task_flags:0x4208060 flags:0x00080000
Workqueue: ib_cm cm_work_handler
Call Trace:
<TASK>
__schedule+0x180f/0x4bb0
? _raw_spin_lock_irq+0x1/0xe0
? __pfx___schedule+0x10/0x10
schedule+0x70/0x1f0
schedule_timeout+0x1cd/0x200
? __pfx_schedule_timeout+0x10/0x10
? rxe_qp_from_attr+0x1497/0x18c0
? __pfx__raw_spin_lock_irq+0x10/0x10
? __pfx__raw_spin_lock_irqsave+0x10/0x10
? _raw_spin_lock_irqsave+0x86/0xe0
__wait_for_common+0x29e/0x420
? __pfx_schedule_timeout+0x10/0x10
? __pfx___wait_for_common+0x10/0x10
__ib_drain_sq+0x251/0x340
? __pfx___ib_drain_sq+0x10/0x10
? __pfx_ib_drain_qp_done+0x10/0x10
? irq_work_queue+0x1c/0xb0
? __pfx__printk+0x10/0x10
? __pfx_process_timeout+0x10/0x10
ib_drain_sq+0x146/0x1a0
ib_drain_qp+0xd/0x50
isert_disconnected_handler.isra.0+0xc4/0x3f0
isert_cma_handler+0xd74/0x27d0
? kasan_save_stack+0x42/0x60
? kasan_save_stack+0x33/0x60
? kasan_save_track+0x14/0x30
? kasan_save_free_info+0x3b/0x60
? __kasan_slab_free+0x43/0x70
? kfree+0x121/0x390
? cm_process_work+0x8b/0x330
? __pfx_isert_cma_handler+0x10/0x10
? kthread+0x2c8/0x3b0
? ret_from_fork+0x36e/0x5a0
? ret_from_fork_asm+0x1a/0x30
? _raw_spin_lock_irqsave+0x86/0xe0
cma_cm_event_handler+0x3e/0x240
cma_ib_handler+0x190/0x750
? __pfx_cma_ib_handler+0x10/0x10
cm_process_work+0x41/0x330
cm_work_handler+0x582b/0xc160
? _raw_spin_lock_irqsave+0x86/0xe0
? __pfx__raw_spin_lock_irqsave+0x10/0x10
? hrtimer_start_range_ns+0xa6/0x2c0
? __pfx_cm_work_handler+0x10/0x10
? finish_task_switch.isra.0+0x1d9/0xa70
process_one_work+0x697/0x1060
? __pfx___schedule+0x10/0x10
? __pfx_process_one_work+0x10/0x10
? _raw_spin_lock_irq+0x81/0xe0
? __pfx_cm_work_handler+0x10/0x10
? assign_work+0x11d/0x370
worker_thread+0x45b/0xd10
? __pfx_worker_thread+0x10/0x10
? __pfx_worker_thread+0x10/0x10
kthread+0x2c8/0x3b0
? recalc_sigpending+0x15c/0x1e0
? __pfx_kthread+0x10/0x10
ret_from_fork+0x36e/0x5a0
? __pfx_ret_from_fork+0x10/0x10
? __switch_to+0x572/0xdc0
? __pfx_kthread+0x10/0x10
ret_from_fork_asm+0x1a/0x30
</TASK>
Fixes: 6d1fba0c2cc7 ("iser-target: Rework connection termination")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yehyeong Lee <yhlee@xxxxxxxxxxxxxxxxxx>
---
drivers/infiniband/ulp/isert/ib_isert.c | 75 ++++++++++++-------------
1 file changed, 36 insertions(+), 39 deletions(-)
diff --git a/drivers/infiniband/ulp/isert/ib_isert.c b/drivers/infiniband/ulp/isert/ib_isert.c
index ef2edaa3218fee..61b214012a70b0 100644
--- a/drivers/infiniband/ulp/isert/ib_isert.c
+++ b/drivers/infiniband/ulp/isert/ib_isert.c
@@ -556,43 +556,21 @@ isert_put_conn(struct isert_conn *isert_conn)
kref_put(&isert_conn->kref, isert_release_kref);
}
-static void
-isert_handle_unbound_conn(struct isert_conn *isert_conn)
-{
- struct isert_np *isert_np = isert_conn->cm_id->context;
-
- mutex_lock(&isert_np->mutex);
- if (!list_empty(&isert_conn->node)) {
- /*
- * This means iscsi doesn't know this connection
- * so schedule a cleanup ourselves
- */
- list_del_init(&isert_conn->node);
- isert_put_conn(isert_conn);
- queue_work(isert_release_wq, &isert_conn->release_work);
- }
- mutex_unlock(&isert_np->mutex);
-}
-
/**
* isert_conn_terminate() - Initiate connection termination
* @isert_conn: isert connection struct
*
- * Notes:
- * In case the connection state is BOUND, move state
- * to TEMINATING and start teardown sequence (rdma_disconnect).
- * In case the connection state is UP, complete flush as well.
- *
- * This routine must be called with mutex held. Thus it is
- * safe to call multiple times.
+ * Return: true if this caller moved the connection to ISER_CONN_TERMINATING.
*/
-static void
+static bool
isert_conn_terminate(struct isert_conn *isert_conn)
{
int err;
+ lockdep_assert_held(&isert_conn->mutex);
+
if (isert_conn->state >= ISER_CONN_TERMINATING)
- return;
+ return false;
isert_info("Terminating conn %p state %d\n",
isert_conn, isert_conn->state);
@@ -601,6 +579,8 @@ isert_conn_terminate(struct isert_conn *isert_conn)
if (err)
isert_warn("Failed rdma_disconnect isert_conn %p\n",
isert_conn);
+
+ return true;
}
static int
@@ -635,26 +615,44 @@ isert_disconnected_handler(struct rdma_cm_id *cma_id,
enum rdma_cm_event_type event)
{
struct isert_conn *isert_conn = cma_id->qp->qp_context;
+ struct isert_np *isert_np = cma_id->context;
+ bool owned = false, bound = false, terminated = false;
mutex_lock(&isert_conn->mutex);
switch (isert_conn->state) {
- case ISER_CONN_TERMINATING:
- break;
case ISER_CONN_UP:
- isert_conn_terminate(isert_conn);
- ib_drain_qp(isert_conn->qp);
- isert_handle_unbound_conn(isert_conn);
+ mutex_lock(&isert_np->mutex);
+ if (list_empty(&isert_conn->node)) {
+ bound = isert_conn->state == ISER_CONN_BOUND ||
+ isert_conn->state == ISER_CONN_FULL_FEATURE;
+ } else {
+ owned = true;
+ list_del_init(&isert_conn->node);
+ }
+ mutex_unlock(&isert_np->mutex);
break;
case ISER_CONN_BOUND:
case ISER_CONN_FULL_FEATURE: /* FALLTHRU */
- iscsit_cause_connection_reinstatement(isert_conn->conn, 0);
+ bound = true;
break;
default:
- isert_warn("conn %p terminating in state %d\n",
- isert_conn, isert_conn->state);
+ break;
}
+
+ if (owned)
+ terminated = isert_conn_terminate(isert_conn);
+ else if (bound)
+ iscsit_cause_connection_reinstatement(isert_conn->conn, 0);
mutex_unlock(&isert_conn->mutex);
+ if (!owned)
+ return 0;
+
+ if (terminated)
+ ib_drain_qp(isert_conn->qp);
+ isert_put_conn(isert_conn);
+ queue_work(isert_release_wq, &isert_conn->release_work);
+
return 0;
}
@@ -665,8 +663,6 @@ isert_connect_error(struct rdma_cm_id *cma_id)
struct isert_np *isert_np = cma_id->context;
bool owned;
- ib_drain_qp(isert_conn->qp);
-
mutex_lock(&isert_np->mutex);
owned = !list_empty(&isert_conn->node);
list_del_init(&isert_conn->node);
@@ -677,6 +673,7 @@ isert_connect_error(struct rdma_cm_id *cma_id)
return 0;
}
+ ib_drain_qp(isert_conn->qp);
isert_conn->cm_id = NULL;
isert_put_conn(isert_conn);
@@ -2479,11 +2476,10 @@ isert_accept_np(struct iscsi_np *np, struct iscsit_conn *conn)
isert_conn = list_first_entry(&isert_np->pending,
struct isert_conn, node);
list_del_init(&isert_conn->node);
- mutex_unlock(&isert_np->mutex);
-
conn->context = isert_conn;
isert_conn->conn = conn;
isert_conn->state = ISER_CONN_BOUND;
+ mutex_unlock(&isert_np->mutex);
isert_set_conn_info(np, conn, isert_conn);
@@ -2547,6 +2543,7 @@ isert_free_np(struct iscsi_np *np)
isert_info("cleaning isert_conn %p state (%d)\n",
isert_conn, isert_conn->state);
+ ib_drain_qp(isert_conn->qp);
if (promoted)
isert_put_conn(isert_conn);
isert_put_conn(isert_conn);
--
2.43.0