[PATCH v2 3/7] md/raid1: serialize non-write-behind writes on CollisionCheck rdevs

From: Mykola Marzhan

Date: Sun Jul 19 2026 - 06:57:47 EST


A write-behind write is acknowledged to the caller while its copy to
the write-mostly member is still in flight. wait_for_serialization()
exists to order later overlapping writes behind it, but the non-behind
clone path only takes it under serialize_policy. A write to a
write-behind array skips the behind path whenever the behind queue is
full, a reader is waiting on behind completion, or the behind bio
allocation fails (raid1_start_write_behind()) -- and then races the
in-flight behind write on the write-mostly member without any
ordering: if the older behind data lands last, the member keeps stale
data for sectors whose newer write the caller has already seen
acknowledged.

Gate the non-behind serialization on CollisionCheck alone. The flag
marks exactly the rdevs that own a serial tree: every rdev present
when serialize_policy is enabled, and write-mostly members when
write-behind arms serialization, which is the case the current test
misses. Take the matching remove_serial() at completion under the
same condition. Discards take the same non-behind path and are now
ordered as well. For an rdev hot-added while serialize_policy is
already enabled, mddev_create_serial_pool() never builds a serial
tree, so the old MD_SERIALIZE_POLICY gate sent it into
wait_for_serialization() with rdev->serial == NULL -- a latent oops
that the per-rdev gate avoids by construction. REQ_NOWAIT writes
can wait here when they overlap an in-flight behind write, as they
already do on the behind path and under serialize_policy.

Fixes: d0d2d8ba0494 ("md/raid1: introduce wait_for_serialization")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Claude:claude-fable-5
Signed-off-by: Mykola Marzhan <mykola@xxxxxxxxxxx>
---
drivers/md/raid1.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
index afe2ca96ad8c..8172df882f26 100644
--- a/drivers/md/raid1.c
+++ b/drivers/md/raid1.c
@@ -564,7 +564,7 @@ static void raid1_end_write_request(struct bio *bio)
call_bio_endio(r1_bio);
}
}
- } else if (test_bit(MD_SERIALIZE_POLICY, &rdev->mddev->flags))
+ } else if (test_bit(CollisionCheck, &rdev->flags))
remove_serial(rdev, lo, hi);
if (r1_bio->bios[mirror] == NULL)
rdev_dec_pending(rdev, conf->mddev);
@@ -1677,7 +1677,14 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio,
mbio = bio_alloc_clone(rdev->bdev, bio, GFP_NOIO,
&mddev->bio_set);

- if (test_bit(MD_SERIALIZE_POLICY, &mddev->flags))
+ /*
+ * Order against in-flight write-behind I/O: a
+ * behind write is acked early, and an unordered
+ * overwrite could land first, leaving its stale
+ * data on the member last. CollisionCheck marks
+ * every rdev that owns a serial tree.
+ */
+ if (test_bit(CollisionCheck, &rdev->flags))
wait_for_serialization(rdev, r1_bio);
}

--
2.43.0