[PATCH] md: fix soft lockup during resync when sync is repeatedly skipped

Yunye Zhao posted 1 patch 1 week ago
drivers/md/md.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
[PATCH] md: fix soft lockup during resync when sync is repeatedly skipped
Posted by Yunye Zhao 1 week ago
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
[PATCH v2 0/3] md/raid10: fix recovery corruption and soft lockup on large arrays
Posted by Yunye Zhao 1 day, 15 hours ago
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
[PATCH v2 1/3] md/raid10: fix still_degraded being inverted in raid10_sync_request()
Posted by Yunye Zhao 1 day, 15 hours ago
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
[PATCH v2 2/3] md: add cond_resched() to md_do_sync()'s skip path
Posted by Yunye Zhao 1 day, 15 hours ago
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
[PATCH v2 3/3] md/raid10: skip clean regions in bulk during recovery
Posted by Yunye Zhao 1 day, 15 hours ago
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
Re: [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped
Posted by yu kuai 5 days, 18 hours ago
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
Re: [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped
Posted by Yunye Zhao 4 days, 23 hours ago
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
Re: [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped
Posted by yu kuai 3 days, 1 hour ago
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