Fix activating large_microzap on receive

This ensures that the in-memory state of the feature is recorded and
that `dsl_dataset_activate_feature` is not called when the feature
is already active.

Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Austin Wise <AustinWise@gmail.com>
Closes #18143
Closes #18144
This commit is contained in:
Austin Wise 2026-02-05 15:48:03 -08:00 committed by GitHub
parent 20f94ef24a
commit 4f180e095a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 215 additions and 18 deletions

View File

@ -997,24 +997,6 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx)
numredactsnaps, tx);
}
if (featureflags & DMU_BACKUP_FEATURE_LARGE_MICROZAP) {
/*
* The source has seen a large microzap at least once in its
* life, so we activate the feature here to match. It's not
* strictly necessary since a large microzap is usable without
* the feature active, but if that object is sent on from here,
* we need this info to know to add the stream feature.
*
* There may be no large microzap in the incoming stream, or
* ever again, but this is a very niche feature and its very
* difficult to spot a large microzap in the stream, so its
* not worth the effort of trying harder to activate the
* feature at first use.
*/
dsl_dataset_activate_feature(dsobj, SPA_FEATURE_LARGE_MICROZAP,
(void *)B_TRUE, tx);
}
dmu_buf_will_dirty(newds->ds_dbuf, tx);
dsl_dataset_phys(newds)->ds_flags |= DS_FLAG_INCONSISTENT;
@ -1044,6 +1026,26 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx)
newds->ds_feature[SPA_FEATURE_LONGNAME] = (void *)B_TRUE;
}
if (featureflags & DMU_BACKUP_FEATURE_LARGE_MICROZAP &&
!dsl_dataset_feature_is_active(newds, SPA_FEATURE_LARGE_MICROZAP)) {
/*
* The source has seen a large microzap at least once in its
* life, so we activate the feature here to match. It's not
* strictly necessary since a large microzap is usable without
* the feature active, but if that object is sent on from here,
* we need this info to know to add the stream feature.
*
* There may be no large microzap in the incoming stream, or
* ever again, but this is a very niche feature and its very
* difficult to spot a large microzap in the stream, so its
* not worth the effort of trying harder to activate the
* feature at first use.
*/
dsl_dataset_activate_feature(dsobj, SPA_FEATURE_LARGE_MICROZAP,
(void *)B_TRUE, tx);
newds->ds_feature[SPA_FEATURE_LARGE_MICROZAP] = (void *)B_TRUE;
}
/*
* If we actually created a non-clone, we need to create the objset
* in our new dataset. If this is a raw send we postpone this until

View File

@ -998,6 +998,7 @@ tests = ['recv_dedup', 'recv_dedup_encrypted_zvol', 'rsend_001_pos',
'send_spill_block', 'send_holds', 'send_hole_birth', 'send_mixed_raw',
'send-wR_encrypted_zvol', 'send_partial_dataset', 'send_invalid',
'send_large_blocks_incremental', 'send_large_blocks_initial',
'send_large_microzap_incremental', 'send_large_microzap_transitive',
'send_doall', 'send_raw_spill_block', 'send_raw_ashift',
'send_raw_large_blocks', 'send_leak_keymaps']
tags = ['functional', 'rsend']

View File

@ -114,6 +114,7 @@ BCLONE_WAIT_DIRTY bclone_wait_dirty zfs_bclone_wait_dirty
DIO_ENABLED dio_enabled zfs_dio_enabled
DIO_STRICT dio_strict zfs_dio_strict
XATTR_COMPAT xattr_compat zfs_xattr_compat
ZAP_MICRO_MAX_SIZE zap_micro_max_size zap_micro_max_size
ZEVENT_LEN_MAX zevent.len_max zfs_zevent_len_max
ZEVENT_RETAIN_MAX zevent.retain_max zfs_zevent_retain_max
ZIO_SLOW_IO_MS zio.slow_io_ms zio_slow_io_ms

View File

@ -2067,6 +2067,8 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/rsend/send_invalid.ksh \
functional/rsend/send_large_blocks_incremental.ksh \
functional/rsend/send_large_blocks_initial.ksh \
functional/rsend/send_large_microzap_incremental.ksh \
functional/rsend/send_large_microzap_transitive.ksh \
functional/rsend/send_leak_keymaps.ksh \
functional/rsend/send-L_toggle.ksh \
functional/rsend/send_mixed_raw.ksh \

View File

@ -0,0 +1,91 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0
#
# 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.
#
#
# Copyright (c) 2026 by Austin Wise. All rights reserved.
#
. $STF_SUITE/tests/functional/rsend/rsend.kshlib
. $STF_SUITE/include/properties.shlib
#
# Description:
# Ensure that it is possible to receive an incremental send of a snapshot that has the large microzap
# feature active into a pool where the feature is already active.
# Regression test for https://github.com/openzfs/zfs/issues/18143
#
# Strategy:
# 1. Activate the large microzap feature in the source dataset.
# 2. Create a snapshot.
# 3. Send the snapshot from the first pool to a second pool.
# 4. Create a second snapshot.
# 5. Send the second snapshot incrementally to the second pool.
#
verify_runnable "both"
log_assert "Verify incremental receive handles inactive large_blocks feature correctly."
function cleanup
{
restore_tunable ZAP_MICRO_MAX_SIZE
cleanup_pool $POOL
cleanup_pool $POOL2
}
function assert_feature_state {
typeset pool=$1
typeset expected_state=$2
typeset actual_state=$(zpool get -H -o value feature@large_microzap $pool)
log_note "Zpool $pool feature@large_microzap=$actual_state"
if [[ "$actual_state" != "$expected_state" ]]; then
log_fail "pool $pool feature@large_microzap=$actual_state (expected '$expected_state')"
fi
}
typeset src=$POOL/src
typeset second=$POOL2/second
log_onexit cleanup
# Allow micro ZAPs to grow beyond SPA_OLD_MAXBLOCKSIZE.
set_tunable64 ZAP_MICRO_MAX_SIZE 1048576
# Create a dataset with a large recordsize (1MB)
log_must zfs create -o recordsize=1M $src
typeset mntpnt=$(get_prop mountpoint $src)
# Activate the large_microzap feature by creating a micro ZAP that is larger than SPA_OLD_MAXBLOCKSIZE (128k)
# but smaller than MZAP_MAX_SIZE (1MB). Each micro ZAP entry is 64 bytes (MZAP_ENT_LEN),
# so 4096 files is about 256k.
log_must eval "seq 1 4096 | xargs -I REPLACE_ME touch $mntpnt/REPLACE_ME"
log_must zpool sync $POOL
# Assert initial state of pools
assert_feature_state $POOL "active"
assert_feature_state $POOL2 "enabled"
# Create initial snapshot and send to second pool.
log_must zfs snapshot $src@snap
log_must eval "zfs send -p -L $src@snap | zfs receive $second"
log_must zpool sync $POOL2
assert_feature_state $POOL2 "active"
# Create a second snapshot and send incrementally.
# This ensures that the feature is not activated a second time, which would cause a panic.
log_must zfs snapshot $src@snap2
log_must eval "zfs send -L -i $src@snap $src@snap2 | zfs receive -F $second"
log_pass "Feature activation propagated successfully."

View File

@ -0,0 +1,100 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0
#
# 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.
#
#
# Copyright (c) 2026 by Austin Wise. All rights reserved.
#
. $STF_SUITE/tests/functional/rsend/rsend.kshlib
. $STF_SUITE/include/properties.shlib
#
# Description:
# Ensures that sending a snapshot with the large microzap feature active propagates
# the feature activation to the receiving pool. Also checks that the received snapshot also
# correctly propagates the feature activation when sent to a third third pool.
# Regression test for https://github.com/openzfs/zfs/issues/18143
#
# Strategy:
# 1. Enable the large microzap feature in the source dataset.
# 2. Create a snapshot.
# 3. Send the snapshot from the first pool to a second pool.
# 4. Send the snapshot from the second pool to a third pool.
# 5. Verify that all pools have the large microzap feature active.
#
verify_runnable "both"
verify_disk_count "$DISKS" 3
log_assert "Verify incremental receive handles inactive large_blocks feature correctly."
function cleanup
{
restore_tunable ZAP_MICRO_MAX_SIZE
cleanup_pool $POOL
cleanup_pool $POOL2
cleanup_pool $POOL3
}
function assert_feature_state {
typeset pool=$1
typeset expected_state=$2
typeset actual_state=$(zpool get -H -o value feature@large_microzap $pool)
log_note "Zpool $pool feature@large_microzap=$actual_state"
if [[ "$actual_state" != "$expected_state" ]]; then
log_fail "pool $pool feature@large_microzap=$actual_state (expected '$expected_state')"
fi
}
typeset src=$POOL/src
typeset second=$POOL2/second
typeset third=$POOL3/third
log_onexit cleanup
# Allow micro ZAPs to grow beyond SPA_OLD_MAXBLOCKSIZE.
set_tunable64 ZAP_MICRO_MAX_SIZE 1048576
# Ensure the third pool exists.
datasetexists $POOL3 || log_must zpool create $POOL3 $DISK3
# Create a dataset with a large recordsize (1MB)
log_must zfs create -o recordsize=1M $src
typeset mntpnt=$(get_prop mountpoint $src)
# Activate the large_microzap feature by creating a micro ZAP that is larger than SPA_OLD_MAXBLOCKSIZE (128k)
# but smaller than MZAP_MAX_SIZE (1MB). Each micro ZAP entry is 64 bytes (MZAP_ENT_LEN),
# so 4096 files is about 256k.
log_must eval "seq 1 4096 | xargs -I REPLACE_ME touch $mntpnt/REPLACE_ME"
log_must zpool sync $POOL
# Assert initial state of pools
assert_feature_state $POOL "active"
assert_feature_state $POOL2 "enabled"
assert_feature_state $POOL3 "enabled"
# Create initial snapshot and send to second pool.
log_must zfs snapshot $src@snap
log_must eval "zfs send -p -L $src@snap | zfs receive $second"
log_must zpool sync $POOL2
assert_feature_state $POOL2 "active"
# Send to third pool from second. This ensures that the second pool correctly recorded that that
# large_microzap feature was active when it received the first send stream.
log_must eval "zfs send -p -L $second@snap | zfs receive $third"
log_must zpool sync $POOL3
assert_feature_state $POOL3 "active"
log_pass "Feature activation propagated successfully."