scan: count skipped blocks as examined

dsl_scan_scrub_cb() passes a block outside the scan's txg range to
count_block_skipped() and returns before scn_examined is touched, so
scn_skipped can exceed scn_examined and the resume in dsl_scan_init()
wraps: zpool status reports 16.0E issued. The deferred resilver check
in dsl_scan_sync() reads the same counter, and a wrapped value never
falls below zfs_resilver_defer_percent, so a resilver that should
restart early stays deferred.

Count those blocks as examined as well as skipped; they were traversed,
and the two counters have to cover the same blocks. scn_phys is on
disk, so keep clamping the resume for pools an older version skewed,
and clamp scn_to_examine - scn_skipped, which wraps the same way.

Reviewed-by: Brian Behlendorf <[email protected]>
Signed-off-by: Nick Price <[email protected]>
Closes #18928
This commit is contained in:
Nick Price authored and GitHub committed 2026-08-23 10:33:51 -07:00
1 parent cf2a8ce078
commit 5bd7d44667
1 file changed
+13 -5
+13 -5
View File
@@ -674,10 +674,12 @@ dsl_scan_init(dsl_pool_t *dp, uint64_t txg)
/*
* We might be restarting after a reboot, so jump the issued
* counter to how far we've scanned. We know we're consistent
* up to here.
* up to here. scn_phys is on disk, so an older version may
* have left scn_skipped above scn_examined.
*/
scn->scn_issued_before_pass = scn->scn_phys.scn_examined -
scn->scn_phys.scn_skipped;
scn->scn_issued_before_pass =
scn->scn_phys.scn_examined > scn->scn_phys.scn_skipped ?
scn->scn_phys.scn_examined - scn->scn_phys.scn_skipped : 0;
if (dsl_scan_is_running(scn) &&
spa_prev_software_version(dp->dp_spa) < SPA_VERSION_SCAN) {
@@ -4510,8 +4512,10 @@ dsl_scan_sync(dsl_pool_t *dp, dmu_tx_t *tx)
* See print_scan_scrub_resilver_status() issued/total_i
* @ cmd/zpool/zpool_main.c
*/
to_issue =
scn->scn_phys.scn_to_examine - scn->scn_phys.scn_skipped;
/* scn_to_examine is sampled once; scn_skipped keeps growing. */
to_issue = scn->scn_phys.scn_to_examine >
scn->scn_phys.scn_skipped ? scn->scn_phys.scn_to_examine -
scn->scn_phys.scn_skipped : 0;
issued =
scn->scn_issued_before_pass + spa->spa_scan_pass_issued;
restart_early =
@@ -4989,6 +4993,10 @@ dsl_scan_scrub_cb(dsl_pool_t *dp,
count_block(dp->dp_blkstats, bp);
if (phys_birth <= scn->scn_phys.scn_min_txg ||
phys_birth >= scn->scn_phys.scn_max_txg) {
/* Traversed but not scrubbed; both counters must see it. */
uint64_t asize = BP_GET_ASIZE(bp);
scn->scn_phys.scn_examined += asize;
spa->spa_scan_pass_exam += asize;
count_block_skipped(scn, bp, B_TRUE);
return (0);
}