zfs_dedupe_range_copy_memcmp() livelocks if zfs_vnops_read_chunk_size is set to 0
System information
| Type | Version |
|---|---|
| Operating System | any |
| ZFS Version | master @ aa26ca67b |
Describe the problem
zfs_dedupe_range_copy_memcmp() in module/zfs/zfs_vnops.c computes its copy
chunk size without a lower bound (line ~2128):
uint64_t chunk = MIN(len, (uint64_t)zfs_vnops_read_chunk_size);zfs_vnops_read_chunk_size is registered as a ZMOD_RW U64 tunable
(zfs_vnops.c:2945) with no param_set validation, so it can be set to 0
at runtime via /sys/module/zfs/parameters/zfs_vnops_read_chunk_size. With
chunk == 0 the compare loop cannot make progress:
n = MIN(len, chunk)is always0,dmu_read()of 0 bytes succeeds,memcmp()of 0 bytes reports equality,len -= 0never advances,
so the calling kernel thread spins forever (only issig() can break it out).
This is a livelock on the FIDEDUPERANGE / dedupe path.
The two sibling users of the same tunable in the same file both clamp it:
zfs_read()(line ~429):chunk_size = MIN(MAX(zfs_vnops_read_chunk_size, blksz), DMU_MAX_ACCESS / 2);zfs_dedupe_range_memcmp()(line ~2242):chunk = MAX(1, MIN(zfs_vnops_read_chunk_size, DMU_MAX_ACCESS / 2) / blksz) * blksz;
zfs_dedupe_range_copy_memcmp() is the fallback taken for files with a
non-power-of-2 recordsize (dn_datablkshift == 0) whose compare range
extends past the last (partial) block, so it is rare but reachable.
Describe how to reproduce
echo 0 > /sys/module/zfs/parameters/zfs_vnops_read_chunk_size- Create a dataset with a non-power-of-2
recordsizeand a file whose compare range extends beyond the end of the last (partial) block, so the dbuf-basedzfs_dedupe_range_memcmp()path cannot be used - Issue
FIDEDUPERANGEon that range; the calling thread livelocks
Suggested fix
Clamp the tunable the same way the siblings do:
uint64_t chunk = MAX(1, MIN(len, (uint64_t)zfs_vnops_read_chunk_size));Found while reviewing the FIDEDUPERANGE work (afb401032 and follow-ups). Happy to send a PR.
Source: openzfs/zfs