* [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped
@ 2026-07-17 6:27 Yunye Zhao
2026-07-17 6:51 ` sashiko-bot
` (2 more replies)
0 siblings, 3 replies; 11+ messages in thread
From: Yunye Zhao @ 2026-07-17 6:27 UTC (permalink / raw)
To: Song Liu, Yu Kuai
Cc: Li Nan, Xiao Ni, Joseph Qi, linux-raid, linux-kernel, Yunye Zhao
md_do_sync()'s main loop advances io_sectors only when I/O is actually
issued (skipped == 0). When sync_request() keeps returning skipped == 1,
io_sectors never increases, the "last_check + window > io_sectors" test
stays true, and every iteration takes the continue branch:
sectors = mddev->pers->sync_request(mddev, j, max_sectors, &skipped);
...
if (!skipped)
io_sectors += sectors;
j += sectors;
...
if (last_check + window > io_sectors || j == max_sectors)
continue;
During recovery or resync of a large array with a sparse bitmap, many
regions that need no syncing are skipped:
raid10_sync_request()
md_bitmap_start_sync() -> must_sync = false (no bitmap page)
/* every mirror skipped */
biolist == NULL -> *skipped = 1; return max_sync;
j then traverses the whole skipped range while io_sectors stays
unchanged. On a non-preemptive kernel the resync thread (mdX_resync)
hogs the CPU for a long time and eventually triggers a soft lockup:
watchdog: BUG: soft lockup - CPU#149 stuck for 313s! [mdX_resync]
md_bitmap_start_sync+0x6f/0xe0
raid10_sync_request+0x2c9/0x1530 [raid10]
md_do_sync+0x810/0x1030
md_thread+0xa7/0x150
Add cond_resched() to this continue path.
Signed-off-by: Yunye Zhao <yunye.zhao@linux.alibaba.com>
---
drivers/md/md.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/md/md.c b/drivers/md/md.c
index d1465bcd86c8..e7411b033490 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -9881,9 +9881,10 @@ void md_do_sync(struct md_thread *thread)
*/
md_new_event();
- if (last_check + window > io_sectors || j == max_sectors)
+ if (last_check + window > io_sectors || j == max_sectors) {
+ cond_resched();
continue;
-
+ }
last_check = io_sectors;
repeat:
if (time_after_eq(jiffies, mark[last_mark] + SYNC_MARK_STEP )) {
--
2.19.1.6.gb485710b
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped
2026-07-17 6:27 [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped Yunye Zhao
@ 2026-07-17 6:51 ` sashiko-bot
2026-07-19 10:45 ` yu kuai
2026-07-23 13:55 ` [PATCH v2 0/3] md/raid10: fix recovery corruption and soft lockup on large arrays Yunye Zhao
2 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-07-17 6:51 UTC (permalink / raw)
To: Yunye Zhao; +Cc: yukuai, linux-raid
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Repeatedly skipped I/O blocks cause an event storm, calling md_new_event() millions of times in a tight loop.
--
--- Patch [1]: [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped ---
commit 02c063c34357030eaa8514f57d5e37328d410c0b
Author: Yunye Zhao <yunye.zhao@linux.alibaba.com>
md: fix soft lockup during resync when sync is repeatedly skipped
The md_do_sync() main loop advances io_sectors only when I/O is actually
issued. When regions are skipped, io_sectors never increases and the loop
takes the continue branch, eventually triggering a soft lockup on
non-preemptive kernels. This patch adds cond_resched() to this continue
path to prevent the soft lockup.
> diff --git a/drivers/md/md.c b/drivers/md/md.c
> index d1465bcd86c81..e7411b0334901 100644
> --- a/drivers/md/md.c
> +++ b/drivers/md/md.c
> @@ -9881,9 +9881,10 @@ void md_do_sync(struct md_thread *thread)
> */
> md_new_event();
>
> - if (last_check + window > io_sectors || j == max_sectors)
> + if (last_check + window > io_sectors || j == max_sectors) {
> + cond_resched();
> continue;
[Severity: High]
This is a pre-existing issue, but can this loop in md_do_sync() cause an event
storm when I/O is repeatedly skipped?
During array recovery or resync with a sparse bitmap, io_sectors remains 0
when regions are skipped. Because last_check starts at 0, the check
(last_check + window > io_sectors) evaluates to true, causing the loop to
continue.
Since last_check is only updated after this continue statement, it remains 0
permanently across skipped regions. As a result, the preceding check for
(last_check == 0) remains true, calling md_new_event() on every skipped
iteration. This repeatedly wakes up user-space processes polling /proc/mdstat
and causes lock contention.
> -
> + }
> last_check = io_sectors;
> repeat:
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260717062743.128189-1-yunye.zhao@linux.alibaba.com?part=1
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped
2026-07-17 6:27 [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped Yunye Zhao
2026-07-17 6:51 ` sashiko-bot
@ 2026-07-19 10:45 ` yu kuai
2026-07-20 6:20 ` Yunye Zhao
2026-07-23 13:55 ` [PATCH v2 0/3] md/raid10: fix recovery corruption and soft lockup on large arrays Yunye Zhao
2 siblings, 1 reply; 11+ messages in thread
From: yu kuai @ 2026-07-19 10:45 UTC (permalink / raw)
To: Yunye Zhao, Song Liu, Yu Kuai
Cc: Li Nan, Xiao Ni, Joseph Qi, linux-raid, linux-kernel
Hi,
在 2026/7/17 14:27, Yunye Zhao 写道:
> md_do_sync()'s main loop advances io_sectors only when I/O is actually
> issued (skipped == 0). When sync_request() keeps returning skipped == 1,
> io_sectors never increases, the "last_check + window > io_sectors" test
> stays true, and every iteration takes the continue branch:
That's not expected, io_sectors should always increase in the skip case.
>
> sectors = mddev->pers->sync_request(mddev, j, max_sectors, &skipped);
> ...
> if (!skipped)
> io_sectors += sectors;
> j += sectors;
> ...
> if (last_check + window > io_sectors || j == max_sectors)
> continue;
>
> During recovery or resync of a large array with a sparse bitmap, many
> regions that need no syncing are skipped:
>
> raid10_sync_request()
> md_bitmap_start_sync() -> must_sync = false (no bitmap page)
> /* every mirror skipped */
> biolist == NULL -> *skipped = 1; return max_sync;
>
> j then traverses the whole skipped range while io_sectors stays
> unchanged. On a non-preemptive kernel the resync thread (mdX_resync)
> hogs the CPU for a long time and eventually triggers a soft lockup:
>
> watchdog: BUG: soft lockup - CPU#149 stuck for 313s! [mdX_resync]
> md_bitmap_start_sync+0x6f/0xe0
> raid10_sync_request+0x2c9/0x1530 [raid10]
> md_do_sync+0x810/0x1030
> md_thread+0xa7/0x150
What kernel version you're testing? If this is latest kernel, bitmap_start_sync()
need to be fixed. It can't return skip while setting skipping sectors to 0. And
since this is dead loop, a cond_resched() will not fix anything.
>
> Add cond_resched() to this continue path.
>
> Signed-off-by: Yunye Zhao <yunye.zhao@linux.alibaba.com>
> ---
> drivers/md/md.c | 5 +++--
> 1 file changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/md/md.c b/drivers/md/md.c
> index d1465bcd86c8..e7411b033490 100644
> --- a/drivers/md/md.c
> +++ b/drivers/md/md.c
> @@ -9881,9 +9881,10 @@ void md_do_sync(struct md_thread *thread)
> */
> md_new_event();
>
> - if (last_check + window > io_sectors || j == max_sectors)
> + if (last_check + window > io_sectors || j == max_sectors) {
> + cond_resched();
> continue;
> -
> + }
> last_check = io_sectors;
> repeat:
> if (time_after_eq(jiffies, mark[last_mark] + SYNC_MARK_STEP )) {
--
Thanks,
Kuai
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped
2026-07-19 10:45 ` yu kuai
@ 2026-07-20 6:20 ` Yunye Zhao
2026-07-22 4:00 ` yu kuai
0 siblings, 1 reply; 11+ messages in thread
From: Yunye Zhao @ 2026-07-20 6:20 UTC (permalink / raw)
To: Yu Kuai, Song Liu
Cc: Yunye Zhao, Li Nan, Xiao Ni, Joseph Qi, linux-raid, linux-kernel
Hi Kuai,
On 2026/7/17 14:27, Yu Kuai wrote:
>> md_do_sync()'s main loop advances io_sectors only when I/O is actually
>> issued (skipped == 0). When sync_request() keeps returning skipped == 1,
>> io_sectors never increases, [...]
>
> That's not expected, io_sectors should always increase in the skip case.
Sorry, my description was not accurate. The problem is not that io_sectors
stays 0. md_do_sync()'s loop exit condition is j == max_sectors, and in
raid10_sync_request()'s recovery path sectors is always 128, so for a very
large max_sectors the loop iterates a huge number of times.
raid10_sync_request(), recovery branch:
max_sync = RESYNC_PAGES << (PAGE_SHIFT-9); /* 128 */
must_sync = md_bitmap_start_sync(mddev, sect, &sync_blocks, true);
if (sync_blocks < max_sync) /* sync_blocks huge, never true */
max_sync = sync_blocks; /* so max_sync stays 128 */
...
if (biolist == NULL) {
*skipped = 1;
return max_sync; /* 128; the large span is discarded */
}
Back in md_do_sync()'s main loop, j then crawls forward 128 sectors per
call until it reaches max_sectors:
while (j < max_sectors) {
sectors = mddev->pers->sync_request(mddev, j, max_sectors, &skipped);
if (!skipped) /* skipped == 1 -> io_sectors stays 0 */
io_sectors += sectors;
j += sectors; /* j += 128 only */
if (last_check + window > io_sectors || j == max_sectors)
continue;
}
From the vmcore:
sync_blocks returned = 0x5ED03680 (~1.59e8 sectors), clamped to 128
recovery max_sectors = dev_sectors = 2^40
iterations = 2^40 / 128 = 2^33 (~8.6e9)
> What kernel version you're testing?
6.6.102. I also tested mainline and hit the same problem.
> If this is latest kernel, bitmap_start_sync() need to be fixed. It can't
> return skip while setting skipping sectors to 0.
bitmap_start_sync() is actually fine -- it returns a large clean span
(0x5ED03680 above). The recovery path just discards it: max_sync is only
ever clamped down, so it returns 128 regardless.
> And since this is dead loop, a cond_resched() will not fix anything.
Agreed -- cond_resched() only stops the watchdog; the thread still spins
~8.6e9 no-op iterations and pins a CPU for ~313s.
For v2 I'd fix this at the source: make the recovery path honour the clean
span reported by the bitmap instead of capping the skip at 128. Does that
direction look right to you?
Thanks,
Yunye
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped
2026-07-20 6:20 ` Yunye Zhao
@ 2026-07-22 4:00 ` yu kuai
0 siblings, 0 replies; 11+ messages in thread
From: yu kuai @ 2026-07-22 4:00 UTC (permalink / raw)
To: Yunye Zhao, Song Liu, yu kuai
Cc: Li Nan, Xiao Ni, Joseph Qi, linux-raid, linux-kernel
Hi,
在 2026/7/20 14:20, Yunye Zhao 写道:
> Hi Kuai,
>
> On 2026/7/17 14:27, Yu Kuai wrote:
>>> md_do_sync()'s main loop advances io_sectors only when I/O is actually
>>> issued (skipped == 0). When sync_request() keeps returning skipped == 1,
>>> io_sectors never increases, [...]
>> That's not expected, io_sectors should always increase in the skip case.
> Sorry, my description was not accurate. The problem is not that io_sectors
> stays 0. md_do_sync()'s loop exit condition is j == max_sectors, and in
> raid10_sync_request()'s recovery path sectors is always 128, so for a very
> large max_sectors the loop iterates a huge number of times.
>
> raid10_sync_request(), recovery branch:
>
> max_sync = RESYNC_PAGES << (PAGE_SHIFT-9); /* 128 */
> must_sync = md_bitmap_start_sync(mddev, sect, &sync_blocks, true);
> if (sync_blocks < max_sync) /* sync_blocks huge, never true */
> max_sync = sync_blocks; /* so max_sync stays 128 */
> ...
> if (biolist == NULL) {
> *skipped = 1;
> return max_sync; /* 128; the large span is discarded */
> }
>
> Back in md_do_sync()'s main loop, j then crawls forward 128 sectors per
> call until it reaches max_sectors:
>
> while (j < max_sectors) {
> sectors = mddev->pers->sync_request(mddev, j, max_sectors, &skipped);
> if (!skipped) /* skipped == 1 -> io_sectors stays 0 */
> io_sectors += sectors;
> j += sectors; /* j += 128 only */
> if (last_check + window > io_sectors || j == max_sectors)
> continue;
> }
>
> From the vmcore:
>
> sync_blocks returned = 0x5ED03680 (~1.59e8 sectors), clamped to 128
> recovery max_sectors = dev_sectors = 2^40
> iterations = 2^40 / 128 = 2^33 (~8.6e9)
Please update and fix the commit message "io_sectors never increases", this
really is misleading.
>
>> What kernel version you're testing?
> 6.6.102. I also tested mainline and hit the same problem.
>
>> If this is latest kernel, bitmap_start_sync() need to be fixed. It can't
>> return skip while setting skipping sectors to 0.
> bitmap_start_sync() is actually fine -- it returns a large clean span
> (0x5ED03680 above). The recovery path just discards it: max_sync is only
> ever clamped down, so it returns 128 regardless.
>
>> And since this is dead loop, a cond_resched() will not fix anything.
> Agreed -- cond_resched() only stops the watchdog; the thread still spins
> ~8.6e9 no-op iterations and pins a CPU for ~313s.
>
> For v2 I'd fix this at the source: make the recovery path honour the clean
> span reported by the bitmap instead of capping the skip at 128. Does that
> direction look right to you?
This patch is still valuable, even a 5s lockup is not acceptable.
And I think this is not a problem for llbitmap, it skips one bit at a time and
bits are limited to at most 4k*128. Perhaps bitmap can do this as well, it can
definably return much bigger skipping sectors from bitmap_start_sync().
>
> Thanks,
> Yunye
--
Thanks,
Kuai
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 0/3] md/raid10: fix recovery corruption and soft lockup on large arrays
2026-07-17 6:27 [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped Yunye Zhao
2026-07-17 6:51 ` sashiko-bot
2026-07-19 10:45 ` yu kuai
@ 2026-07-23 13:55 ` Yunye Zhao
2026-07-23 13:55 ` [PATCH v2 1/3] md/raid10: fix still_degraded being inverted in raid10_sync_request() Yunye Zhao
` (2 more replies)
2 siblings, 3 replies; 11+ messages in thread
From: Yunye Zhao @ 2026-07-23 13:55 UTC (permalink / raw)
To: Song Liu, Yu Kuai
Cc: Li Nan, Xiao Ni, Joseph Qi, linux-raid, linux-kernel, Yunye Zhao
This series fixes two independent problems in raid10 recovery, plus a
safety net in md core.
Problem 1: silent data corruption when recovering with more than one
mirror missing.
Commit fe6a19d40ceb ("md/md-bitmap: merge md_bitmap_start_sync() into
bitmap_operations") inverted still_degraded in raid10_sync_request()
(int 1 -> bool false; the raid1 side of the same conversion is correct).
Recovering one disk while another is still missing therefore clears
bitmap bits the missing disk still needs; after its re-add the dirty
regions are skipped and the disk is marked In_sync with stale data.
Patch 1 restores the correct value.
Problem 2: soft lockup / CPU burn on recovery of a large sparse array
(the original report behind v1).
Recovering a degraded RAID10 with a mostly-clean bitmap makes
raid10_sync_request() take the "everything skipped" path, which returns
only 128 sectors per call even though the bitmap already reported a much
larger clean span. md_do_sync() then crawls across the whole device in
128-sector steps with no reschedule; on a 2^40-sector array that is 2^33
no-op iterations:
watchdog: BUG: soft lockup - CPU#149 stuck for 313s! [mdX_resync]
md_bitmap_start_sync+0x6f/0xe0
raid10_sync_request+0x2c9/0x1530 [raid10]
md_do_sync+0x810/0x1030
md_thread+0xa7/0x150
Patch 2 adds a cond_resched() to md_do_sync()'s skip path (safety net,
any personality/layout). Patch 3 fixes the root cause for near layouts:
convert the bitmap-reported clean span from array to per-device sectors
and skip it in one step. The conversion cannot be done generically in
the bitmap layer because the recovery cursor is in per-device space
(llbitmap's skip_sync_blocks() likewise returns 0 when degraded), and
resync paths already skip whole clean spans, so the fix lives in raid10.
Tested on QEMU/KVM on the latest mainline (raid10 near=2, 4 disks,
internal bitmap):
- data consistency: write, fail one disk of each pair, write 256MiB
while degraded, re-add both, wait for recovery, run "check":
mismatch_cnt=262272 and changed file hashes before patch 1,
mismatch_cnt=0 and identical hashes after.
- recovery of two re-added disks on a sparse 8T-per-disk array: one
37.2s recovery sweep of 100% CPU spinning before, 6.4 ms and 12.8 ms
for the two recovery passes after patch 3 (dmesg "recover of RAID
array" -> "recover done": 44.886958 -> 44.893378 and
44.923957 -> 44.936716); no soft lockup with softlockup_panic=1.
Changes since v1 (a single cond_resched patch):
- patch 1 (new): fix the still_degraded inversion, a silent data
corruption found while testing.
- patch 2 (was v1): rewrite the changelog and retitle; the v1 claim
that "io_sectors never increases" was wrong (Yu Kuai).
- patch 3 (new): fix the 128-sector recovery crawl at the source
instead of only silencing the watchdog.
v1: https://lore.kernel.org/linux-raid/20260717062743.128189-1-yunye.zhao@linux.alibaba.com/
Yunye Zhao (3):
md/raid10: fix still_degraded being inverted in raid10_sync_request()
md: add cond_resched() to md_do_sync()'s skip path
md/raid10: skip clean regions in bulk during recovery
drivers/md/md.c | 4 ++-
drivers/md/raid10.c | 57 ++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 56 insertions(+), 5 deletions(-)
base-commit: 4539944e515183668109bdf4d0c3d7d228383d88
--
2.19.1.6.gb485710b
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 1/3] md/raid10: fix still_degraded being inverted in raid10_sync_request()
2026-07-23 13:55 ` [PATCH v2 0/3] md/raid10: fix recovery corruption and soft lockup on large arrays Yunye Zhao
@ 2026-07-23 13:55 ` Yunye Zhao
2026-07-23 13:55 ` [PATCH v2 2/3] md: add cond_resched() to md_do_sync()'s skip path Yunye Zhao
2026-07-23 13:55 ` [PATCH v2 3/3] md/raid10: skip clean regions in bulk during recovery Yunye Zhao
2 siblings, 0 replies; 11+ messages in thread
From: Yunye Zhao @ 2026-07-23 13:55 UTC (permalink / raw)
To: Song Liu, Yu Kuai
Cc: Li Nan, Xiao Ni, Joseph Qi, linux-raid, linux-kernel, Yunye Zhao
Commit fe6a19d40ceb ("md/md-bitmap: merge md_bitmap_start_sync() into
bitmap_operations") converted still_degraded from int to bool, but
inverted the assignment in the loop that checks whether the array will
still be degraded after the current device is recovered:
"still_degraded = 1" became "still_degraded = false".
As a result, recovering a device while another mirror is still missing
calls md_bitmap_start_sync() with degraded == false, which clears bitmap
bits that the still-missing device needs. When that device is re-added,
its bitmap-based recovery finds the bits already cleared and skips every
region written while the array was degraded, so it is marked In_sync
while holding stale data: silent corruption.
Reproducer (raid10 near=2, 4 disks, internal bitmap):
- fail and remove one disk of each mirror pair
- write to the degraded array
- re-add both disks and let recovery finish
- "check" reports mismatch_cnt=262272 after 256 MiB of degraded
writes and file contents differ; the second disk's "recovery"
completes in milliseconds because everything is skipped
The same conversion in raid1 got it right (still_degraded = true).
Restore the correct value.
Fixes: fe6a19d40ceb ("md/md-bitmap: merge md_bitmap_start_sync() into bitmap_operations")
Cc: stable@vger.kernel.org
Signed-off-by: Yunye Zhao <yunye.zhao@linux.alibaba.com>
---
drivers/md/raid10.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index 0a3cfdd3f5df..54cddb3a98cd 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -3365,7 +3365,7 @@ static sector_t raid10_sync_request(struct mddev *mddev, sector_t sector_nr,
struct md_rdev *rdev = conf->mirrors[j].rdev;
if (rdev == NULL || test_bit(Faulty, &rdev->flags)) {
- still_degraded = false;
+ still_degraded = true;
break;
}
}
base-commit: 4539944e515183668109bdf4d0c3d7d228383d88
--
2.19.1.6.gb485710b
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 2/3] md: add cond_resched() to md_do_sync()'s skip path
2026-07-23 13:55 ` [PATCH v2 0/3] md/raid10: fix recovery corruption and soft lockup on large arrays Yunye Zhao
2026-07-23 13:55 ` [PATCH v2 1/3] md/raid10: fix still_degraded being inverted in raid10_sync_request() Yunye Zhao
@ 2026-07-23 13:55 ` Yunye Zhao
2026-07-23 14:05 ` sashiko-bot
2026-07-23 13:55 ` [PATCH v2 3/3] md/raid10: skip clean regions in bulk during recovery Yunye Zhao
2 siblings, 1 reply; 11+ messages in thread
From: Yunye Zhao @ 2026-07-23 13:55 UTC (permalink / raw)
To: Song Liu, Yu Kuai
Cc: Li Nan, Xiao Ni, Joseph Qi, linux-raid, linux-kernel, Yunye Zhao
When sync_request() reports a skipped region (*skipped == 1),
md_do_sync()'s main loop advances the cursor and takes an early
continue:
j += sectors;
...
if (last_check + window > io_sectors || j == max_sectors)
continue;
If the personality returns a small span per call (raid10 recovery
returns only 128 sectors), syncing a large, mostly clean array iterates
this branch an enormous number of times without ever yielding the CPU.
On a non-preemptive kernel the resync thread then trips the soft-lockup
watchdog:
watchdog: BUG: soft lockup - CPU#149 stuck for 313s! [mdX_resync]
md_bitmap_start_sync+0x6f/0xe0
raid10_sync_request+0x2c9/0x1530 [raid10]
md_do_sync+0x810/0x1030
md_thread+0xa7/0x150
Add a cond_resched(). This does not reduce the wasted iterations; the
excessive iteration count is a raid10 problem addressed separately.
Cc: stable@vger.kernel.org
Signed-off-by: Yunye Zhao <yunye.zhao@linux.alibaba.com>
---
drivers/md/md.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/md/md.c b/drivers/md/md.c
index d1465bcd86c8..c2e8b203c4db 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -9881,8 +9881,10 @@ void md_do_sync(struct md_thread *thread)
*/
md_new_event();
- if (last_check + window > io_sectors || j == max_sectors)
+ if (last_check + window > io_sectors || j == max_sectors) {
+ cond_resched();
continue;
+ }
last_check = io_sectors;
repeat:
--
2.19.1.6.gb485710b
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 3/3] md/raid10: skip clean regions in bulk during recovery
2026-07-23 13:55 ` [PATCH v2 0/3] md/raid10: fix recovery corruption and soft lockup on large arrays Yunye Zhao
2026-07-23 13:55 ` [PATCH v2 1/3] md/raid10: fix still_degraded being inverted in raid10_sync_request() Yunye Zhao
2026-07-23 13:55 ` [PATCH v2 2/3] md: add cond_resched() to md_do_sync()'s skip path Yunye Zhao
@ 2026-07-23 13:55 ` Yunye Zhao
2026-07-23 14:16 ` sashiko-bot
2 siblings, 1 reply; 11+ messages in thread
From: Yunye Zhao @ 2026-07-23 13:55 UTC (permalink / raw)
To: Song Liu, Yu Kuai
Cc: Li Nan, Xiao Ni, Joseph Qi, linux-raid, linux-kernel, Yunye Zhao
During recovery of a degraded RAID10 with a mostly-clean bitmap, the
"everything skipped" path (biolist == NULL) returns max_sync == 128
sectors, even though md_bitmap_start_sync() already reported a much
larger clean range (an unallocated bitmap page spans 2^31 sectors with
a 512M bitmap chunk). On the reported 2^40-sector array md_do_sync()
then crawls through 2^33 no-op iterations, burning a CPU for the whole
sweep; the cond_resched() in md_do_sync()'s skip path only stops the
watchdog firing.
The skip path cannot simply return sync_blocks: sync_blocks is measured
in array sectors while the return value advances the per-device recovery
cursor, and the two spaces differ by the raid10 layout. For a near
layout (far_copies == 1 && !far_offset) the mapping is linear with slope
raid_disks / near_copies; when near_copies evenly divides raid_disks the
conversion is exact. Track the minimum clean span across the skipped
devices, convert it to device sectors and skip it in one step.
far/offset layouts are not a linear scale and keep the previous
behaviour.
Only devices skipped as clean feed that minimum, and the skip is
suppressed when a device needed rebuilding but had no readable source
(missing_source), so the cursor never jumps past sectors that still
need recovery.
The generic bitmap skip_sync_blocks() path cannot be used here: it does
not see mrdev/mreplace (the recovery target and its replacement), and
replacement targets must rebuild even bitmap-clean chunks.
On a mostly-clean array with 8T per device (near=2, 4 disks) the
recovery sweep drops from 37.2s of CPU spinning to about 10ms.
Signed-off-by: Yunye Zhao <yunye.zhao@linux.alibaba.com>
---
drivers/md/raid10.c | 46 ++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 43 insertions(+), 3 deletions(-)
diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index 54cddb3a98cd..8acbd5ef618b 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -3176,6 +3176,9 @@ static sector_t raid10_sync_request(struct mddev *mddev, sector_t sector_nr,
int i;
int max_sync;
sector_t sync_blocks;
+ sector_t min_sync_blocks = MaxSector;
+ sector_t sync_end;
+ bool missing_source = false;
sector_t chunk_mask = conf->geo.chunk_mask;
int page_idx = 0;
@@ -3258,6 +3261,14 @@ static sector_t raid10_sync_request(struct mddev *mddev, sector_t sector_nr,
if (max_sector > mddev->resync_max)
max_sector = mddev->resync_max; /* Don't do IO beyond here */
+ /*
+ * The chunk clamp below caps a single sync I/O to at most one chunk.
+ * The bitmap-clean recovery skip issues no I/O, so remember the real
+ * end here to bound the skip against it rather than the chunk
+ * boundary.
+ */
+ sync_end = max_sector;
+
/* make sure whole request will fit in a chunk - if chunks
* are meaningful
*/
@@ -3334,9 +3345,13 @@ static sector_t raid10_sync_request(struct mddev *mddev, sector_t sector_nr,
if (!must_sync &&
mreplace == NULL &&
!conf->fullsync) {
- /* yep, skip the sync_blocks here, but don't assume
- * that there will never be anything to do here
+ /* Skip the clean sync_blocks here; don't
+ * assume there will never be anything to
+ * do. Only a genuinely skipped clean span
+ * may widen the bulk skip below.
*/
+ if (sync_blocks < min_sync_blocks)
+ min_sync_blocks = sync_blocks;
continue;
}
if (mrdev)
@@ -3459,6 +3474,7 @@ static sector_t raid10_sync_request(struct mddev *mddev, sector_t sector_nr,
if (j == conf->copies) {
/* Cannot recover, so abort the recovery or
* record a bad block */
+ missing_source = true;
if (any_working) {
/* problem is that there are bad blocks
* on other device(s)
@@ -3514,6 +3530,8 @@ static sector_t raid10_sync_request(struct mddev *mddev, sector_t sector_nr,
}
}
if (biolist == NULL) {
+ sector_t skip_sectors = max_sync;
+
while (r10_bio) {
struct r10bio *rb2 = r10_bio;
r10_bio = (struct r10bio*) rb2->master_bio;
@@ -3521,7 +3539,29 @@ static sector_t raid10_sync_request(struct mddev *mddev, sector_t sector_nr,
put_buf(rb2);
}
*skipped = 1;
- return max_sync;
+
+ /*
+ * min_sync_blocks is in array sectors, but the return
+ * value advances the per-device cursor; for a near
+ * layout the two spaces differ by the factor
+ * raid_disks / near_copies.
+ */
+ if (!missing_source &&
+ conf->geo.far_copies == 1 && !conf->geo.far_offset &&
+ conf->geo.raid_disks % conf->geo.near_copies == 0 &&
+ min_sync_blocks != MaxSector) {
+ sector_t clean_sectors = min_sync_blocks;
+
+ clean_sectors *= conf->geo.near_copies;
+ sector_div(clean_sectors, conf->geo.raid_disks);
+ clean_sectors &= ~chunk_mask;
+ if (clean_sectors > sync_end - sector_nr)
+ clean_sectors = sync_end - sector_nr;
+ if (clean_sectors > skip_sectors)
+ skip_sectors = clean_sectors;
+ }
+
+ return skip_sectors;
}
} else {
/* resync. Schedule a read for every block at this virt offset */
--
2.19.1.6.gb485710b
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v2 2/3] md: add cond_resched() to md_do_sync()'s skip path
2026-07-23 13:55 ` [PATCH v2 2/3] md: add cond_resched() to md_do_sync()'s skip path Yunye Zhao
@ 2026-07-23 14:05 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-07-23 14:05 UTC (permalink / raw)
To: Yunye Zhao; +Cc: yukuai, linux-raid
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Unbounded waitqueue spam from md_new_event() during sync skip loops.
--
--- Patch [2]: [PATCH v2 2/3] md: add cond_resched() to md_do_sync()'s skip path ---
commit 6f3b7b59be28b3b7daedbe090461ee12870bd481
Author: Yunye Zhao <yunye.zhao@linux.alibaba.com>
md: add cond_resched() to md_do_sync()'s skip path
This patch adds a cond_resched() to the skip loop in md_do_sync(). It
addresses a soft lockup watchdog trigger during raid10 recovery on large,
mostly clean arrays by yielding the CPU when regions are skipped.
> diff --git a/drivers/md/md.c b/drivers/md/md.c
> index d1465bcd86c81..c2e8b203c4db2 100644
> --- a/drivers/md/md.c
> +++ b/drivers/md/md.c
> @@ -9881,8 +9881,10 @@ void md_do_sync(struct md_thread *thread)
> */
> md_new_event();
>
> - if (last_check + window > io_sectors || j == max_sectors)
> + if (last_check + window > io_sectors || j == max_sectors) {
> + cond_resched();
> continue;
> + }
[Severity: High]
This isn't a bug introduced by this patch, but does this code cause unbounded
waitqueue spam from md_new_event() during sync skip loops?
When last_check starts at 0 and the personality skips regions, io_sectors
does not increment. Because last_check remains 0, the block just above this
diff checks:
if (last_check == 0)
/* this is the earliest that rebuild will be
* visible in /proc/mdstat
*/
md_new_event();
Since last_check == 0 evaluates to true on every iteration of the skip loop,
md_new_event() is called continuously.
For a large clean array with small skip chunks (like 128 sectors), this could
execute millions of times.
Could this lead to severe waitqueue spam and lock contention on
md_event_waiters, resulting in spurious wakeups for userspace processes
monitoring mdstat?
>
> last_check = io_sectors;
> repeat:
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723135535.101995-1-yunye.zhao@linux.alibaba.com?part=2
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 3/3] md/raid10: skip clean regions in bulk during recovery
2026-07-23 13:55 ` [PATCH v2 3/3] md/raid10: skip clean regions in bulk during recovery Yunye Zhao
@ 2026-07-23 14:16 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-07-23 14:16 UTC (permalink / raw)
To: Yunye Zhao; +Cc: linux-raid, yukuai
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Fallback to an unscaled `max_sync` mixes array and device sectors, overshooting the recovery cursor and silently skipping dirty sectors.
--
--- Patch [3]: [PATCH v2 3/3] md/raid10: skip clean regions in bulk during recovery ---
commit 4abce2392f76451810793b3efca0299da30d9898
Author: Yunye Zhao <yunye.zhao@linux.alibaba.com>
md/raid10: skip clean regions in bulk during recovery
During recovery of a degraded RAID10 array with a mostly-clean bitmap, this
patch calculates the skipped clean span in bulk. It tracks the minimum clean
span across skipped devices, converts it to device sectors, and skips it in
one step to reduce CPU spinning.
> diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
> index 54cddb3a98cd6..8acbd5ef618b4 100644
> --- a/drivers/md/raid10.c
> +++ b/drivers/md/raid10.c
[ ... ]
> @@ -3514,6 +3530,8 @@ static sector_t raid10_sync_request(struct mddev *mddev, sector_t sector_nr,
> }
> }
> if (biolist == NULL) {
> + sector_t skip_sectors = max_sync;
> +
> while (r10_bio) {
> struct r10bio *rb2 = r10_bio;
> r10_bio = (struct r10bio*) rb2->master_bio;
> @@ -3521,7 +3539,29 @@ static sector_t raid10_sync_request(struct mddev *mddev, sector_t sector_nr,
> put_buf(rb2);
> }
> *skipped = 1;
> - return max_sync;
> +
> + /*
> + * min_sync_blocks is in array sectors, but the return
> + * value advances the per-device cursor; for a near
> + * layout the two spaces differ by the factor
> + * raid_disks / near_copies.
> + */
> + if (!missing_source &&
> + conf->geo.far_copies == 1 && !conf->geo.far_offset &&
> + conf->geo.raid_disks % conf->geo.near_copies == 0 &&
> + min_sync_blocks != MaxSector) {
> + sector_t clean_sectors = min_sync_blocks;
> +
> + clean_sectors *= conf->geo.near_copies;
> + sector_div(clean_sectors, conf->geo.raid_disks);
> + clean_sectors &= ~chunk_mask;
> + if (clean_sectors > sync_end - sector_nr)
> + clean_sectors = sync_end - sector_nr;
> + if (clean_sectors > skip_sectors)
> + skip_sectors = clean_sectors;
> + }
> +
> + return skip_sectors;
[Severity: Critical]
This is a pre-existing issue, but does returning the unscaled max_sync mix
array and device sectors here?
When recovering a degraded RAID10 array, md_bitmap_start_sync() returns
sync_blocks in array sectors, which is then assigned to max_sync.
If a clean span is small or not chunk-aligned, the truncation:
clean_sectors &= ~chunk_mask;
can force the scaled clean_sectors to 0. When that happens, the condition:
if (clean_sectors > skip_sectors)
skip_sectors = clean_sectors;
fails, leaving skip_sectors as the unscaled max_sync value.
Because raid10_sync_request() then returns skip_sectors (an array-sector
distance) to advance the physical per-device cursor in md_do_sync(), could
the physical cursor overstep the actual clean physical span?
For layouts where array sectors advance faster than device sectors
(e.g., near_copies > 1), won't this silently skip dirty sectors and leave
mirrors out of sync?
> }
> } else {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723135535.101995-1-yunye.zhao@linux.alibaba.com?part=3
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-07-23 14:16 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-17 6:27 [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped Yunye Zhao
2026-07-17 6:51 ` sashiko-bot
2026-07-19 10:45 ` yu kuai
2026-07-20 6:20 ` Yunye Zhao
2026-07-22 4:00 ` yu kuai
2026-07-23 13:55 ` [PATCH v2 0/3] md/raid10: fix recovery corruption and soft lockup on large arrays Yunye Zhao
2026-07-23 13:55 ` [PATCH v2 1/3] md/raid10: fix still_degraded being inverted in raid10_sync_request() Yunye Zhao
2026-07-23 13:55 ` [PATCH v2 2/3] md: add cond_resched() to md_do_sync()'s skip path Yunye Zhao
2026-07-23 14:05 ` sashiko-bot
2026-07-23 13:55 ` [PATCH v2 3/3] md/raid10: skip clean regions in bulk during recovery Yunye Zhao
2026-07-23 14:16 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox