bytes
bytes copied to clipboard
0.5 release prevents going back from Bytes to BytesMut
Is the (new) inability to roundtrip between Bytes and BytesMut intentional? The inability to do so somewhat diminishes the usefulness of the reference counting feature this crate offers, if it means that freeze() renders a chunk of memory permanently immutable with no way to reclaim borrowed ranges and start over again without reallocating.
I understand that Bytes is (now) unaware of the existence of BytesMut at this time, but it should still be possible to update the BytesMut api to recover memory whose ref count has reached zero. I was all set to patch BytesMut to add a version of freeze() that looked like this:
pub fn freeze() -> (Bytes, Unfreeze)
where Unfreeze is a struct with a single function
pub fn unfreeze(self) -> BytesMut
with Unfreeze itself containing a weak reference to the same shared memory used by Bytes instances, but then I discovered that Bytes doesn't use Arc but rather a custom implementation that does not support weak references.
(The reason for having BytesMut return an Unfreeze rather than adding, e.g., an unfreeze() /try_mut() method to Bytes is that the latter would require Bytes keeping track of whether it was instantiated from read-only memory or not, which the current vtable mask isn't capable of distinguishing between.)
(My use case involves reading some bytes into a buffer, sending the ref-counted read bytes to another thread for processing, then reclaiming them when an event is set (and the Bytes have been dropped) to reuse the buffer for the next cycle.)
Are you dropping the BytesMut, and wishing to get one back from the Bytes you now have? Or are you splitting off frozen Bytes while still retaining the BytesMut?
If the latter, you can just try to reserve space on it, and if all frozen Bytes have been dropped, the BytesMut can notice and reuse its memory.
Thanks for the quick reply.
Are you dropping the
BytesMut, and wishing to get one back from theBytesyou now have? Or are you splitting off frozenByteswhile still retaining theBytesMut?
I can structure it either way, but I was originally thinking the latter so that the original buffer size is a hard guarantee (to avoid all allocations).
If the latter, you can just try to reserve space on it, and if all frozen
Byteshave been dropped, theBytesMutcan notice and reuse its memory.
With a possible exception of the case where the BytesMut write pointer is pointing to the very beginning of the remainder after a split, this would necessarily imply either a) result in fragmentation of a once-contiguous underlying buffer (as bytes mapped to memory range 0..m now are being reused after range m..n) or else b) it would not be a zero-cost operation if the contiguous range is detected, the disjoint buffers unified, and the previous contents of the BytesMut post-split are then copied/moved to the beginning of the reclaimed range?
Even if fragmentation or lack of contiguity were not an issue, I think there would be merit in offering an explicit API, if only for a developer's peace of mind?
If the latter, you can just try to reserve space on it, and if all frozen
Byteshave been dropped, theBytesMutcan notice and reuse its memory.
I stumbled across this from Tokio Framed, or at least I believe this is the right thread.
AFAICT BytesMut can't be reused because freeze() consumes the BytesMut. I couldn't find another way to get a Bytes from BytesMut.
I'd expect to be able to do something like the following:
let mut my_bytes = BytesMut::with_capacity(999);
write_stuff(&mut my_bytes);
send(my_bytes.to_bytes()).await?;
my_bytes.clear();
write_stuff2(&mut my_bytes);
send(my_bytes.to_bytes()).await?;
...
I could do .split().freeze() but AFAICT there's no way to unsplit() the frozen bytes afterwards.
Edit:
Is it the case that if you drop the frozen split bytes and don't write anything new to the original bytesmut in the interim, the original bytesmut will be able to reuse the memory? Is BytesMut less like a byte array and more like a pool of byte arrays?
Edit edit: Per https://github.com/tokio-rs/bytes/pull/435 it looks like there's legitimately no way to reuse allocated memory. TBH for my use case I'm not sure anything more than a Vec is really required (Arc, etc) so maybe I should look for something other than LengthDelimitedCodec which appears to be what made the decision to use Bytes here.
@andrewbaxter If the reference count is equal to one (i.e. all other split Bytes/BytesMut have been dropped) when the BytesMut tries to reallocate, then it will reuse the entire allocation even if you have previously done a split+freeze.
Ah, thanks for the quick reply! Okay, so that usage is idiomatic.
I think my confusion arose basically from crate documentation that says This is managed by using a reference count to track when the memory is no longer needed and can be freed. wrt Bytes, but doesn't mention writing or BytesMut and I would expect those to have different constraints. Since Bytes is RO, I thought the reference counting was for zero copy parsing, rather than reusing write buffers.
Reading deeper into the BytesMut documentation I guess when Bytes is obtained from BytesMut both Bytes and BytesMut are views of a single underlying data store, and the reference count in Bytes in this case is actually the reference count on the data store shared by BytesMut rather than a Bytes-specific reference count.
So this would be the ideal usage for write/read/reuse:
let mut my_bytes = BytesMut::with_capacity(999);
write_stuff(&mut my_bytes);
send(my_bytes.split().freeze()).await?;
write_stuff2(&mut my_bytes);
send(my_bytes.split().freeze()).await?;
Indeed, a single allocation can contain any number of BytesMut and Bytes pointing into it as long as each BytesMut does not overlap with the range of any other handle.