[PATCH v3] md/raid5: use dedicated llist for stripe plug
From: Li Youhong
Date: Tue Oct 20 2026 - 21:40:51 EST
From: Li Youhong <liyouhong@xxxxxxxxxx>
release_stripe_plug() and do_release_stripe() share sh->lru.
release_stripe_plug() sets STRIPE_ON_UNPLUG_LIST and
list_add_tail()s sh->lru onto raid5_plug_cb.list without
device_lock. do_release_stripe() holds device_lock and, when the
last reference drops, list_add()s the same lru onto a handle or
inactive list.
Two list_add()s on one node corrupt it. sh->lru can be
reinitialized into a self-loop while raid5_plug_cb.list still
points at that stripe. raid5_unplug() then walks the list under
device_lock with IRQs disabled and never finishes. Other CPUs
waiting for the same lock hard-lockup.
Add a dedicated llist_node, unplug_list, to stripe_head, as
release_list is used for released_stripes.
Fixes: 8811b5968f62 ("raid5: make_request use batch stripe release")
Suggested-by: Yu Kuai <yukuai@xxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Li Youhong <liyouhong@xxxxxxxxxx>
---
v3:
- Skip device_lock when llist_del_all() returns NULL. The plug
callback can still run with an empty list, and the check avoids
an unnecessary spin_lock.
- v2: link: https://lore.kernel.org/linux-raid/20260922060928.3486799-1-dayou5941@xxxxxxx/
v2:
- Drop taking device_lock in release_stripe_plug(). Add a dedicated
unplug_list.
- v1: link: https://lore.kernel.org/linux-raid/20260902095307.358569-1-dayou5941@xxxxxxx/
---
drivers/md/raid5.c | 20 +++++++++++---------
drivers/md/raid5.h | 1 +
2 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index b91545ce090d..dbc6e6876509 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -5710,7 +5710,7 @@ static struct stripe_head *__get_priority_stripe(struct r5conf *conf, int group)
struct raid5_plug_cb {
struct blk_plug_cb cb;
- struct list_head list;
+ struct llist_head unplug_list;
struct list_head temp_inactive_list[NR_STRIPE_HASH_LOCKS];
};
@@ -5718,17 +5718,18 @@ static void raid5_unplug(struct blk_plug_cb *blk_cb, bool from_schedule)
{
struct raid5_plug_cb *cb = container_of(
blk_cb, struct raid5_plug_cb, cb);
- struct stripe_head *sh;
+ struct stripe_head *sh, *tmp;
struct mddev *mddev = cb->cb.data;
struct r5conf *conf = mddev->private;
+ struct llist_node *head;
int cnt = 0;
int hash;
- if (cb->list.next && !list_empty(&cb->list)) {
+ head = llist_del_all(&cb->unplug_list);
+ if (head) {
+ head = llist_reverse_order(head);
spin_lock_irq(&conf->device_lock);
- while (!list_empty(&cb->list)) {
- sh = list_first_entry(&cb->list, struct stripe_head, lru);
- list_del_init(&sh->lru);
+ llist_for_each_entry_safe(sh, tmp, head, unplug_list) {
/*
* avoid race release_stripe_plug() sees
* STRIPE_ON_UNPLUG_LIST clear but the stripe
@@ -5768,15 +5769,16 @@ static void release_stripe_plug(struct mddev *mddev,
cb = container_of(blk_cb, struct raid5_plug_cb, cb);
- if (cb->list.next == NULL) {
+ if (!cb->temp_inactive_list[0].next) {
int i;
- INIT_LIST_HEAD(&cb->list);
+
+ init_llist_head(&cb->unplug_list);
for (i = 0; i < NR_STRIPE_HASH_LOCKS; i++)
INIT_LIST_HEAD(cb->temp_inactive_list + i);
}
if (!test_and_set_bit(STRIPE_ON_UNPLUG_LIST, &sh->state))
- list_add_tail(&sh->lru, &cb->list);
+ llist_add(&sh->unplug_list, &cb->unplug_list);
else
raid5_release_stripe(sh);
}
diff --git a/drivers/md/raid5.h b/drivers/md/raid5.h
index cb5feae04db2..e314f17eb949 100644
--- a/drivers/md/raid5.h
+++ b/drivers/md/raid5.h
@@ -201,6 +201,7 @@ struct stripe_head {
struct hlist_node hash;
struct list_head lru; /* inactive_list or handle_list */
struct llist_node release_list;
+ struct llist_node unplug_list;
struct r5conf *raid_conf;
short generation; /* increments with every
* reshape */
--
2.25.1