diff --git a/module/zfs/zio.c b/module/zfs/zio.c index 26b1ae180..77383df0e 100644 --- a/module/zfs/zio.c +++ b/module/zfs/zio.c @@ -4157,6 +4157,17 @@ zio_ddt_free(zio_t *zio) ddt_phys_variant_t v = ddt_phys_select(ddt, dde, bp); if (v != DDT_PHYS_NONE) ddt_phys_decref(dde->dde_phys, v); + else + /* + * If the entry was found but the phys was not, then + * this block must have been pruned from the dedup + * table, and the entry refers to a later version of + * this data. Therefore, the caller is trying to delete + * the only stored instance of this block, and so we + * need to do a normal (not dedup) free. Clear dde so + * we fall into the block below. + */ + dde = NULL; } ddt_exit(ddt); diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index a69c6e3c8..3b5760e3b 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -706,7 +706,8 @@ tags = ['functional', 'deadman'] [tests/functional/dedup] tests = ['dedup_fdt_create', 'dedup_fdt_import', 'dedup_fdt_pacing', 'dedup_legacy_create', 'dedup_legacy_import', 'dedup_legacy_fdt_upgrade', - 'dedup_legacy_fdt_mixed', 'dedup_quota', 'dedup_prune', 'dedup_zap_shrink'] + 'dedup_legacy_fdt_mixed', 'dedup_quota', 'dedup_prune', 'dedup_prune_leak', + 'dedup_zap_shrink'] pre = post = tags = ['functional', 'dedup'] diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index 23284234c..4502f268a 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -1482,6 +1482,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/dedup/dedup_legacy_fdt_upgrade.ksh \ functional/dedup/dedup_legacy_fdt_mixed.ksh \ functional/dedup/dedup_prune.ksh \ + functional/dedup/dedup_prune_leak.ksh \ functional/dedup/dedup_quota.ksh \ functional/dedup/dedup_zap_shrink.ksh \ functional/delegate/cleanup.ksh \ diff --git a/tests/zfs-tests/tests/functional/dedup/dedup_prune_leak.ksh b/tests/zfs-tests/tests/functional/dedup/dedup_prune_leak.ksh new file mode 100755 index 000000000..ca281cc3f --- /dev/null +++ b/tests/zfs-tests/tests/functional/dedup/dedup_prune_leak.ksh @@ -0,0 +1,86 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2025, Klara Inc. +# Copyright (c) 2025, Nutanix Inc. +# + +# DESCRIPTION: +# Verify that zpool ddtprune successfully reduces the number of entries +# in the DDT. +# +# STRATEGY: +# 1. Create a pool with dedup=on +# 2. Add non-duplicate entries to the DDT +# 3. ddtprune all entries +# 4. Remove the file +# 5. Verify there's no space leak +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/events/events_common.kshlib + +verify_runnable "both" + +log_assert "Verify DDT pruning does not cause space leak" + +# We set the dedup log txg interval to 1, to get a log flush every txg, +# effectively disabling the log. Without this it's hard to predict when +# entries appear in the DDT ZAP +log_must save_tunable DEDUP_LOG_TXG_MAX +log_must set_tunable32 DEDUP_LOG_TXG_MAX 1 +log_must save_tunable DEDUP_LOG_FLUSH_ENTRIES_MIN +log_must set_tunable32 DEDUP_LOG_FLUSH_ENTRIES_MIN 100000 +function cleanup +{ + if poolexists $TESTPOOL ; then + destroy_pool $TESTPOOL + fi + log_must restore_tunable DEDUP_LOG_TXG_MAX + log_must restore_tunable DEDUP_LOG_FLUSH_ENTRIES_MIN +} + +log_onexit cleanup + +log_must zpool create -f $TESTPOOL $DISKS + +log_must zfs create -o dedup=on $TESTPOOL/$TESTFS +typeset mountpoint=$(get_prop mountpoint $TESTPOOL/$TESTFS) +log_must dd if=/dev/urandom of=$mountpoint/f1 bs=1M count=16 +# We seems to need some amount of txg sync here to make it more consistently +# reproducible +for i in $(seq 50); do + zpool sync $TESTPOOL +done + +log_must zpool ddtprune -p 100 $TESTPOOL +log_must rm $mountpoint/f1 +sync_pool $TESTPOOL + +zdb_out=$(zdb -bcc $TESTPOOL) +echo "$zdb_out" +if echo "$zdb_out" | grep -q "leaked space"; then + log_fail "DDT pruning causes space leak" +fi + +log_pass "DDT pruning does not cause space leak"