Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decoder::decode() says an Err informs Framed that the stream should be terminated, but Framed doesn't terminate #3976

Closed
lilyball opened this issue Jul 20, 2021 · 3 comments
Labels
A-tokio-util Area: The tokio-util crate C-bug Category: This is a bug. E-help-wanted Call for participation: Help is requested to fix this issue. E-medium Call for participation: Experience needed to fix: Medium / intermediate M-codec Module: tokio-util/codec

Comments

@lilyball
Copy link

Version
tokio-util v0.6.7

Platform
Darwin My-Computer 20.5.0 Darwin Kernel Version 20.5.0: Sat May 8 05:10:33 PDT 2021; root:xnu-7195.121.3~9/RELEASE_X86_64 x86_64

Description
tokio_util::codec::FramedRead will happily continue trying to decode a stream after the associated tokio_util::codec::Decoder implementation returns Err from Decoder::decode() or Decoder::decode_eof(). This happens in spite of the documentation on Decoder::decode():

Finally, if the bytes in the buffer are malformed then an error is returned indicating why. This informs Framed that the stream is now corrupt and should be terminated.

(decode_eof() does not document the semantics of returning an error but it's safe to assume it's the same as decode()).

Looking at <FramedImpl as Stream>::poll_next(), it returns the Err to its caller without modifying its state:

let frame = pinned.codec.decode_eof(&mut state.buffer)?;
if frame.is_none() {
state.is_readable = false; // prepare pausing -> paused
}
// implicit pausing -> pausing or pausing -> paused
return Poll::Ready(frame.map(Ok));
}
// framing
trace!("attempting to decode a frame");
if let Some(frame) = pinned.codec.decode(&mut state.buffer)? {

This means that if the decode() method returns an Err without consuming any bytes from the buffer, a simple loop like the following will go into a tight infinite loop:

while let Some(result) = framed.next().await {
    process(result);
}

And if a Decoder impl can return recoverable errors, and does so from the decode() method instead of moving them into the Item, then that lends itself to the following loop:

while let Some(result) = framed.next().await {
    match result {
        Ok(val) => process(val),
        Err(e) => warn!(log, "error decoding stream"; "error" => %e),
    }
}

It's really easy to write this loop, especially if you haven't carefully read all of the associated documentation, and you won't realize what happened until you hit a nonrecoverable error.

If Framed actually did what the Decoder::decode() documentation suggests and moved to a terminal state upon encountering an Err, then these loops would instantly go from an infinite loop to one that logs an error and terminates. This would also strongly encourage Decoder impls that can return recoverable errors to make that distinction properly in its return type.

Optionally, if someone is using a non-conforming Decoder impl that mixes recoverable and non-recoverable errors together and the calling code wants to intelligently handle that, Framed could offer a method to recover from the terminal state.

This behavior change should probably be considered a breaking change, but I could make the argument that it's a bugfix (makes behavior conform to documentation) and that it's an important safety improvement for existing code (turns infinite loops into finite ones).

@lilyball lilyball added A-tokio Area: The main tokio crate C-bug Category: This is a bug. labels Jul 20, 2021
@Darksonn Darksonn added A-tokio-util Area: The tokio-util crate M-codec Module: tokio-util/codec and removed A-tokio Area: The main tokio crate labels Jul 21, 2021
@Darksonn
Copy link
Contributor

It seems reasonable that it should return None after an error.

@Darksonn Darksonn added E-help-wanted Call for participation: Help is requested to fix this issue. E-medium Call for participation: Experience needed to fix: Medium / intermediate labels Sep 19, 2021
@bIgBV
Copy link
Contributor

bIgBV commented Oct 11, 2021

@Darksonn I can take a look at this issue.

@Darksonn
Copy link
Contributor

Fixed in #4166

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 C-bug Category: This is a bug. E-help-wanted Call for participation: Help is requested to fix this issue. E-medium Call for participation: Experience needed to fix: Medium / intermediate M-codec Module: tokio-util/codec
Projects
None yet
Development

No branches or pull requests

3 participants