Skip to content

Commit 4f180e0

Browse files
authored
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
1 parent 20f94ef commit 4f180e0

File tree

6 files changed

+215
-18
lines changed

6 files changed

+215
-18
lines changed

module/zfs/dmu_recv.c

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -997,24 +997,6 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx)
997997
numredactsnaps, tx);
998998
}
999999

1000-
if (featureflags & DMU_BACKUP_FEATURE_LARGE_MICROZAP) {
1001-
/*
1002-
* The source has seen a large microzap at least once in its
1003-
* life, so we activate the feature here to match. It's not
1004-
* strictly necessary since a large microzap is usable without
1005-
* the feature active, but if that object is sent on from here,
1006-
* we need this info to know to add the stream feature.
1007-
*
1008-
* There may be no large microzap in the incoming stream, or
1009-
* ever again, but this is a very niche feature and its very
1010-
* difficult to spot a large microzap in the stream, so its
1011-
* not worth the effort of trying harder to activate the
1012-
* feature at first use.
1013-
*/
1014-
dsl_dataset_activate_feature(dsobj, SPA_FEATURE_LARGE_MICROZAP,
1015-
(void *)B_TRUE, tx);
1016-
}
1017-
10181000
dmu_buf_will_dirty(newds->ds_dbuf, tx);
10191001
dsl_dataset_phys(newds)->ds_flags |= DS_FLAG_INCONSISTENT;
10201002

@@ -1044,6 +1026,26 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx)
10441026
newds->ds_feature[SPA_FEATURE_LONGNAME] = (void *)B_TRUE;
10451027
}
10461028

