Thread (24 messages) 24 messages, 7 authors, 2d ago

Re: [PATCH 6/6] md: take q->limits_lock before locking and suspending the array

From: Jinpu Wang <jinpu.wang@cloud.ionos.com>
Date: 2026-09-08 04:45:08
Also in: sashiko-reviews

On Mon, Sep 7, 2026 at 4:00 PM [off-list ref] wrote:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [Medium] queue_limits_commit_update() return value is ignored and called unconditionally on error paths instead of queue_limits_cancel_update()
Will fix in newer version
Pre-existing issues:
- [High] ABBA deadlock between q->limits_lock and mddev_suspend in level_store()
Yes, there is a plan: thread a queue_limits through ->run() in all
five personalities so level_store() owns the update, the way
md_ioctl() now does. I left it out because it's ~40 lines across
raid0/1/5/10/linear and orthogonal to the deadlock being fixed here.
I'll post it as a follow-up. Worth noting the hang you describe is
reachable today without this series, since queue_limits_set() freezes
the queue while level_store() has the array suspended — the freeze
then waits for I/O that mddev->suspended is holding back, no second
task needed.
--

commit ecb6e260e45c52011fdf103645ad33814409ccd9
Author: Jack Wang [off-list ref]

md: take q->limits_lock before locking and suspending the array

This commit updates the block layer limits locking order for MD devices to
prevent a deadlock when writing queue limits attributes during disk operations.
It takes the limits update before locking and suspending the array, passing it
down into personality stacks.
quoted
Two callers still take the lock inside reconfig_mutex, both 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() -> queue_limits_set() from level_store(), which already
hangs on its own because it freezes the queue while suspended.
[Severity: High]
This is a pre-existing issue, but is there a plan to address the ABBA
deadlock in level_store()? When the array is suspended and locked, normal
I/O parks on the queue usage counter. Concurrently, a udev sysfs write can
take q->limits_lock and wait in blk_mq_freeze_queue() for the counter to
drain, while level_store() holds the suspend lock and waits for
q->limits_lock via pers->run() -> queue_limits_set(), causing a system
hang.
quoted
diff --git a/drivers/md/md.c b/drivers/md/md.c
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -3780,15 +3783,37 @@ rdev_attr_store(struct kobject *kobj, struct attribute *attr,
[ ... ]
quoted
      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) {
+                     queue_limits_commit_update(q, limp);
+                     limp = NULL;
+             }
[Severity: Medium]
Are we missing a check for the return value of queue_limits_commit_update()
here? If the block layer rejects the new limits because blk_validate_limits()
fails, the return value is ignored. This could allow the array to proceed
using the new disk with old limits, risking I/O errors if requests exceed
the disk's hardware limits.
quoted
              suspend ? mddev_unlock_and_resume(mddev) : mddev_unlock(mddev);
      }

+     if (limp)
+             queue_limits_commit_update(q, limp);
+
[Severity: Medium]
Does this code incorrectly call queue_limits_commit_update() unconditionally
on error paths? If mddev_suspend_and_lock() or mddev_lock() failed, rv will
be non-zero but limp will still be set. In this situation, the code will
commit unchanged limits instead of cancelling them via
queue_limits_cancel_update().

[ ... ]
quoted
@@ -8618,6 +8673,9 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
          err != -EINVAL)
              mddev->hold_active = 0;

+     if (limp)
+             queue_limits_commit_update(q, limp);
+
[Severity: Medium]
Similar to the rdev_attr_store() issue above, should this use
queue_limits_cancel_update() on error paths? If an inner ioctl command
fails, this will erroneously commit the unchanged limits rather than
cancelling the update.

[ ... ]
quoted
@@ -10419,6 +10497,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);
[Severity: Medium]
Does this safely handle limit validation failures when adding spares? The
return value is ignored, which could leave the array with outdated limits if
the limits fail to stack correctly.

--
Sashiko AI review · https://sashiko.dev/#/patchset/20260907133929.1081540-1-jinpu.wang@ionos.com?part=6
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help