Skip to content

Commit 8ce0596

Browse files
committed
ZLFS: add a concurrent-writers stress regress
zlfs-parallel.sh runs several writers in parallel, each rewriting and syncing its own file (sizes spanning direct, single- and double-indirect ranges; half also splice mid-file), while a background loop churns junk to drive GC and zone compaction at the same time. The concurrent sync(2)s contend in the commit's per-inode trylock/retry path -- the first real multi-process exercise of the concurrency-safe commit. Each file is compared against a fixed template after the run and after a remount, and a keeper guards against the cleaner eating live data under load.
1 parent ffde8bc commit 8ce0596

1 file changed

Lines changed: 154 additions & 0 deletions

File tree

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
# ZLFS concurrent-writers stress test.
5+
#
6+
# Runs several writers in parallel, each repeatedly rewriting and
7+
# syncing its OWN file (mixed sizes spanning direct, single- and
8+
# double-indirect ranges; two also splice mid-file, exercising the
9+
# read-modify-write overlay), while a background loop churns junk to
10+
# force garbage collection and zone compaction at the same time. The
11+
# concurrent sync(2)s contend in the commit's per-inode trylock/retry
12+
# path and the compactor runs from the sync path under live load.
13+
#
14+
# Each writer's file is compared byte-for-byte against a fixed template
15+
# after the run and again after a remount; a keeper file written up
16+
# front guards against the cleaner eating live data under contention.
17+
#
18+
# Sized for the ~8 GB QEMU ZNS test device; on a much smaller disk the
19+
# concurrent churn can win the space race and a writer may see a
20+
# transient ENOSPC before GC catches up.
21+
#
22+
# WARNING: this reformats the given disk with newfs_zlfs.
23+
24+
usage()
25+
{
26+
echo "usage: $0 disk [mount-point [writers [iters]]]" >&2
27+
echo "example: $0 sd1c /mnt/zlfs 8 40" >&2
28+
echo "warning: this reformats the disk with newfs_zlfs" >&2
29+
exit 1
30+
}
31+
32+
die()
33+
{
34+
echo "$0: $*" >&2
35+
exit 1
36+
}
37+
38+
section()
39+
{
40+
echo
41+
echo "== $* =="
42+
}
43+
44+
disk=${1-}
45+
mnt=${2-/mnt/zlfs}
46+
writers=${3-8}
47+
iters=${4-40}
48+
49+
[ -n "$disk" ] && [ $# -le 4 ] || usage
50+
[ "$(id -u)" -eq 0 ] || die "must run as root"
51+
52+
dev=/dev/$disk
53+
tdir=/tmp/zlfs-par.$$
54+
mkdir -p "$tdir"
55+
trap 'rm -rf "$tdir"' EXIT
56+
57+
# Per-writer template block counts (4 KB blocks): a spread across the
58+
# direct (<12), single-indirect (<524) and double-indirect ranges, so
59+
# the concurrent commits build and rewrite indirect trees too.
60+
sizes="4 20 200 600 1500 40 300 4096"
61+
62+
section "newfs + mount ($writers writers, $iters iters each)"
63+
umount "$mnt" 2>/dev/null || true
64+
newfs_zlfs "$disk"
65+
mkdir -p "$mnt"
66+
mount_zlfs "$dev" "$mnt"
67+
68+
section "keeper file (must survive the whole run)"
69+
dd if=/dev/random of="$mnt/keeper" bs=4k count=100 2>/dev/null
70+
sync
71+
kcsum=$(cksum < "$mnt/keeper")
72+
73+
section "build $writers templates"
74+
w=0
75+
while [ "$w" -lt "$writers" ]; do
76+
cnt=$(echo "$sizes" | cut -d' ' -f$((w + 1)))
77+
[ -n "$cnt" ] || cnt=64
78+
dd if=/dev/random of="$tdir/t$w" bs=4k count="$cnt" 2>/dev/null
79+
w=$((w + 1))
80+
done
81+
82+
# A background junk churn to keep GC and compaction busy under load.
83+
# Create the run flag BEFORE forking so the child cannot test it first
84+
# and exit immediately (which would silently drop the GC pressure).
85+
mkdir "$mnt/churn"
86+
: > "$tdir/churn_on"
87+
(
88+
while [ -f "$tdir/churn_on" ]; do
89+
dd if=/dev/zero of="$mnt/churn/j" bs=4k count=12000 \
90+
2>/dev/null || true
91+
sync 2>/dev/null || true
92+
rm -f "$mnt/churn/j"
93+
sync 2>/dev/null || true
94+
done
95+
) &
96+
churn_pid=$!
97+
98+
section "launch $writers concurrent writers"
99+
pids=""
100+
w=0
101+
while [ "$w" -lt "$writers" ]; do
102+
(
103+
i=0
104+
while [ "$i" -lt "$iters" ]; do
105+
cp "$tdir/t$w" "$mnt/f$w"
106+
# Half the writers splice mid-file (RMW), then
107+
# restore, so the file always ends == template.
108+
if [ $((w % 2)) -eq 1 ]; then
109+
printf 'XYZ' | dd of="$mnt/f$w" bs=1 seek=17 \
110+
conv=notrunc 2>/dev/null
111+
cp "$tdir/t$w" "$mnt/f$w"
112+
fi
113+
sync
114+
i=$((i + 1))
115+
done
116+
) &
117+
pids="$pids $!"
118+
w=$((w + 1))
119+
done
120+
121+
section "wait for writers"
122+
fail=0
123+
for p in $pids; do
124+
wait "$p" || fail=1
125+
done
126+
rm -f "$tdir/churn_on"
127+
wait "$churn_pid" 2>/dev/null || true
128+
rm -f "$mnt/churn/j"
129+
rmdir "$mnt/churn" 2>/dev/null || true
130+
sync
131+
[ "$fail" -eq 0 ] || die "a writer exited non-zero"
132+
echo " ok: all $writers writers finished, no ENOSPC/errors"
133+
134+
section "every file matches its template?"
135+
w=0
136+
while [ "$w" -lt "$writers" ]; do
137+
cmp "$tdir/t$w" "$mnt/f$w" || die "f$w differs from its template"
138+
w=$((w + 1))
139+
done
140+
[ "$(cksum < "$mnt/keeper")" = "$kcsum" ] || die "keeper changed under load"
141+
echo " ok: all $writers files and the keeper intact"
142+
143+
section "everything intact after remount?"
144+
umount "$mnt"
145+
mount_zlfs "$dev" "$mnt"
146+
w=0
147+
while [ "$w" -lt "$writers" ]; do
148+
cmp "$tdir/t$w" "$mnt/f$w" || die "f$w differs after remount"
149+
w=$((w + 1))
150+
done
151+
[ "$(cksum < "$mnt/keeper")" = "$kcsum" ] || die "keeper lost after remount"
152+
echo " ok: all files and the keeper survive remount"
153+
154+
section "PASS"

0 commit comments

Comments
 (0)