1029+
if (featureflags & DMU_BACKUP_FEATURE_LARGE_MICROZAP &&
1030+
!dsl_dataset_feature_is_active(newds, SPA_FEATURE_LARGE_MICROZAP)) {
1031+
/*
1032+
* The source has seen a large microzap at least once in its
1033+
* life, so we activate the feature here to match. It's not
1034+
* strictly necessary since a large microzap is usable without
1035+
* the feature active, but if that object is sent on from here,
1036+
* we need this info to know to add the stream feature.
1037+
*
1038+
* There may be no large microzap in the incoming stream, or
1039+
* ever again, but this is a very niche feature and its very
1040+
* difficult to spot a large microzap in the stream, so its
1041+
* not worth the effort of trying harder to activate the
1042+
* feature at first use.
1043+
*/
1044+
dsl_dataset_activate_feature(dsobj, SPA_FEATURE_LARGE_MICROZAP,
1045+
(void *)B_TRUE, tx);
1046+
newds->ds_feature[SPA_FEATURE_LARGE_MICROZAP] = (void *)B_TRUE;
1047+
}
1048+
10471049
/*
10481050
* If we actually created a non-clone, we need to create the objset
10491051
* in our new dataset. If this is a raw send we postpone this until

tests/runfiles/common.run

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,7 @@ tests = ['recv_dedup', 'recv_dedup_encrypted_zvol', 'rsend_001_pos',
998998
'send_spill_block', 'send_holds', 'send_hole_birth', 'send_mixed_raw',
999999
'send-wR_encrypted_zvol', 'send_partial_dataset', 'send_invalid',
10001000
'send_large_blocks_incremental', 'send_large_blocks_initial',
1001+
'send_large_microzap_incremental', 'send_large_microzap_transitive',
10011002
'send_doall', 'send_raw_spill_block', 'send_raw_ashift',
10021003
'send_raw_large_blocks', 'send_leak_keymaps']
10031004
tags = ['functional', 'rsend']

tests/zfs-tests/include/tunables.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ BCLONE_WAIT_DIRTY bclone_wait_dirty zfs_bclone_wait_dirty
114114
DIO_ENABLED dio_enabled zfs_dio_enabled
115115
DIO_STRICT dio_strict zfs_dio_strict
116116
XATTR_COMPAT xattr_compat zfs_xattr_compat
117+
ZAP_MICRO_MAX_SIZE zap_micro_max_size zap_micro_max_size
117118
ZEVENT_LEN_MAX zevent.len_max zfs_zevent_len_max
118119
ZEVENT_RETAIN_MAX zevent.retain_max zfs_zevent_retain_max
119120
ZIO_SLOW_IO_MS zio.slow_io_ms zio_slow_io_ms

tests/zfs-tests/tests/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,8 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
20672067
functional/rsend/send_invalid.ksh \
20682068
functional/rsend/send_large_blocks_incremental.ksh \
20692069
functional/rsend/send_large_blocks_initial.ksh \
2070+
functional/rsend/send_large_microzap_incremental.ksh \
2071+
functional/rsend/send_large_microzap_transitive.ksh \
20702072
functional/rsend/send_leak_keymaps.ksh \
20712073
functional/rsend/send-L_toggle.ksh \
20722074
functional/rsend/send_mixed_raw.ksh \
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/bin/ksh -p
2+
# SPDX-License-Identifier: CDDL-1.0
3+
4+
#
5+
# This file and its contents are supplied under the terms of the
6+
# Common Development and Distribution License ("CDDL"), version 1.0.
7+
# You may only use this file in accordance with the terms of version
8+
# 1.0 of the CDDL.
9+
#
10+
# A full copy of the text of the CDDL should have accompanied this
11+
# source. A copy of the CDDL is also available via the Internet at
12+
# http://www.illumos.org/license/CDDL.
13+
#
14+
15+
#
16+
# Copyright (c) 2026 by Austin Wise. All rights reserved.
17+
#
18+
19+
. $STF_SUITE/tests/functional/rsend/rsend.kshlib
20+
. $STF_SUITE/include/properties.shlib
21+
22+
#
23+
# Description:
24+
# Ensure that it is possible to receive an incremental send of a snapshot that has the large microzap
25+
# feature active into a pool where the feature is already active.
26+
# Regression test for https://github.com/openzfs/zfs/issues/18143
27+
#
28+
# Strategy:
29+
# 1. Activate the large microzap feature in the source dataset.
30+
# 2. Create a snapshot.
31+
# 3. Send the snapshot from the first pool to a second pool.
32+
# 4. Create a second snapshot.
33+
# 5. Send the second snapshot incrementally to the second pool.
34+
#
35+
36+
verify_runnable "both"
37+
38+
log_assert "Verify incremental receive handles inactive large_blocks feature correctly."
39+
40+
function cleanup
41+
{
42+
restore_tunable ZAP_MICRO_MAX_SIZE
43+
cleanup_pool $POOL
44+
cleanup_pool $POOL2
45+
}
46+
47+
function assert_feature_state {
48+
typeset pool=$1
49+
typeset expected_state=$2
50+
51+
typeset actual_state=$(zpool get -H -o value feature@large_microzap $pool)
52+
log_note "Zpool $pool feature@large_microzap=$actual_state"
53+
if [[ "$actual_state" != "$expected_state" ]]; then
54+
log_fail "pool $pool feature@large_microzap=$actual_state (expected '$expected_state')"
55+
fi
56+
}
57+
58+
typeset src=$POOL/src
59+
typeset second=$POOL2/second
60+
61+
log_onexit cleanup
62+
63+
# Allow micro ZAPs to grow beyond SPA_OLD_MAXBLOCKSIZE.
64+
set_tunable64 ZAP_MICRO_MAX_SIZE 1048576
65+
66+
# Create a dataset with a large recordsize (1MB)
67+
log_must zfs create -o recordsize=1M $src
68+
typeset mntpnt=$(get_prop mountpoint $src)
69+
70+
# Activate the large_microzap feature by creating a micro ZAP that is larger than SPA_OLD_MAXBLOCKSIZE (128k)
71+
# but smaller than MZAP_MAX_SIZE (1MB). Each micro ZAP entry is 64 bytes (MZAP_ENT_LEN),
72+
# so 4096 files is about 256k.
73+
log_must eval "seq 1 4096 | xargs -I REPLACE_ME touch $mntpnt/REPLACE_ME"
74+
log_must zpool sync $POOL
75+
76+
# Assert initial state of pools
77+
assert_feature_state $POOL "active"
78+
assert_feature_state $POOL2 "enabled"
79+
80+
# Create initial snapshot and send to second pool.
81+
log_must zfs snapshot $src@snap
82+
log_must eval "zfs send -p -L $src@snap | zfs receive $second"
83+
log_must zpool sync $POOL2
84+
assert_feature_state $POOL2 "active"
85+
86+
# Create a second snapshot and send incrementally.
87+
# This ensures that the feature is not activated a second time, which would cause a panic.
88+
log_must zfs snapshot $src@snap2
89+
log_must eval "zfs send -L -i $src@snap $src@snap2 | zfs receive -F $second"
90+
91+
log_pass "Feature activation propagated successfully."
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/bin/ksh -p
2+
# SPDX-License-Identifier: CDDL-1.0
3+
4+
#
5+
# This file and its contents are supplied under the terms of the
6+
# Common Development and Distribution License ("CDDL"), version 1.0.
7+
# You may only use this file in accordance with the terms of version
8+
# 1.0 of the CDDL.
9+
#
10+
# A full copy of the text of the CDDL should have accompanied this
11+
# source. A copy of the CDDL is also available via the Internet at
12+
# http://www.illumos.org/license/CDDL.
13+
#
14+
15+
#
16+
# Copyright (c) 2026 by Austin Wise. All rights reserved.
17+
#
18+
19+
. $STF_SUITE/tests/functional/rsend/rsend.kshlib
20+
. $STF_SUITE/include/properties.shlib
21+
22+
#
23+
# Description:
24+
# Ensures that sending a snapshot with the large microzap feature active propagates
25+
# the feature activation to the receiving pool. Also checks that the received snapshot also
26+
# correctly propagates the feature activation when sent to a third third pool.
27+
# Regression test for https://github.com/openzfs/zfs/issues/18143
28+
#
29+
# Strategy:
30+
# 1. Enable the large microzap feature in the source dataset.
31+
# 2. Create a snapshot.
32+
# 3. Send the snapshot from the first pool to a second pool.
33+
# 4. Send the snapshot from the second pool to a third pool.
34+
# 5. Verify that all pools have the large microzap feature active.
35+
#
36+
37+
verify_runnable "both"
38+
verify_disk_count "$DISKS" 3
39+
40+
log_assert "Verify incremental receive handles inactive large_blocks feature correctly."
41+
42+
function cleanup
43+
{
44+
restore_tunable ZAP_MICRO_MAX_SIZE
45+
cleanup_pool $POOL
46+
cleanup_pool $POOL2
47+
cleanup_pool $POOL3
48+
}
49+
50+
function assert_feature_state {
51+
typeset pool=$1
52+
typeset expected_state=$2
53+
54+
typeset actual_state=$(zpool get -H -o value feature@large_microzap $pool)
55+
log_note "Zpool $pool feature@large_microzap=$actual_state"
56+
if [[ "$actual_state" != "$expected_state" ]]; then
57+
log_fail "pool $pool feature@large_microzap=$actual_state (expected '$expected_state')"
58+
fi
59+
}
60+
61+
typeset src=$POOL/src
62+
typeset second=$POOL2/second
63+
typeset third=$POOL3/third
64+
65+
log_onexit cleanup
66+
67+
# Allow micro ZAPs to grow beyond SPA_OLD_MAXBLOCKSIZE.
68+
set_tunable64 ZAP_MICRO_MAX_SIZE 1048576
69+
70+
# Ensure the third pool exists.
71+
datasetexists $POOL3 || log_must zpool create $POOL3 $DISK3
72+
73+
# Create a dataset with a large recordsize (1MB)
74+
log_must zfs create -o recordsize=1M $src
75+
typeset mntpnt=$(get_prop mountpoint $src)
76+
77+
# Activate the large_microzap feature by creating a micro ZAP that is larger than SPA_OLD_MAXBLOCKSIZE (128k)
78+
# but smaller than MZAP_MAX_SIZE (1MB). Each micro ZAP entry is 64 bytes (MZAP_ENT_LEN),
79+
# so 4096 files is about 256k.
80+
log_must eval "seq 1 4096 | xargs -I REPLACE_ME touch $mntpnt/REPLACE_ME"
81+
log_must zpool sync $POOL
82+
83+
# Assert initial state of pools
84+
assert_feature_state $POOL "active"
85+
assert_feature_state $POOL2 "enabled"
86+
assert_feature_state $POOL3 "enabled"
87+
88+
# Create initial snapshot and send to second pool.
89+
log_must zfs snapshot $src@snap
90+
log_must eval "zfs send -p -L $src@snap | zfs receive $second"
91+
log_must zpool sync $POOL2
92+
assert_feature_state $POOL2 "active"
93+
94+
# Send to third pool from second. This ensures that the second pool correctly recorded that that
95+
# large_microzap feature was active when it received the first send stream.
96+
log_must eval "zfs send -p -L $second@snap | zfs receive $third"
97+
log_must zpool sync $POOL3
98+
assert_feature_state $POOL3 "active"
99+
100+
log_pass "Feature activation propagated successfully."

0 commit comments

Comments
 (0)