Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

nvmet: move async event work off nvmet-wq

For target nvmet_ctrl_free() flushes ctrl->async_event_work.
If nvmet_ctrl_free() runs on nvmet-wq, the flush re-enters workqueue
completion for the same worker:-

A. Async event work queued on nvmet-wq (prior to disconnect):
nvmet_execute_async_event()
queue_work(nvmet_wq, &ctrl->async_event_work)

nvmet_add_async_event()
queue_work(nvmet_wq, &ctrl->async_event_work)

B. Full pre-work chain (RDMA CM path):
nvmet_rdma_cm_handler()
nvmet_rdma_queue_disconnect()
__nvmet_rdma_queue_disconnect()
queue_work(nvmet_wq, &queue->release_work)
process_one_work()
lock((wq_completion)nvmet-wq) <--------- 1st
nvmet_rdma_release_queue_work()

C. Recursive path (same worker):
nvmet_rdma_release_queue_work()
nvmet_rdma_free_queue()
nvmet_sq_destroy()
nvmet_ctrl_put()
nvmet_ctrl_free()
flush_work(&ctrl->async_event_work)
__flush_work()
touch_wq_lockdep_map()
lock((wq_completion)nvmet-wq) <--------- 2nd

Lockdep splat:

============================================
WARNING: possible recursive locking detected
6.19.0-rc3nvme+ #14 Tainted: G N
--------------------------------------------
kworker/u192:42/44933 is trying to acquire lock:
ffff888118a00948 ((wq_completion)nvmet-wq){+.+.}-{0:0}, at: touch_wq_lockdep_map+0x26/0x90

but task is already holding lock:
ffff888118a00948 ((wq_completion)nvmet-wq){+.+.}-{0:0}, at: process_one_work+0x53e/0x660

3 locks held by kworker/u192:42/44933:
#0: ffff888118a00948 ((wq_completion)nvmet-wq){+.+.}-{0:0}, at: process_one_work+0x53e/0x660
#1: ffffc9000e6cbe28 ((work_completion)(&queue->release_work)){+.+.}-{0:0}, at: process_one_work+0x1c5/0x660
#2: ffffffff82d4db60 (rcu_read_lock){....}-{1:3}, at: __flush_work+0x62/0x530

Workqueue: nvmet-wq nvmet_rdma_release_queue_work [nvmet_rdma]
Call Trace:
__flush_work+0x268/0x530
nvmet_ctrl_free+0x140/0x310 [nvmet]
nvmet_cq_put+0x74/0x90 [nvmet]
nvmet_rdma_free_queue+0x23/0xe0 [nvmet_rdma]
nvmet_rdma_release_queue_work+0x19/0x50 [nvmet_rdma]
process_one_work+0x206/0x660
worker_thread+0x184/0x320
kthread+0x10c/0x240
ret_from_fork+0x319/0x390

Move async event work to a dedicated nvmet-aen-wq to avoid reentrant
flush on nvmet-wq.

Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Chaitanya Kulkarni <kch@nvidia.com>
Signed-off-by: Keith Busch <kbusch@kernel.org>

authored by

Chaitanya Kulkarni and committed by
Keith Busch
2922e350 b4e78f14

+15 -3
+1 -1
drivers/nvme/target/admin-cmd.c
··· 1586 1586 ctrl->async_event_cmds[ctrl->nr_async_event_cmds++] = req; 1587 1587 mutex_unlock(&ctrl->lock); 1588 1588 1589 - queue_work(nvmet_wq, &ctrl->async_event_work); 1589 + queue_work(nvmet_aen_wq, &ctrl->async_event_work); 1590 1590 } 1591 1591 1592 1592 void nvmet_execute_keep_alive(struct nvmet_req *req)
+12 -2
drivers/nvme/target/core.c
··· 27 27 28 28 struct workqueue_struct *nvmet_wq; 29 29 EXPORT_SYMBOL_GPL(nvmet_wq); 30 + struct workqueue_struct *nvmet_aen_wq; 31 + EXPORT_SYMBOL_GPL(nvmet_aen_wq); 30 32 31 33 /* 32 34 * This read/write semaphore is used to synchronize access to configuration ··· 208 206 list_add_tail(&aen->entry, &ctrl->async_events); 209 207 mutex_unlock(&ctrl->lock); 210 208 211 - queue_work(nvmet_wq, &ctrl->async_event_work); 209 + queue_work(nvmet_aen_wq, &ctrl->async_event_work); 212 210 } 213 211 214 212 static void nvmet_add_to_changed_ns_log(struct nvmet_ctrl *ctrl, __le32 nsid) ··· 1961 1959 if (!nvmet_wq) 1962 1960 goto out_free_buffered_work_queue; 1963 1961 1962 + nvmet_aen_wq = alloc_workqueue("nvmet-aen-wq", 1963 + WQ_MEM_RECLAIM | WQ_UNBOUND, 0); 1964 + if (!nvmet_aen_wq) 1965 + goto out_free_nvmet_work_queue; 1966 + 1964 1967 error = nvmet_init_debugfs(); 1965 1968 if (error) 1966 - goto out_free_nvmet_work_queue; 1969 + goto out_free_nvmet_aen_work_queue; 1967 1970 1968 1971 error = nvmet_init_discovery(); 1969 1972 if (error) ··· 1984 1977 nvmet_exit_discovery(); 1985 1978 out_exit_debugfs: 1986 1979 nvmet_exit_debugfs(); 1980 + out_free_nvmet_aen_work_queue: 1981 + destroy_workqueue(nvmet_aen_wq); 1987 1982 out_free_nvmet_work_queue: 1988 1983 destroy_workqueue(nvmet_wq); 1989 1984 out_free_buffered_work_queue: ··· 2003 1994 nvmet_exit_discovery(); 2004 1995 nvmet_exit_debugfs(); 2005 1996 ida_destroy(&cntlid_ida); 1997 + destroy_workqueue(nvmet_aen_wq); 2006 1998 destroy_workqueue(nvmet_wq); 2007 1999 destroy_workqueue(buffered_io_wq); 2008 2000 destroy_workqueue(zbd_wq);
+1
drivers/nvme/target/nvmet.h
··· 501 501 extern struct workqueue_struct *buffered_io_wq; 502 502 extern struct workqueue_struct *zbd_wq; 503 503 extern struct workqueue_struct *nvmet_wq; 504 + extern struct workqueue_struct *nvmet_aen_wq; 504 505 505 506 static inline void nvmet_set_result(struct nvmet_req *req, u32 result) 506 507 {
+1
drivers/nvme/target/rdma.c
··· 2088 2088 mutex_unlock(&nvmet_rdma_queue_mutex); 2089 2089 2090 2090 flush_workqueue(nvmet_wq); 2091 + flush_workqueue(nvmet_aen_wq); 2091 2092 } 2092 2093 2093 2094 static struct ib_client nvmet_rdma_ib_client = {