Skip to content

dedupe: complete a request that deduped nothing, rather than re-queueing it - #416

Open
matthiasgoergens wants to merge 1 commit into
markfasheh:masterfrom
matthiasgoergens:guard-zero-progress
Open

dedupe: complete a request that deduped nothing, rather than re-queueing it#416
matthiasgoergens wants to merge 1 commit into
markfasheh:masterfrom
matthiasgoergens:guard-zero-progress

Conversation

@matthiasgoergens

Copy link
Copy Markdown

Fixes #415.

process_dedupes() re-queues a request whenever the ioctl succeeded and req_total has not reached orig_len. A successful FIDEDUPERANGE reporting zero bytes therefore resubmits an identical request forever.

-		if (info->status || req->req_total >= ctxt->orig_len) {
+		if (info->status || req->req_total >= ctxt->orig_len ||
+		    !info->bytes_deduped) {

req_total already holds what was really deduplicated, so completing the request reports the correct figure to the caller rather than spinning.

This is a workaround for a kernel bug rather than a live failure in duperemove: vfs_dedupe_file_range() reports the requested length instead of the amount actually deduplicated, so bytes_deduped is currently never zero when status is 0. Measured on a kernel with that fixed (btrfs, v7.2-rc5 in a VM), a sub-block request away from EOF returns bytes_deduped=0, status=0, and a plain rmlint --dedupe elicits exactly that in ordinary use. duperemove escapes today only because set_aligned_same_length() always rounds down to fs_blocksize — I traced a -dr run and a --dedupe-options=partial -b 4096 run and every request was aligned or reached EOF.

Worth having regardless of what the kernel does, since duperemove will keep running on kernels without the fix — every LTS since 4.19 over-reports.

No behaviour change on current kernels: builds clean, and a -dr run over a mixed corpus produces identical output to master.

…ing it

process_dedupes() advances req_loff and req_total by info->bytes_deduped and
re-queues the request whenever the ioctl succeeded and req_total has not yet
reached orig_len. If a successful FIDEDUPERANGE ever reports zero bytes
deduped, that re-queues a request with identical parameters, and the loop in
dedupe_extents() never terminates.

The kernel cannot produce that today: vfs_dedupe_file_range() reports the
requested length rather than the amount actually deduped, so bytes_deduped is
non-zero whenever status is 0. That is itself a bug -- generic_remap_check_len()
silently shortens an unaligned length away from EOF, and the loop records
"len" where "deduped" holds the real figure. It was fixed in 4a57a8400075 and
reverted the next day in b926f2adb044, partly out of concern for how userspace
would react to a truthful zero.

So this guard is unreachable at present and is deliberately defensive. It
costs one condition, and it means duperemove is not the reason the kernel
side stays unfixed.

req_total already holds what was really deduped, so completing the request
reports the correct figure to the caller rather than looping.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

process_dedupes() can loop forever if a successful FIDEDUPERANGE reports zero bytes

1 participant