[PATCH 12/19] nvmet-rdma: disconnect only established queues
Keith Busch
kbusch at meta.com
Thu Oct 29 08:52:45 PDT 2026
From: Chris Mason <mason at kernel.org>
Over iWARP, rdma_disconnect() sleeps in iw_cm_disconnect() until the
ESTABLISHED event of the connection has been handled, and iw_cm handles
all events of all connections one at a time on iw_cm_wq.
nvmet_rdma_destroy_port_queues(), nvmet_rdma_remove_one() and
nvmet_rdma_delete_ctrl() walk the queue list with
nvmet_rdma_queue_mutex held and call rdma_disconnect() for each, while
nvmet_rdma_cm_handler() takes the same mutex. If the walk sleeps on a
queue whose ESTABLISHED is queued behind another queue's event, both
are stuck and iWARP connection handling is dead until reboot. A peer
sets that up by connecting in a loop, or over siw by asking for MPA
peer-to-peer mode and then staying silent, which delays ESTABLISHED
indefinitely.
Only call rdma_disconnect() for a queue in NVMET_RDMA_Q_LIVE, which the
handler sets when it gets ESTABLISHED. A queue that never got there is
left to the release work: ib_drain_qp() moves the QP to the error
state, which ends the connection, and rdma_destroy_id() tells the peer.
Over IB and RoCE the skipped call did nothing more than that.
Fixes: f1d4ef7d8883 ("nvmet-rdma: register ib_client to not deadlock in device removal")
Fixes: fcf73a804c7d ("nvmet-rdma: fix use-after-free when a port is removed")
Cc: Christoph Hellwig <hch at lst.de>
Cc: Sagi Grimberg <sagi at grimberg.me>
Cc: Chaitanya Kulkarni <kch at nvidia.com>
Signed-off-by: Chris Mason <mason at kernel.org>
Signed-off-by: Jens Axboe <axboe at kernel.dk>
Signed-off-by: Keith Busch <kbusch at kernel.org>
---
drivers/nvme/target/rdma.c | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c
index fb20ba1b84e7f..fc0c85076cee4 100644
--- a/drivers/nvme/target/rdma.c
+++ b/drivers/nvme/target/rdma.c
@@ -1694,7 +1694,7 @@ static void nvmet_rdma_queue_established(struct nvmet_rdma_queue *queue)
static void __nvmet_rdma_queue_disconnect(struct nvmet_rdma_queue *queue)
{
- bool disconnect = false;
+ bool disconnect = false, live = false;
unsigned long flags;
pr_debug("cm_id= %p queue->state= %d\n", queue->cm_id, queue->state);
@@ -1713,6 +1713,7 @@ static void __nvmet_rdma_queue_disconnect(struct nvmet_rdma_queue *queue)
}
fallthrough;
case NVMET_RDMA_Q_LIVE:
+ live = queue->state == NVMET_RDMA_Q_LIVE;
queue->state = NVMET_RDMA_Q_DISCONNECTING;
disconnect = true;
break;
@@ -1722,7 +1723,17 @@ static void __nvmet_rdma_queue_disconnect(struct nvmet_rdma_queue *queue)
spin_unlock_irqrestore(&queue->state_lock, flags);
if (disconnect) {
- rdma_disconnect(queue->cm_id);
+ /*
+ * Over iWARP rdma_disconnect() sleeps until the CM has handled
+ * the ESTABLISHED event of this connection. The CM handles the
+ * events of all connections one at a time, and our handler
+ * takes nvmet_rdma_queue_mutex, which callers of this function
+ * hold. So a queue that has not seen the event is left to the
+ * release work: ib_drain_qp() moves the queue pair to the error
+ * state, which ends the connection.
+ */
+ if (live)
+ rdma_disconnect(queue->cm_id);
queue_work(nvmet_wq, &queue->release_work);
}
}
--
2.52.0
More information about the Linux-nvme
mailing list