Skip to content

Commit 7dc9b1a

Browse files
committed
ZTS: Add zvol_misc_fullblock
Verify that a synchronous write covering an aligned volblocksize block on a zvol is stored indirectly: full-block sync writes must increment the objset's zil_itx_indirect_count, sub-block writes must not, and a dedicated SLOG must keep even full-block writes in the log. Because this changes which writes are logged indirectly, the test also freezes a dedicated pool, writes full blocks, then exports and re-imports it so the WR_INDIRECT records are claimed and replayed; the zvol contents must be byte-for-byte identical afterwards. This guards the actual risk of the change: an indirectly logged block that cannot be read back at import. The test drives aligned block writes with dd oflag=direct, which is Linux-only, so it is recorded as an expected skip on FreeBSD. Exercises the fix for the zvol write amplification in #17677. Signed-off-by: MorganaFuture <103630661+MorganaFuture@users.noreply.github.com> Closes #17677 Signed-off-by: MorganaFuture <103630661+MorganaFuture@users.noreply.github.com>
1 parent a953fab commit 7dc9b1a

4 files changed

Lines changed: 170 additions & 2 deletions

File tree

tests/runfiles/common.run

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,8 +1166,9 @@ tests = ['zvol_cli_001_pos', 'zvol_cli_002_pos', 'zvol_cli_003_neg']
11661166
tags = ['functional', 'zvol', 'zvol_cli']
11671167

11681168
[tests/functional/zvol/zvol_misc]
1169-
tests = ['zvol_misc_002_pos', 'zvol_misc_hierarchy', 'zvol_misc_rename_inuse',
1170-
'zvol_misc_snapdev', 'zvol_misc_trim', 'zvol_misc_volmode', 'zvol_misc_zil']
1169+
tests = ['zvol_misc_002_pos', 'zvol_misc_fullblock', 'zvol_misc_hierarchy',
1170+
'zvol_misc_rename_inuse', 'zvol_misc_snapdev', 'zvol_misc_trim',
1171+
'zvol_misc_volmode', 'zvol_misc_zil']
11711172
tags = ['functional', 'zvol', 'zvol_misc']
11721173

11731174
[tests/functional/zvol/zvol_stress]

tests/test-runner/bin/zts-report.py.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ if sys.platform.startswith('freebsd'):
279279
'pool_checkpoint/checkpoint_indirect': ['FAIL', 12623],
280280
'resilver/resilver_restart_001': ['FAIL', known_reason],
281281
'zvol/zvol_misc/zvol_misc_volmode': ['FAIL', 16668],
282+
'zvol/zvol_misc/zvol_misc_fullblock': ['SKIP', na_reason],
282283
'bclone/bclone_crossfs_corner_cases': ['SKIP', cfr_cross_reason],
283284
'bclone/bclone_crossfs_corner_cases_limited':
284285
['SKIP', cfr_cross_reason],

