Skip to content

Conversation

@Mrreadiness
Copy link

Implement from_parts() for FramedWrite and FramedRead.

Motivation

Thanks to #7566, FramedWrite and FramedRead can be transformed into FramedParts, but there is no way to create them back from parts, or to transform Framed into FramedRead or FramedWrite without losing progress in buffers.

My original problem was splitting Framed into both FramedRead and FramedWrite, to swap the codec for one of them with map_codec. I wish we could have something like into_split for Framed:

impl<T, U> Framed<T, U>
where
    T: AsyncRead + AsyncWrite,
    U: Clone,
{
    pub fn into_split(self) -> (FramedRead<ReadHalf<T>, U>, FramedWrite<WriteHalf<T>, U>) {
        let (r, w) = split(self.inner.inner);
        let read = FramedRead::from_parts(FramedParts {
            io: r,
            codec: self.inner.codec.clone(),
            read_buf: self.inner.state.read.buffer,
            write_buf: BytesMut::new(),
            _priv: (),
        });
        let write = FramedWrite::from_parts(FramedParts {
            io: w,
            codec: self.inner.codec,
            read_buf: BytesMut::new(),
            write_buf: self.inner.state.write.buffer,
            _priv: (),
        });
        return (read, write);
    }
}

But cloning of the codec doesn't feel right here. So, with from_parts, a user will have more control over the reader and writer reconstruction.

Solution

Implement from_parts() for FramedWrite and FramedRead, which builds those structures from FramedParts and preserves internal buffers.

@github-actions github-actions bot added the R-loom-util Run loom tokio-util tests on this PR label Dec 6, 2025
@ADD-SP ADD-SP added A-tokio-util Area: The tokio-util crate M-codec Module: tokio-util/codec labels Dec 7, 2025
Copy link
Member

@ADD-SP ADD-SP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would mind adding some tests for both from_parts and into_parts?

@ADD-SP ADD-SP added the S-waiting-on-author Status: awaiting some action (such as code changes) from the PR or issue author. label Dec 7, 2025
@Mrreadiness
Copy link
Author

@ADD-SP Thanks! I added tests as well.

I also noticed that FramedParts::new() forces the creation of parts with at least an Encoder codec. But it doesn't make much sense, because it could be used for Decoder without Encoder. Looks like it's just a historical mistake, and there was an attempt to remove this limitation (#5280), but it's a breaking change, so it was reverted.
That's why I added an alternative constructor: FramedParts::new_parts.

So, solving the task of splitting up Framed will look like:

let framed = Framed::from_parts(parts);
let FramedParts {
    io,
    read_buf,
    write_buf,
    ..
} = framed.into_parts();
let (io_read, io_write) = tokio::io::split(parts.io);
let mut read_parts = FramedParts::new_parts(io_read, Codec::new());
read_parts.read_buf = read_buf;
let framed_read = FramedRead::from_parts(read_parts);
let mut write_parts = FramedParts::new_parts(io_write, Codec::new());
write_parts.write_buf = write_buf;
let framed_write = FramedWrite::from_parts(read_parts);

It's far from perfect, but at least doable now.
If you see a better way to do such a split or even an ergonomic way to add it as a method of Framed, I will be happy to implement it.

@ADD-SP ADD-SP removed the S-waiting-on-author Status: awaiting some action (such as code changes) from the PR or issue author. label Dec 7, 2025
@ADD-SP
Copy link
Member

ADD-SP commented Dec 8, 2025

Thanks for the context! I'm considering deprecating the tokio_util::codec::FramedParts::new. What do you think? What should the new constructor be called? new_parts? new2? new_v2?

@Mrreadiness
Copy link
Author

I end up with new_parts. Sounds like not a great naming situation anyway.

};
}

const INITIAL_CAPACITY: usize = 8 * 1024;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inner: FramedImpl {
inner: parts.io,
codec: parts.codec,
state: parts.read_buf.into(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replicated the same behaviour as in Framed.
As I understood from the state machine, it's okay.

has_error - used in 2 cases:

  1. Error from "previous" Decoder. A user already saw this error and intentionally created a new Framed (probably with another Decoder) to try to decode again.
  2. Error from underlying IO. I'm not 100% sure, but I suppose we get the same error with the next read attempt. A user saw this IO error as well.

For eof, we should restore this knowledge with the next call to the underlying IO here. As I understood from AsyncRead docs, the expected behavior after reaching EOF is Ok(Ready) with 0 new bytes for all subsequent read attempts.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realised that these 2 flags are related to IO, and the user can change parts.io. So we don't have any other way to get this knowledge back, but to interact with IO itself.

@martin-g
Copy link
Member

martin-g commented Dec 8, 2025

What should the new constructor be called? new_parts? new2? new_v2?

How about new_unchecked() ?!
To make it more clear that U: Encoder<I> is not required.

@ADD-SP
Copy link
Member

ADD-SP commented Dec 9, 2025

How about new_unchecked() ?!

@martin-g The _unchecked suffix is usually for unsafe function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-tokio-util Area: The tokio-util crate M-codec Module: tokio-util/codec R-loom-util Run loom tokio-util tests on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants