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

[Bug] Infinite loop in polling function of Forward #2552

Open
jerry73204 opened this issue Jan 15, 2022 · 1 comment
Open

[Bug] Infinite loop in polling function of Forward #2552

jerry73204 opened this issue Jan 15, 2022 · 1 comment
Labels
A-task Area: futures::task bug

Comments

@jerry73204
Copy link

Here describes an scenario that hangs stream.forward(sink) in the polling function of Forward (code). The code below is simplified version of the polling function. It goes back and forth between part A and B in the case that the stream always returns an item when being polled, and sink is always ready to receive an item. There is no chance to escape the loop.

loop {
    if buffered_item.is_some() {
        // part A
        ready!(si.as_mut().poll_ready(cx))?;  // return here if pending or error
        si.as_mut().start_send(buffered_item.take().unwrap())?;
    }

    match stream.as_mut().poll_next(cx) {
        Poll::Ready(Some(item)) => {
            // part B
            *buffered_item = Some(item);
        }
        /* return in other cases */
    }
}

A simple experiment can be done in the example below, in which the polling of future fut1 never ends. The future fut2 has no chance to be executed.

let fut1 = stream::repeat(()).map(Ok).forward(futures::sink::drain());
let fut2 = async {
    rt::sleep(Duration::from_secs(1)).await;
    dbg!();  // never print this.
};
futures::join!(fut1, fut2);

A simple fix is to remove the loop and call cx.waker().wake_by_ref() to request polling next time. It slightly adds up runtime cost but effectively eliminates the hanging issue.

if buffered_item.is_some() {
    // part A
    ready!(si.as_mut().poll_ready(cx))?;  // return here if pending or error
    si.as_mut().start_send(buffered_item.take().unwrap())?;
}

match stream.as_mut().poll_next(cx) {
    Poll::Ready(Some(item)) => {
        // part B
        *buffered_item = Some(item);

        // Fix: wake itself and return
        cx.waker().wake_by_ref();
        return Pending;
     }
    /* return in other cases */
}
hecrj added a commit to iced-rs/iced that referenced this issue May 27, 2022
`StreamExt::forward` will keep polling a ready `Stream` in a loop. If the
`Stream` is always ready, the `poll` method of `Forward` effectively
blocks (see rust-lang/futures-rs#2552).

The fix consists in manually implementing a simpler version of `Forward`.
hecrj added a commit to iced-rs/iced that referenced this issue May 27, 2022
`StreamExt::forward` will keep polling a ready `Stream` in a loop. If the
`Stream` is always ready, the `poll` method of `Forward` effectively
blocks (see rust-lang/futures-rs#2552).

The fix consists in manually implementing a simpler version of `Forward`.
@taiki-e taiki-e added bug A-task Area: futures::task labels Jun 5, 2022
@taiki-e
Copy link
Member

taiki-e commented Jun 5, 2022

Thanks for the report. The way good than yield per poll to fix this is to limit iterations. See tokio-rs/tokio#3625 for more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-task Area: futures::task bug
Projects
None yet
Development

No branches or pull requests

2 participants