Skip to content

Fix receive of split large blocks with a short trailing chunk#18749

Draft
MorganaFuture wants to merge 1 commit into
openzfs:masterfrom
MorganaFuture:recv-largeblock-split-tail
Draft

Fix receive of split large blocks with a short trailing chunk#18749
MorganaFuture wants to merge 1 commit into
openzfs:masterfrom
MorganaFuture:recv-largeblock-split-tail

Conversation

@MorganaFuture

Copy link
Copy Markdown

Motivation and Context

Closes #17829.

A dataset with a large recordsize can store a single-block file whose block
size is not a power of two (a single-block object's dblksz is rounded up to a
512-byte multiple, e.g. a 300000-byte file gets a 300032-byte block). Sending
such a file without large blocks (no -L) makes the sender split the block
into SPA_OLD_MAXBLOCKSIZE (128K) chunks, and the final chunk is shorter than
the block size.

Receiving that stream panics receive_writer on a debug build (the reporter's
6-line reproducer: recordsize=1m, one 300000-byte file, zfs send without
-L piped to zfs recv), wedging the receive in an unkillable D state.

Description

In flush_write_batch_impl(), a WRITE record whose logical size differs from
the object's block size is written with a normal dmu_write(). The code first
asserted that such a record is always larger than the block size:

ASSERT3U(drrw->drr_logical_size, >, dn->dn_datablksz);

But the short trailing chunk above is smaller than the block size (the object
is created with the clamped 128K block size, while the tail chunk is e.g.
37888 bytes), so the assertion fails. Production builds compile the assertion
out and already take the correct dmu_write() path, which handles a partial
final block; only debug builds panic.

Drop the assertion and reword the comment to describe both size-mismatch cases.
No production code path changes.

How Has This Been Tested?

  • Reproduced the receive_writer D-state hang on current master (debug build,
    Ubuntu aarch64 VM); confirmed the stack sits in flush_write_batch.
  • With the fix, the same send/recv succeeds and the received file matches the
    source byte-for-byte, for plain, compressed, and incremental sends.
  • New rsend/send_split_large_block test (guards the non-power-of-2 block via
    zdb, then sends without -L and diffs) passes; send-L_toggle,
    send-cpL_varied_recsize, send_large_blocks_initial and
    send_large_blocks_incremental still pass — no regression.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Quality assurance (non-breaking change which makes the code more robust against bugs)

Checklist:

  • My code follows the OpenZFS code style requirements.
  • I have updated the documentation accordingly.
  • I have read the contributing document.
  • I have added tests to cover my changes.
  • I have run the ZFS Test Suite with this change applied.
  • All commit messages are properly formatted and contain Signed-off-by.

@github-actions github-actions Bot added the Status: Work in Progress Not yet ready for general review label Jul 5, 2026
A dataset with a large recordsize can store a single-block file whose
block size is not a power of two.  When such a block is sent without
large blocks (no -L), the sender splits it into SPA_OLD_MAXBLOCKSIZE
(128K) chunks, and the final chunk is smaller than the block size.

flush_write_batch_impl() already handles any WRITE record whose size
differs from the object's block size by doing a normal dmu_write(), but
it first asserted the record was always larger than the block size.  The
shorter trailing chunk violates that assertion, so receiving such a
stream panicked the receive_writer thread on debug builds; production
builds took the correct dmu_write() path and were unaffected.

Drop the assertion and describe both size-mismatch cases in the comment;
the dmu_write() path already handles a record smaller than the block
size.  Add an rsend test that sends such a block without -L (initial and
incremental, plain and compressed) and verifies the received file
matches.

Signed-off-by: MorganaFuture <103630661+MorganaFuture@users.noreply.github.com>
Closes openzfs#17829
@MorganaFuture MorganaFuture force-pushed the recv-largeblock-split-tail branch from 0229f82 to 07aac89 Compare July 5, 2026 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status: Work in Progress Not yet ready for general review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

zfs send with large blocks is unreliable / data corruption

1 participant