[PATCH v2 4/8] md: defer the io_opt update out of the sync thread
From: Jack Wang <jinpu.wang@ionos.com>
Date: 2026-09-10 08:11:23
Also in:
dm-devel, linux-raid, lkml
Subsystem:
software raid (multiple disks) support, the rest · Maintainers:
Song Liu, Yu Kuai, Linus Torvalds
From: Jack Wang <jinpu.wang@cloud.ionos.com> mddev_update_io_opt() runs from end_reshape() in the sync thread, and md_reap_sync_thread() waits for that thread with reconfig_mutex held. Taking q->limits_lock there hangs a finishing reshape whenever the lock's holder waits for I/O that only md_check_recovery() can let complete, and no ordering avoids it: the sync thread is what lets that I/O finish. Hand the update to a work item, which holds neither reconfig_mutex nor the suspend and so takes q->limits_lock in the order the rest of md uses, before suspending. __md_stop() flushes it, as it suspends the array. Also give the function a queue_limits argument, so a caller that already owns an update has it changed in place; the users of that path follow. Assisted-by: LLM Signed-off-by: Jack Wang <jinpu.wang@cloud.ionos.com> --- drivers/md/md.c | 49 ++++++++++++++++++++++++++++++++++++++------- drivers/md/md.h | 7 ++++++- drivers/md/raid10.c | 2 +- drivers/md/raid5.c | 2 +- 4 files changed, 50 insertions(+), 10 deletions(-)
diff --git a/drivers/md/md.c b/drivers/md/md.c
index 3067ea05ba27..87e17ba86d93 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c@@ -671,6 +671,7 @@ void mddev_put(struct mddev *mddev) static void md_safemode_timeout(struct timer_list *t); static void md_start_sync(struct work_struct *ws); +static void md_io_opt_work(struct work_struct *ws); static void active_io_release(struct percpu_ref *ref) {
@@ -794,6 +795,7 @@ int mddev_init(struct mddev *mddev) mddev->level = LEVEL_NONE; INIT_WORK(&mddev->sync_work, md_start_sync); + INIT_WORK(&mddev->io_opt_work, md_io_opt_work); INIT_WORK(&mddev->del_work, mddev_delayed_delete); return 0;
@@ -6330,20 +6332,51 @@ int mddev_stack_rdev_into(struct mddev *mddev, struct md_rdev *rdev, EXPORT_SYMBOL_GPL(mddev_stack_rdev_into); /* update the optimal I/O size after a reshape */ -void mddev_update_io_opt(struct mddev *mddev, unsigned int nr_stripes) +static void md_io_opt_work(struct work_struct *ws) { + struct mddev *mddev = container_of(ws, struct mddev, io_opt_work); + struct request_queue *q = mddev->gendisk->queue; struct queue_limits lim; + /* + * Nothing is held here, so take q->limits_lock in the order the rest + * of md uses: before the suspend, see md_start_sync(). + */ + lim = queue_limits_start_update(q); + if (mddev_suspend(mddev, false) < 0) { + queue_limits_cancel_update(q); + return; + } + lim.io_opt = lim.io_min * READ_ONCE(mddev->io_opt_nr_stripes); + if (queue_limits_commit_update(q, &lim)) + pr_err("%s: could not apply queue limits\n", mdname(mddev)); + mddev_resume(mddev); +} + +void mddev_update_io_opt(struct mddev *mddev, unsigned int nr_stripes, + struct queue_limits *lim) +{ if (mddev_is_dm(mddev)) return; - /* don't bother updating io_opt if we can't suspend the array */ - if (mddev_suspend(mddev, false) < 0) + /* + * With an update owned by the caller just change it in place; it is + * committed, and the array resumed, by whoever started it. Taking + * q->limits_lock here would nest it inside reconfig_mutex and the + * suspend, which deadlocks, see md_start_sync(). + */ + if (lim) { + lim->io_opt = lim->io_min * nr_stripes; return; - lim = queue_limits_start_update(mddev->gendisk->queue); - lim.io_opt = lim.io_min * nr_stripes; - queue_limits_commit_update(mddev->gendisk->queue, &lim); - mddev_resume(mddev); + } + + /* + * Called from the sync thread, which md_reap_sync_thread() waits for + * with reconfig_mutex held, so q->limits_lock cannot be taken here + * either. Hand it to a work item that holds neither. + */ + WRITE_ONCE(mddev->io_opt_nr_stripes, nr_stripes); + queue_work(md_misc_wq, &mddev->io_opt_work); } EXPORT_SYMBOL_GPL(mddev_update_io_opt);
@@ -7139,6 +7172,8 @@ static void __md_stop(struct mddev *mddev) { struct md_personality *pers = mddev->pers; + /* the deferred io_opt update suspends the array, so let it finish */ + flush_work(&mddev->io_opt_work); mddev_detach(mddev); md_bitmap_destroy(mddev); spin_lock(&mddev->lock);
diff --git a/drivers/md/md.h b/drivers/md/md.h
index ebdd57677062..1b2e8720f0d1 100644
--- a/drivers/md/md.h
+++ b/drivers/md/md.h@@ -554,6 +554,10 @@ struct mddev { /* used for register new sync thread */ struct work_struct sync_work; + /* deferred io_opt update, see mddev_update_io_opt() */ + struct work_struct io_opt_work; + unsigned int io_opt_nr_stripes; + /* "lock" protects: * flush_bio transition from NULL to !NULL * rdev superblocks, events
@@ -1056,7 +1060,8 @@ int mddev_stack_rdev_into(struct mddev *mddev, struct md_rdev *rdev, * is added with the array's current limits. */ #define MDDEV_STACK_SKIP ((struct queue_limits *)ERR_PTR(-EAGAIN)) -void mddev_update_io_opt(struct mddev *mddev, unsigned int nr_stripes); +void mddev_update_io_opt(struct mddev *mddev, unsigned int nr_stripes, + struct queue_limits *lim); extern const struct block_device_operations md_fops;
diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index 222bd7badcff..5580ca77ef1e 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c@@ -4932,7 +4932,7 @@ static void end_reshape(struct r10conf *conf) conf->reshape_safe = MaxSector; spin_unlock_irq(&conf->device_lock); - mddev_update_io_opt(conf->mddev, raid10_nr_stripes(conf)); + mddev_update_io_opt(conf->mddev, raid10_nr_stripes(conf), NULL); conf->fullsync = 0; }
diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 0ec555ada64a..3faa2a94c03b 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c@@ -8800,7 +8800,7 @@ static void end_reshape(struct r5conf *conf) wake_up(&conf->wait_for_reshape); mddev_update_io_opt(conf->mddev, - conf->raid_disks - conf->max_degraded); + conf->raid_disks - conf->max_degraded, NULL); } }
--
2.43.0