tests/zfs-tests/tests/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2442,6 +2442,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
24422442
functional/zvol/zvol_misc/zvol_misc_005_neg.ksh \
24432443
functional/zvol/zvol_misc/zvol_misc_006_pos.ksh \
24442444
functional/zvol/zvol_misc/zvol_misc_fua.ksh \
2445+
functional/zvol/zvol_misc/zvol_misc_fullblock.ksh \
24452446
functional/zvol/zvol_misc/zvol_misc_hierarchy.ksh \
24462447
functional/zvol/zvol_misc/zvol_misc_rename_inuse.ksh \
24472448
functional/zvol/zvol_misc/zvol_misc_snapdev.ksh \
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#!/bin/ksh -p
2+
# SPDX-License-Identifier: CDDL-1.0
3+
#
4+
# CDDL HEADER START
5+
#
6+
# The contents of this file are subject to the terms of the
7+
# Common Development and Distribution License (the "License").
8+
# You may not use this file except in compliance with the License.
9+
#
10+
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11+
# or https://opensource.org/licenses/CDDL-1.0.
12+
# See the License for the specific language governing permissions
13+
# and limitations under the License.
14+
#
15+
# When distributing Covered Code, include this CDDL HEADER in each
16+
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17+
# If applicable, add the following below this CDDL HEADER, with the
18+
# fields enclosed by brackets "[]" replaced with your own identifying
19+
# information: Portions Copyright [yyyy] [name of copyright owner]
20+
#
21+
# CDDL HEADER END
22+
#
23+
24+
#
25+
# Copyright (c) 2026 by MorganaFuture. All rights reserved.
26+
#
27+
28+
. $STF_SUITE/include/libtest.shlib
29+
. $STF_SUITE/include/kstat.shlib
30+
. $STF_SUITE/tests/functional/zvol/zvol_common.shlib
31+
32+
#
33+
# DESCRIPTION:
34+
# A synchronous write that exactly covers an aligned volblocksize block
35+
# is stored indirectly (WR_INDIRECT), so the data reaches the pool only
36+
# once instead of being written to the ZIL and again at TXG commit.
37+
# This avoids the ZVOL write amplification reported in #17677.
38+
#
39+
# STRATEGY:
40+
# 1. Create a small-volblocksize ZVOL with sync=always and no SLOG.
41+
# 2. Full aligned block sync writes must increase zil_itx_indirect_count.
42+
# 3. Sub-block (partial) sync writes must NOT use indirect.
43+
# 4. Indirectly logged full-block writes must survive ZIL replay with
44+
# identical data (freeze, write, export, import).
45+
# 5. With a dedicated SLOG, even full block writes must NOT use indirect
46+
# (the SLOG latency optimization must be preserved).
47+
#
48+
49+
verify_runnable "global"
50+
51+
if ! is_linux ; then
52+
log_unsupported "Requires dd oflag=direct for aligned full-block writes"
53+
fi
54+
55+
typeset VOLBS=16384
56+
typeset COUNT=64
57+
typeset ZVOL="$TESTPOOL/vol"
58+
typeset ZDEV="$ZVOL_DEVDIR/$ZVOL"
59+
typeset slog="$(mktemp -p $TEST_BASE_DIR zvol_misc_fullblock_slog.XXXXXX)"
60+
61+
# A separate pool is frozen for the replay check so it cannot disturb
62+
# $TESTPOOL, which is shared with the rest of the zvol_misc group.
63+
typeset rpool="${TESTPOOL}_replay"
64+
typeset rvol="$rpool/vol"
65+
typeset rdev="$ZVOL_DEVDIR/$rvol"
66+
typeset rvdev="$(mktemp -p $TEST_BASE_DIR zvol_misc_fullblock_vdev.XXXXXX)"
67+
68+
function cleanup
69+
{
70+
datasetexists $ZVOL && destroy_dataset $ZVOL
71+
if zpool list -v $TESTPOOL | grep -q "$slog" ; then
72+
log_must zpool remove $TESTPOOL "$slog"
73+
fi
74+
poolexists $rpool && destroy_pool $rpool
75+
rm -f "$slog" "$rvdev"
76+
block_device_wait
77+
}
78+
79+
# Number of indirect ZIL records logged for the zvol's objset so far.
80+
function indirect_count
81+
{
82+
kstat_dataset $ZVOL zil_itx_indirect_count
83+
}
84+
85+
log_assert "Full aligned block sync writes to a ZVOL are stored indirectly"
86+
log_onexit cleanup
87+
88+
log_must zfs create -V $VOLSIZE -b $VOLBS -o sync=always \
89+
-o compression=off -o volmode=dev $ZVOL
90+
block_device_wait $ZDEV
91+
92+
# 1. Full aligned block sync writes: expect indirect writes.
93+
typeset before=$(indirect_count)
94+
log_must dd if=/dev/urandom of=$ZDEV bs=$VOLBS count=$COUNT \
95+
oflag=direct conv=fdatasync
96+
typeset after=$(indirect_count)
97+
typeset delta=$((after - before))
98+
log_note "full-block writes: indirect_count delta=$delta (expected ~$COUNT)"
99+
if [[ $delta -le $((COUNT / 2)) ]] ; then
100+
log_fail "Full block sync writes did not use indirect writes " \
101+
"($delta <= $((COUNT / 2)))"
102+
fi
103+
104+
# 2. Partial (sub-block) sync writes: must not use indirect.
105+
before=$(indirect_count)
106+
log_must dd if=/dev/urandom of=$ZDEV bs=$((VOLBS / 2)) count=$COUNT \
107+
oflag=direct conv=fdatasync
108+
after=$(indirect_count)
109+
delta=$((after - before))
110+
log_note "partial writes: indirect_count delta=$delta (expected 0)"
111+
if [[ $delta -ne 0 ]] ; then
112+
log_fail "Partial sync writes unexpectedly used indirect writes " \
113+
"($delta != 0)"
114+
fi
115+
116+
# 3. Indirectly logged full-block writes must replay with identical data.
117+
# A dedicated pool is frozen so the post-freeze writes reach disk only
118+
# through the ZIL; they survive an export/import only if the WR_INDIRECT
119+
# records and the blocks they reference are claimed and replayed
120+
# correctly. Using a private pool keeps the freeze from disturbing
121+
# $TESTPOOL.
122+
log_must truncate -s 512m "$rvdev"
123+
log_must zpool create $rpool "$rvdev"
124+
log_must zfs create -V 64m -b $VOLBS -o sync=always -o compression=off \
125+
-o volmode=dev $rvol
126+
block_device_wait $rdev
127+
# A pre-freeze sync write puts the ZIL header on disk so the post-freeze
128+
# records are reachable at import.
129+
log_must dd if=/dev/urandom of=$rdev bs=$VOLBS count=1 \
130+
oflag=direct conv=fdatasync
131+
log_must sync_pool $rpool
132+
log_must zpool freeze $rpool
133+
log_must dd if=/dev/urandom of=$rdev bs=$VOLBS count=$COUNT \
134+
oflag=direct conv=fdatasync
135+
typeset checksum=$(dd if=$rdev bs=$VOLBS count=$COUNT 2>/dev/null | \
136+
xxh128digest)
137+
log_note "Verify transactions to replay:"
138+
log_must zdb -iv $rvol
139+
block_device_wait $rdev
140+
log_must zpool export $rpool
141+
log_must zpool import -f -d $TEST_BASE_DIR $rpool
142+
block_device_wait $rdev
143+
log_must test -b $rdev
144+
typeset checksum1=$(dd if=$rdev bs=$VOLBS count=$COUNT 2>/dev/null | \
145+
xxh128digest)
146+
if [[ "$checksum1" != "$checksum" ]] ; then
147+
log_fail "Indirect ZIL replay corrupted data ($checksum1 != $checksum)"
148+
fi
149+
log_must destroy_pool $rpool
150+
151+
# 4. With a dedicated SLOG, full block writes must stay in the log.
152+
log_must truncate -s $MINVDEVSIZE "$slog"
153+
log_must zpool add $TESTPOOL log "$slog"
154+
before=$(indirect_count)
155+
log_must dd if=/dev/urandom of=$ZDEV bs=$VOLBS count=$COUNT \
156+
oflag=direct conv=fdatasync
157+
after=$(indirect_count)
158+
delta=$((after - before))
159+
log_note "full-block writes with SLOG: indirect_count delta=$delta (expected 0)"
160+
if [[ $delta -ne 0 ]] ; then
161+
log_fail "Full block writes bypassed the SLOG via indirect writes " \
162+
"($delta != 0)"
163+
fi
164+
165+
log_pass "Full aligned block sync writes to a ZVOL are stored indirectly"

0 commit comments

Comments
 (0)