From: Jack Wang <jinpu.wang@ionos.com>
To: Song Liu <song@kernel.org>, Yu Kuai <yukuai@fygo.io>,
linux-raid@vger.kernel.org, Nilay Shroff <nilay@linux.ibm.com>,
abd.masalkhi@gmail.com
Cc: linux-block@vger.kernel.org, Jens Axboe <axboe@kernel.dk>,
Christoph Hellwig <hch@lst.de>,
Damien Le Moal <dlemoal@kernel.org>,
Ming Lei <tom.leiming@gmail.com>, Xiao Ni <xiao@kernel.org>,
Li Nan <magiclinan@didiglobal.com>,
Mike Snitzer <snitzer@kernel.org>,
Mikulas Patocka <mpatocka@redhat.com>,
dm-devel@lists.linux.dev, linux-kernel@vger.kernel.org,
Jack Wang <jinpu.wang@cloud.ionos.com>
Subject: [PATCH v2 5/8] md: take q->limits_lock before locking and suspending the array
Date: Thu, 10 Sep 2026 10:11:10 +0200 [thread overview]
Message-ID: <20260910081114.1605746-6-jinpu.wang@ionos.com> (raw)
In-Reply-To: <20260910081114.1605746-1-jinpu.wang@ionos.com>
From: Jack Wang <jinpu.wang@cloud.ionos.com>
Writing a queue limits attribute while a spare is re-added deadlocks the
array:
udev-worker queue_attr_store() holds q->limits_lock, waits in
blk_mq_freeze_queue() for q_usage_counter to drain
fio holds a q_usage_counter reference, parked in
md_handle_request()'s is_suspended() loop
mdadm suspended the array, waits for reconfig_mutex
md_start_sync holds reconfig_mutex, waits for q->limits_lock
Blocking on q->limits_lock while holding reconfig_mutex, or with the
array suspended, is waiting for normal I/O, which mddev_suspend()
already warns about with lockdep_assert_not_held(). So q->limits_lock
has to nest outside both.
Take the update before the array is locked and suspended, and pass it
down so the personality stacks into it:
- md_start_sync(), at both suspend points
- md_ioctl() for ADD_NEW_DISK and HOT_REMOVE_DISK
- rdev_attr_store(), for slot and for state "remove"/"re-add"
- raid5 skip_copy_store(), which took the lock while suspended
They are converted together because a mix of the two orders is an ABBA.
All of them commit while the array is still quiesced.
Two callers still take the lock inside reconfig_mutex with the array
suspended: ->start_reshape() from action_store(), which suspends before
flushing sync_work so the update cannot be held across it, and
raid*_run() from level_store(), which a later patch converts.
Verified with a raid1 of two ram devices, fio in flight and a loop
writing queue/max_sectors_kb: 20 fail/remove/add cycles complete, where
the same test wedges the array before the change.
Fixes: c99f66e4084a ("block: fix queue freeze vs limits lock order in sysfs store methods")
Assisted-by: LLM
Signed-off-by: Jack Wang <jinpu.wang@cloud.ionos.com>
---
drivers/md/md-autodetect.c | 2 +-
drivers/md/md.c | 120 +++++++++++++++++++++++++++++++------
drivers/md/md.h | 3 +-
drivers/md/raid5.c | 31 +++++++---
4 files changed, 128 insertions(+), 28 deletions(-)
diff --git a/drivers/md/md-autodetect.c b/drivers/md/md-autodetect.c
index 4b80165afd23..929513109657 100644
--- a/drivers/md/md-autodetect.c
+++ b/drivers/md/md-autodetect.c
@@ -213,7 +213,7 @@ static void __init md_setup_drive(struct md_setup_args *args)
(1 << MD_DISK_ACTIVE) | (1 << MD_DISK_SYNC);
}
- md_add_new_disk(mddev, &dinfo);
+ md_add_new_disk(mddev, &dinfo, NULL);
}
if (!err)
diff --git a/drivers/md/md.c b/drivers/md/md.c
index 87e17ba86d93..0668a048db71 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -2983,7 +2983,7 @@ void md_update_sb(struct mddev *mddev, int force_change)
}
EXPORT_SYMBOL(md_update_sb);
-static int add_bound_rdev(struct md_rdev *rdev)
+static int add_bound_rdev(struct md_rdev *rdev, struct queue_limits *lim)
{
struct mddev *mddev = rdev->mddev;
int err = 0;
@@ -2996,7 +2996,7 @@ static int add_bound_rdev(struct md_rdev *rdev)
*/
super_types[mddev->major_version].
validate_super(mddev, NULL/*freshest*/, rdev);
- err = mddev->pers->hot_add_disk(mddev, rdev, NULL);
+ err = mddev->pers->hot_add_disk(mddev, rdev, lim);
if (err) {
md_kick_rdev_from_array(rdev);
return err;
@@ -3119,7 +3119,7 @@ state_store(struct md_rdev *rdev, const char *buf, size_t len,
} else if (cmd_match(buf, "remove")) {
if (rdev->mddev->pers) {
clear_bit(Blocked, &rdev->flags);
- remove_and_add_spares(rdev->mddev, rdev, NULL);
+ remove_and_add_spares(rdev->mddev, rdev, lim);
}
if (rdev->raid_disk >= 0)
err = -EBUSY;
@@ -3238,7 +3238,7 @@ state_store(struct md_rdev *rdev, const char *buf, size_t len,
if (!mddev_is_clustered(rdev->mddev) ||
(err = mddev->cluster_ops->gather_bitmaps(rdev)) == 0) {
clear_bit(Faulty, &rdev->flags);
- err = add_bound_rdev(rdev);
+ err = add_bound_rdev(rdev, lim);
}
} else
err = -EBUSY;
@@ -3325,7 +3325,7 @@ slot_store(struct md_rdev *rdev, const char *buf, size_t len,
if (rdev->mddev->pers->hot_remove_disk == NULL)
return -EINVAL;
clear_bit(Blocked, &rdev->flags);
- remove_and_add_spares(rdev->mddev, rdev, NULL);
+ remove_and_add_spares(rdev->mddev, rdev, lim);
if (rdev->raid_disk >= 0)
return -EBUSY;
set_bit(MD_RECOVERY_NEEDED, &rdev->mddev->recovery);
@@ -3356,7 +3356,7 @@ slot_store(struct md_rdev *rdev, const char *buf, size_t len,
clear_bit(In_sync, &rdev->flags);
clear_bit(Bitmap_sync, &rdev->flags);
err = rdev->mddev->pers->hot_add_disk(rdev->mddev, rdev,
- NULL);
+ lim);
if (err) {
rdev->raid_disk = -1;
return err;
@@ -3762,6 +3762,9 @@ rdev_attr_store(struct kobject *kobj, struct attribute *attr,
struct rdev_sysfs_entry *entry = container_of(attr, struct rdev_sysfs_entry, attr);
struct md_rdev *rdev = container_of(kobj, struct md_rdev, kobj);
struct kernfs_node *kn = NULL;
+ struct request_queue *q = NULL;
+ struct queue_limits lim;
+ struct queue_limits *limp = NULL;
bool suspend = false;
ssize_t rv;
struct mddev *mddev = READ_ONCE(rdev->mddev);
@@ -3782,15 +3785,41 @@ rdev_attr_store(struct kobject *kobj, struct attribute *attr,
suspend = true;
}
+ /*
+ * These can add a leg back, which stacks its limits; the other
+ * state_store() values never reach ->hot_add_disk(). q->limits_lock
+ * nests outside the lock and the suspend, see md_start_sync().
+ */
+ if ((entry->store == slot_store ||
+ (entry->store == state_store &&
+ (cmd_match(page, "remove") || cmd_match(page, "re-add")))) &&
+ !mddev_is_dm(mddev)) {
+ q = mddev->gendisk->queue;
+ lim = queue_limits_start_update(q);
+ limp = &lim;
+ }
+
rv = suspend ? mddev_suspend_and_lock(mddev) : mddev_lock(mddev);
if (!rv) {
if (rdev->mddev == NULL)
rv = -ENODEV;
else
- rv = entry->store(rdev, page, length, NULL);
+ rv = entry->store(rdev, page, length, limp);
+ /* apply the limits before the array takes I/O again */
+ if (limp) {
+ int err = queue_limits_commit_update(q, limp);
+
+ limp = NULL;
+ if (err && rv >= 0)
+ rv = err;
+ }
suspend ? mddev_unlock_and_resume(mddev) : mddev_unlock(mddev);
}
+ /* only reached when the lock failed, so nothing was stacked */
+ if (limp)
+ queue_limits_cancel_update(q);
+
if (kn)
sysfs_unbreak_active_protection(kn);
@@ -7575,7 +7604,8 @@ static int get_disk_info(struct mddev *mddev, void __user * arg)
return 0;
}
-int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info)
+int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info,
+ struct queue_limits *lim)
{
struct md_rdev *rdev;
dev_t dev = MKDEV(info->major,info->minor);
@@ -7723,11 +7753,11 @@ int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info)
if (err)
mddev->cluster_ops->add_new_disk_cancel(mddev);
else
- err = add_bound_rdev(rdev);
+ err = add_bound_rdev(rdev, lim);
}
} else if (!err)
- err = add_bound_rdev(rdev);
+ err = add_bound_rdev(rdev, lim);
return err;
}
@@ -7780,7 +7810,8 @@ int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info)
return 0;
}
-static int hot_remove_disk(struct mddev *mddev, dev_t dev)
+static int hot_remove_disk(struct mddev *mddev, dev_t dev,
+ struct queue_limits *lim)
{
struct md_rdev *rdev;
@@ -7795,7 +7826,7 @@ static int hot_remove_disk(struct mddev *mddev, dev_t dev)
goto kick_rdev;
clear_bit(Blocked, &rdev->flags);
- remove_and_add_spares(mddev, rdev, NULL);
+ remove_and_add_spares(mddev, rdev, lim);
if (rdev->raid_disk >= 0)
goto busy;
@@ -8380,6 +8411,22 @@ static inline int md_ioctl_valid(unsigned int cmd)
}
}
+/*
+ * Commands that can reach ->hot_add_disk(). ADD_NEW_DISK only does so for a
+ * journal device or a personality without ->hot_remove_disk, but that depends
+ * on disk info still in user memory here, so it is included as a whole.
+ */
+static bool md_ioctl_may_add_disk(unsigned int cmd)
+{
+ switch (cmd) {
+ case ADD_NEW_DISK:
+ case HOT_REMOVE_DISK:
+ return true;
+ default:
+ return false;
+ }
+}
+
static bool md_ioctl_need_suspend(unsigned int cmd)
{
switch (cmd) {
@@ -8435,6 +8482,9 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
unsigned int noio_flags = 0;
void __user *argp = (void __user *)arg;
struct mddev *mddev = NULL;
+ struct request_queue *q = NULL;
+ struct queue_limits lim;
+ struct queue_limits *limp = NULL;
bool suspend;
err = md_ioctl_valid(cmd);
@@ -8485,11 +8535,20 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
if (!md_is_rdwr(mddev))
flush_work(&mddev->sync_work);
+ /* q->limits_lock nests outside both, see md_start_sync() */
+ if (md_ioctl_may_add_disk(cmd) && !mddev_is_dm(mddev)) {
+ q = mddev->gendisk->queue;
+ lim = queue_limits_start_update(q);
+ limp = &lim;
+ }
+
suspend = md_ioctl_need_suspend(cmd);
err = suspend ? mddev_suspend_and_lock(mddev) : mddev_lock(mddev);
if (err) {
pr_debug("md: ioctl lock interrupted, reason %d, cmd %d\n",
err, cmd);
+ if (limp)
+ queue_limits_cancel_update(q);
goto out;
}
if (suspend)
@@ -8531,7 +8590,7 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
goto unlock;
case HOT_REMOVE_DISK:
- err = hot_remove_disk(mddev, new_decode_dev(arg));
+ err = hot_remove_disk(mddev, new_decode_dev(arg), limp);
goto unlock;
case ADD_NEW_DISK:
@@ -8547,7 +8606,7 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
/* Need to clear read-only for this */
break;
else
- err = md_add_new_disk(mddev, &info);
+ err = md_add_new_disk(mddev, &info, limp);
goto unlock;
}
break;
@@ -8585,7 +8644,7 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
if (copy_from_user(&info, argp, sizeof(info)))
err = -EFAULT;
else
- err = md_add_new_disk(mddev, &info);
+ err = md_add_new_disk(mddev, &info, limp);
goto unlock;
}
@@ -8618,6 +8677,9 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
err != -EINVAL)
mddev->hold_active = 0;
+ if (limp)
+ err = queue_limits_commit_update(q, limp) ?: err;
+
if (suspend) {
memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
@@ -10346,6 +10408,9 @@ static bool md_choose_sync_action(struct mddev *mddev, int *spares,
static void md_start_sync(struct work_struct *ws)
{
struct mddev *mddev = container_of(ws, struct mddev, sync_work);
+ struct request_queue *q = NULL;
+ struct queue_limits lim;
+ struct queue_limits *limp = NULL;
int spares = 0;
bool suspend = false;
unsigned int noio_flags = 0;
@@ -10357,6 +10422,17 @@ static void md_start_sync(struct work_struct *ws)
*/
if ((mddev->reshape_position == MaxSector || !md_is_rdwr(mddev)) &&
md_spares_need_change(mddev)) {
+ /*
+ * Adding a spare below stacks its limits, which needs
+ * q->limits_lock. Take it before suspending: its holder
+ * waits in blk_mq_freeze_queue() for I/O that
+ * mddev->suspended holds back, so the other order deadlocks.
+ */
+ if (!mddev_is_dm(mddev)) {
+ q = mddev->gendisk->queue;
+ lim = queue_limits_start_update(q);
+ limp = &lim;
+ }
suspend = true;
mddev_suspend(mddev, false);
noio_flags = memalloc_noio_save();
@@ -10371,6 +10447,12 @@ static void md_start_sync(struct work_struct *ws)
if (!suspend && (mddev->reshape_position == MaxSector || !md_is_rdwr(mddev)) &&
md_spares_need_change(mddev)) {
mddev_unlock(mddev);
+ /* see above: q->limits_lock nests outside both */
+ if (!mddev_is_dm(mddev)) {
+ q = mddev->gendisk->queue;
+ lim = queue_limits_start_update(q);
+ limp = &lim;
+ }
mddev_suspend_and_lock_nointr(mddev);
suspend = true;
noio_flags = memalloc_noio_save();
@@ -10384,11 +10466,11 @@ static void md_start_sync(struct work_struct *ws)
* As we only add devices that are already in-sync, we can
* activate the spares immediately.
*/
- remove_and_add_spares(mddev, NULL, NULL);
+ remove_and_add_spares(mddev, NULL, limp);
goto not_running;
}
- if (!md_choose_sync_action(mddev, &spares, NULL))
+ if (!md_choose_sync_action(mddev, &spares, limp))
goto not_running;
if (!mddev->pers->sync_request)
@@ -10419,6 +10501,8 @@ static void md_start_sync(struct work_struct *ws)
* https://bugzilla.kernel.org/show_bug.cgi?id=218200
* Therefore, use __mddev_resume(mddev, false).
*/
+ if (limp && queue_limits_commit_update(q, limp))
+ pr_err("%s: could not apply queue limits\n", mdname(mddev));
if (suspend) {
memalloc_noio_restore(noio_flags);
__mddev_resume(mddev, false);
@@ -10441,6 +10525,8 @@ static void md_start_sync(struct work_struct *ws)
* https://bugzilla.kernel.org/show_bug.cgi?id=218200
* Therefore, use __mddev_resume(mddev, false).
*/
+ if (limp && queue_limits_commit_update(q, limp))
+ pr_err("%s: could not apply queue limits\n", mdname(mddev));
if (suspend) {
memalloc_noio_restore(noio_flags);
__mddev_resume(mddev, false);
diff --git a/drivers/md/md.h b/drivers/md/md.h
index 1b2e8720f0d1..7f4e3ea8b826 100644
--- a/drivers/md/md.h
+++ b/drivers/md/md.h
@@ -1046,7 +1046,8 @@ struct mdu_disk_info_s;
extern int mdp_major;
void md_autostart_arrays(int part);
int md_set_array_info(struct mddev *mddev, struct mdu_array_info_s *info);
-int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info);
+int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info,
+ struct queue_limits *lim);
int do_md_run(struct mddev *mddev);
#define MDDEV_STACK_INTEGRITY (1u << 0)
int mddev_stack_rdev_limits(struct mddev *mddev, struct queue_limits *lim,
diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 3faa2a94c03b..22759c631c4d 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -7288,6 +7288,9 @@ static ssize_t
raid5_store_skip_copy(struct mddev *mddev, const char *page, size_t len)
{
struct r5conf *conf;
+ struct request_queue *q = NULL;
+ struct queue_limits lim;
+ struct queue_limits *limp = NULL;
unsigned long new;
int err;
@@ -7297,23 +7300,33 @@ raid5_store_skip_copy(struct mddev *mddev, const char *page, size_t len)
return -EINVAL;
new = !!new;
+ /* q->limits_lock nests outside both, see md_start_sync() */
+ if (!mddev_is_dm(mddev)) {
+ q = mddev->gendisk->queue;
+ lim = queue_limits_start_update(q);
+ limp = &lim;
+ }
+
err = mddev_suspend_and_lock(mddev);
- if (err)
+ if (err) {
+ if (limp)
+ queue_limits_cancel_update(q);
return err;
+ }
conf = mddev->private;
if (!conf)
err = -ENODEV;
else if (new != conf->skip_copy) {
- struct request_queue *q = mddev->gendisk->queue;
- struct queue_limits lim = queue_limits_start_update(q);
-
conf->skip_copy = new;
- if (new)
- lim.features |= BLK_FEAT_STABLE_WRITES;
- else
- lim.features &= ~BLK_FEAT_STABLE_WRITES;
- err = queue_limits_commit_update(q, &lim);
+ if (limp) {
+ if (new)
+ limp->features |= BLK_FEAT_STABLE_WRITES;
+ else
+ limp->features &= ~BLK_FEAT_STABLE_WRITES;
+ }
}
+ if (limp)
+ err = queue_limits_commit_update(q, limp) ?: err;
mddev_unlock_and_resume(mddev);
return err ?: len;
}
--
2.43.0
next prev parent reply other threads:[~2026-09-10 8:11 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-10 8:11 [PATCH v2 0/8] md: don't wait for q->limits_lock while md holds back I/O Jack Wang
2026-09-10 8:11 ` [PATCH v2 1/8] md: pass a queue_limits down to ->hot_add_disk() Jack Wang
2026-09-10 8:31 ` sashiko-bot
2026-09-11 10:46 ` Nilay Shroff
2026-09-10 8:11 ` [PATCH v2 2/8] md: don't wait for q->limits_lock in check_sb_changes() Jack Wang
2026-09-10 8:36 ` sashiko-bot
2026-09-10 8:11 ` [PATCH v2 3/8] md: pass a queue_limits through the rdev sysfs stores Jack Wang
2026-09-10 8:11 ` [PATCH v2 4/8] md: defer the io_opt update out of the sync thread Jack Wang
2026-09-10 8:37 ` sashiko-bot
2026-09-10 8:11 ` Jack Wang [this message]
2026-09-10 8:51 ` [PATCH v2 5/8] md: take q->limits_lock before locking and suspending the array sashiko-bot
2026-09-10 8:11 ` [PATCH v2 6/8] md: pass a queue_limits through ->run() Jack Wang
2026-09-10 8:49 ` sashiko-bot
2026-09-11 10:54 ` Nilay Shroff
2026-09-10 8:11 ` [PATCH v2 7/8] md: open new legs before locking the array Jack Wang
2026-09-10 8:48 ` sashiko-bot
2026-09-10 8:11 ` [PATCH v2 8/8] md: link a new leg's holder " Jack Wang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260910081114.1605746-6-jinpu.wang@ionos.com \
--to=jinpu.wang@ionos.com \
--cc=abd.masalkhi@gmail.com \
--cc=axboe@kernel.dk \
--cc=dlemoal@kernel.org \
--cc=dm-devel@lists.linux.dev \
--cc=hch@lst.de \
--cc=jinpu.wang@cloud.ionos.com \
--cc=linux-block@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-raid@vger.kernel.org \
--cc=magiclinan@didiglobal.com \
--cc=mpatocka@redhat.com \
--cc=nilay@linux.ibm.com \
--cc=snitzer@kernel.org \
--cc=song@kernel.org \
--cc=tom.leiming@gmail.com \
--cc=xiao@kernel.org \
--cc=yukuai@fygo.io \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.