From 47ca359e952d3994fe66aeb1721296aaebf1fccb Mon Sep 17 00:00:00 2001 From: Favilances <78090594+favilances@users.noreply.github.com> Date: Tue, 26 May 2026 23:18:38 +0300 Subject: [PATCH] Prevent checkpointed DTLs from restarting resilvers Pool checkpoints intentionally keep DTL_MISSING entries after a scan because checkpoint-only blocks are not traversed. Those retained DTLs are needed if the pool is rewound, but they are not evidence of a deferred resilver. vdev_clear_resilver_deferred() treated any remaining DTL on an available leaf vdev as a reason to start another resilver, even when the vdev had not been marked vdev_resilver_deferred. With a checkpoint present, a completed resilver therefore queued another resilver indefinitely. Only request the follow-up scan when a deferred flag was actually cleared. Retained checkpoint DTLs stay preserved, while real deferred resilvers still run after the current scan completes. Add a regression test for the checkpoint case. Closes #11434 Closes #17109 Signed-off-by: Favilances <78090594+favilances@users.noreply.github.com> --- module/zfs/vdev.c | 8 +- tests/runfiles/common.run | 2 +- tests/zfs-tests/tests/Makefile.am | 1 + .../replacement/resilver_restart_003.ksh | 93 +++++++++++++++++++ 4 files changed, 100 insertions(+), 4 deletions(-) create mode 100755 tests/zfs-tests/tests/functional/replacement/resilver_restart_003.ksh diff --git a/module/zfs/vdev.c b/module/zfs/vdev.c index e4dc9e97af71..79f74d22e433 100644 --- a/module/zfs/vdev.c +++ b/module/zfs/vdev.c @@ -5866,13 +5866,14 @@ vdev_defer_resilver(vdev_t *vd) /* * Clears the resilver deferred flag on all leaf devs under vd. Returns - * B_TRUE if we have devices that need to be resilvered and are available to - * accept resilver I/Os. + * B_TRUE if we cleared a deferred device that still needs to be resilvered + * and is available to accept resilver I/Os. */ boolean_t vdev_clear_resilver_deferred(vdev_t *vd, dmu_tx_t *tx) { boolean_t resilver_needed = B_FALSE; + boolean_t was_deferred; spa_t *spa = vd->vdev_spa; for (int c = 0; c < vd->vdev_children; c++) { @@ -5892,9 +5893,10 @@ vdev_clear_resilver_deferred(vdev_t *vd, dmu_tx_t *tx) !vd->vdev_ops->vdev_op_leaf) return (resilver_needed); + was_deferred = vd->vdev_resilver_deferred; vd->vdev_resilver_deferred = B_FALSE; - return (!vdev_is_dead(vd) && !vd->vdev_offline && + return (was_deferred && !vdev_is_dead(vd) && !vd->vdev_offline && vdev_resilver_needed(vd, NULL, NULL)); } diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 6e62b552a0db..41e61707cd2c 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -975,7 +975,7 @@ tests = ['attach_import', 'attach_multiple', 'attach_rebuild', 'rebuild_disabled_feature', 'rebuild_multiple', 'rebuild_raidz', 'replace_import', 'replace_rebuild', 'replace_resilver', 'replace_resilver_sit_out', 'resilver_restart_001', - 'resilver_restart_002', 'scrub_cancel'] + 'resilver_restart_002', 'resilver_restart_003', 'scrub_cancel'] tags = ['functional', 'replacement'] [tests/functional/reservation] diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index 98f392538821..39c21a4046c5 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -2021,6 +2021,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/replacement/replace_resilver_sit_out.ksh \ functional/replacement/resilver_restart_001.ksh \ functional/replacement/resilver_restart_002.ksh \ + functional/replacement/resilver_restart_003.ksh \ functional/replacement/scrub_cancel.ksh \ functional/replacement/setup.ksh \ functional/reservation/cleanup.ksh \ diff --git a/tests/zfs-tests/tests/functional/replacement/resilver_restart_003.ksh b/tests/zfs-tests/tests/functional/replacement/resilver_restart_003.ksh new file mode 100755 index 000000000000..1e90b277ab29 --- /dev/null +++ b/tests/zfs-tests/tests/functional/replacement/resilver_restart_003.ksh @@ -0,0 +1,93 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 + +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/replacement/replacement.cfg + +# +# DESCRIPTION: +# Verify that a resilver does not restart after completing while a pool +# checkpoint is present. +# +# STRATEGY: +# 1. Create a single-disk pool and write data. +# 2. Start an attach resilver and suspend scan progress. +# 3. Create a checkpoint while the resilver is active. +# 4. Let the resilver finish. +# 5. Verify the checkpoint-retained DTL does not start another resilver. +# + +verify_runnable "global" + +function cleanup +{ + log_must set_tunable32 SCAN_SUSPEND_PROGRESS \ + $ORIG_SCAN_SUSPEND_PROGRESS + log_must set_tunable32 RESILVER_MIN_TIME_MS $ORIG_RESILVER_MIN_TIME + log_must zpool events + destroy_pool $TESTPOOL1 + rm -f ${VDEV_FILES[0]} $SPARE_VDEV_FILE +} + +function wait_for_event # pattern timeout +{ + typeset pattern=$1 + typeset timeout=${2:-60} + typeset -i events=0 + + for (( iter = 0; iter < timeout; iter++ )); do + events=$(zpool events | grep -cF "$pattern") + (( events > 0 )) && return 0 + sleep 1 + done + + return 1 +} + +log_assert "Checkpointed DTLs do not restart a completed resilver" + +ORIG_SCAN_SUSPEND_PROGRESS=$(get_tunable SCAN_SUSPEND_PROGRESS) +ORIG_RESILVER_MIN_TIME=$(get_tunable RESILVER_MIN_TIME_MS) + +log_onexit cleanup + +log_must truncate -s $VDEV_FILE_SIZE ${VDEV_FILES[0]} $SPARE_VDEV_FILE +log_must zpool create -f -O recordsize=128K $TESTPOOL1 ${VDEV_FILES[0]} +log_must eval "dd if=/dev/urandom of=/$TESTPOOL1/file bs=1M count=32" \ + ">/dev/null 2>&1" + +log_must zpool events -c +log_must set_tunable32 RESILVER_MIN_TIME_MS 20 +log_must set_tunable32 SCAN_SUSPEND_PROGRESS 1 +log_must zpool attach $TESTPOOL1 ${VDEV_FILES[0]} $SPARE_VDEV_FILE + +log_must wait_for_event "sysevent.fs.zfs.resilver_start" 60 +log_must zpool checkpoint $TESTPOOL1 + +log_must set_tunable32 SCAN_SUSPEND_PROGRESS 0 +log_must wait_for_event "sysevent.fs.zfs.resilver_finish" 120 + +# Wait a few txgs to ensure completion does not queue a deferred resilver. +sync_pool $TESTPOOL1 true +sync_pool $TESTPOOL1 true + +resilver_starts=$(zpool events | grep -cF "sysevent.fs.zfs.resilver_start") +(( resilver_starts != 1 )) && + log_fail "expected 1 resilver start, found $resilver_starts" + +log_pass "Completed resilver was not restarted with checkpoint present"