Conversation
Bytes::advance only increments the start pointer, so advancing all the way to the end leaves an empty Bytes that still holds a reference to the parent buffer. Because the length of a Bytes can never grow again, that reference can only keep the allocation alive without ever being useful, preventing reuse or reclamation. truncate(0) and split_to(self.len()) already replace self with an empty Bytes so the shared reference is dropped. Do the same in advance when cnt equals the current length.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
<Bytes as Buf>::advanceonly callsinc_start, which moves the start pointer and shrinkslenwithout touching the shared reference:bytes/src/bytes.rs
Lines 703 to 705 in 7930d93
bytes/src/bytes.rs
Lines 651 to 656 in 7930d93
So advancing all the way to the end leaves an empty
Bytesthat still holds a reference to the parent buffer. AByteslength can never grow again, so that reference can only keep the allocation alive without ever being useful. It blocks reuse and reclamation for as long as the empty handle is kept around, which is exactly the surprise reported in #840 forclear()/truncate(0).This is the sibling case @abonander pointed out in #840 (comment) while #840 was being fixed. The credit for spotting it is theirs; #842 fixed
truncate(0)and leftadvanceas it was.The inconsistency is now visible inside a single type:
truncate(0)replacesselfwith an emptyBytesand releases the reference (Release shared reference on zero truncate #842)split_to(self.len())replacesselfwith an emptyBytesand releases the referenceadvance(self.len())retains itThe fix mirrors what
split_to(self.len())already does: whencnt == self.len(), replaceselfwithBytes::new_empty_with_ptr(self.ptr.wrapping_add(cnt))and drop the old value. The non-empty path is untouched.Reproduction
Added as a regression test in
tests/test_bytes.rs, modelled on thetruncate_to_zero_releases_shared_referencetest from #842:On master that
unwrappanics withErr(b\"hello\"). With this change it succeeds, and the second half of the test pins the unchanged behaviour for a partial advance, where the remaining handle must still fail to upgrade.Verification
cargo test: full suite green, 1306 tests across all targets.cargo test --all-features: green.MIRIFLAGS=-Zmiri-strict-provenance cargo miri test --test test_bytes: 119 passed, 1 ignored, including the new test. The replacement pointer is one past the end, the same pointersplit_to(self.len())already constructs, andnew_empty_with_ptrstrips its provenance.cargo fmt --checkclean, andcargo clippy --all-targetsreports no new warnings.