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

Warning about unreachable code when combining tokio::select and async function that returns ! #2665

Closed
RAnders00 opened this issue Jul 16, 2020 · 4 comments
Labels
A-tokio Area: The main tokio crate C-bug Category: This is a bug. M-macros Module: macros in the main Tokio crate

Comments

@RAnders00
Copy link

Version
v0.2.21

rust:
stable-x86_64-pc-windows-gnu
rustc 1.44.1 (c7087fe00 2020-06-17)

Platform
Windows 10 1909 64-bit

Description
I have an async function that drives an infinite loop, and I declared it async and its return type is !.

Consider this minimal example:

use std::time::Duration;

async fn one() -> ! {
    loop {
        tokio::time::delay_for(Duration::from_secs(1)).await;
        println!("tick");
    }
}

async fn two() {
    tokio::time::delay_for(Duration::from_secs(3)).await;
}

#[tokio::main]
async fn main() {
    tokio::select! {
        _ = one() => {},
        _ = two() => {}
    };
    
    println!("finished.");
}

playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2924dce7469017c6534d72ab407bdf07

When compiling it, I get the following compiler warning:

warning: unreachable statement
  --> src/main.rs:16:5
   |
16 | /     tokio::select! {
17 | |         _ = one() => {},
18 | |         _ = two() => {}
19 | |     };
   | |      ^
   | |      |
   | |______unreachable statement
   |        any code following this `match` expression is unreachable, as all arms diverge
   |
   = note: `#[warn(unreachable_code)]` on by default
   = note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

I think the warning is silly because this is a perfectly valid way of using tokio::select!, to stop an infinite async task.
I would expect there to be no warning.

I dug a bit into it using cargo expand, and this is what the above example generated (removed/replaced some imports of std internals to make it compile):

use std::time::Duration;
async fn one() -> ! {
    loop {
        tokio::time::delay_for(Duration::from_secs(1)).await;
        println!("tick");
    }
}
async fn two() {
    tokio::time::delay_for(Duration::from_secs(3)).await;
}
fn main() {
    tokio::runtime::Builder::new()
        .basic_scheduler()
        .threaded_scheduler()
        .enable_all()
        .build()
        .unwrap()
        .block_on(async {
            {
                mod util {
                    pub(super) enum Out<_0, _1> {
                        _0(_0),
                        _1(_1),
                        Disabled,
                    }
                    pub(super) type Mask = u8;
                }

                use ::tokio::macros::support::Future;
                use ::tokio::macros::support::Pin;
                use ::tokio::macros::support::Poll::{Pending, Ready};
                const BRANCHES: u32 = 2;
                let mut disabled: util::Mask = Default::default();

                if !true {
                    let mask = 1 << 0;
                    disabled |= mask;
                }

                if !true {
                    let mask = 1 << 1;
                    disabled |= mask;
                }

                let mut output = {
                    let mut futures = (one(), two());
                    ::tokio::macros::support::poll_fn(|cx| {
                        let mut is_pending = false;
                        let start = ::tokio::macros::support::thread_rng_n(BRANCHES);
                        for i in 0..BRANCHES {
                            let branch = (start + i) % BRANCHES;
                            match branch {
                                0 => {
                                    let mask = 1 << branch;
                                    if disabled & mask == mask {
                                        continue;
                                    }
                                    let (fut, ..) = &mut futures;
                                    let mut fut = unsafe { Pin::new_unchecked(fut) };
                                    let out = match fut.poll(cx) {
                                        Ready(out) => out,
                                        Pending => {
                                            is_pending = true;
                                            continue;
                                        }
                                    };
                                    disabled |= mask;
                                    #[allow(unused_variables)]
                                    match &out {
                                        _ => {}
                                        _ => continue,
                                    }
                                    return Ready(util::Out::_0(out));
                                }
                                1 => {
                                    let mask = 1 << branch;
                                    if disabled & mask == mask {
                                        continue;
                                    }
                                    let (_, fut, ..) = &mut futures;
                                    let mut fut = unsafe { Pin::new_unchecked(fut) };
                                    let out = match fut.poll(cx) {
                                        Ready(out) => out,
                                        Pending => {
                                            is_pending = true;
                                            continue;
                                        }
                                    };
                                    disabled |= mask;
                                    #[allow(unused_variables)]
                                    match &out {
                                        _ => {}
                                        _ => continue,
                                    }
                                    return Ready(util::Out::_1(out));
                                }
                                _ => panic!(),
                            }
                        }
                        if is_pending {
                            Pending
                        } else {
                            Ready(util::Out::Disabled)
                        }
                    })
                    .await
                };
                match output {
                    util::Out::_0(_) => {}
                    util::Out::_1(_) => {}
                    util::Out::Disabled => panic!(),
                    _ => panic!(),
                }
            };

            println!("finished");
        })
}

playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=cbaf73e335a70623c88feaa64e4aa8a9

Where the error is now (on the expanded version):

warning: unreachable statement
  --> src\main.rs:67:37
   |
60 |   ...                   let out = match fut.poll(cx) {
   |  _________________________________-
61 | | ...                       Ready(out) => out,
62 | | ...                       Pending => {
63 | | ...                           is_pending = true;
64 | | ...                           continue;
65 | | ...                       }
66 | | ...                   };
   | |_______________________- any code following this `match` expression is unreachable, as all arms diverge
67 |   ...                   disabled |= mask;
   |                         ^^^^^^^^^^^^^^^^^ unreachable statement
   |
   = note: `#[warn(unreachable_code)]` on by default
@RAnders00 RAnders00 added A-tokio Area: The main tokio crate C-bug Category: This is a bug. labels Jul 16, 2020
@Darksonn Darksonn added M-macros Module: macros in the main Tokio crate E-help-wanted Call for participation: Help is requested to fix this issue. labels Jul 20, 2020
@blasrodri
Copy link
Contributor

Hi, @RAnders00 ! I've tried to follow your lead.

First, this is exactly what you wrote (I'm copying again to compare it)

use std::time::Duration;

async fn one() -> ! {
    loop {
        tokio::time::delay_for(Duration::from_secs(1)).await;
        println!("tick");
    }
}

async fn two() {
    tokio::time::delay_for(Duration::from_secs(3)).await;
}

#[tokio::main]
async fn main() {
    tokio::select! {
        _ = one() => {},
        _ = two() => {}
    };
    
    println!("finished.");
}

And this expands to:

use std::time::Duration;

async fn one() -> ! {
    loop {
        tokio::time::delay_for(Duration::from_secs(1)).await;
        println!("tick");
    }
}

async fn two() {
    tokio::time::delay_for(Duration::from_secs(3)).await;
}

#[tokio::main]
async fn main() {
    tokio::select! {
        _ = one() => {},
        _ = two() => {}
    };
    
    println!("finished.");
}
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;
use std::time::Duration;
async fn one() -> ! {
    loop {
        tokio::time::delay_for(Duration::from_secs(1)).await;
        {
            ::std::io::_print(::core::fmt::Arguments::new_v1(
                &["tick\n"],
                &match () {
                    () => [],
                },
            ));
        };
    }
}
async fn two() {
    tokio::time::delay_for(Duration::from_secs(3)).await;
}
fn main() {
    tokio::runtime::Runtime::new().unwrap().block_on(async {
        {
            {
                mod util {
                    pub(super) enum Out<_0, _1> {
                        _0(_0),
                        _1(_1),
                        Disabled,
                    }
                    pub(super) type Mask = u8;
                }
                use ::tokio::macros::support::Future;
                use ::tokio::macros::support::Pin;
                use ::tokio::macros::support::Poll::{Ready, Pending};
                const BRANCHES: u32 = 2;
                let mut disabled: util::Mask = Default::default();
                if !true {
                    let mask = 1 << 0;
                    disabled |= mask;
                }
                if !true {
                    let mask = 1 << 1;
                    disabled |= mask;
                }
                let mut output = {
                    let mut futures = (one(), two());
                    ::tokio::macros::support::poll_fn(|cx| {
                        let mut is_pending = false;
                        let start = ::tokio::macros::support::thread_rng_n(BRANCHES);
                        for i in 0..BRANCHES {
                            let branch = (start + i) % BRANCHES;
                            match branch {
                                0 => {
                                    let mask = 1 << branch;
                                    if disabled & mask == mask {
                                        continue;
                                    }
                                    let (fut, ..) = &mut futures;
                                    let mut fut = unsafe { Pin::new_unchecked(fut) };
                                    let out = match fut.poll(cx) {
                                        Ready(out) => out,
                                        Pending => {
                                            is_pending = true;
                                            continue;
                                        }
                                    };
                                    disabled |= mask;
                                    #[allow(unused_variables)]
                                    match &out {
                                        _ => {}
                                        _ => continue,
                                    }
                                    return Ready(util::Out::_0(out));
                                }
                                1 => {
                                    let mask = 1 << branch;
                                    if disabled & mask == mask {
                                        continue;
                                    }
                                    let (_, fut, ..) = &mut futures;
                                    let mut fut = unsafe { Pin::new_unchecked(fut) };
                                    let out = match fut.poll(cx) {
                                        Ready(out) => out,
                                        Pending => {
                                            is_pending = true;
                                            continue;
                                        }
                                    };
                                    disabled |= mask;
                                    #[allow(unused_variables)]
                                    match &out {
                                        _ => {}
                                        _ => continue,
                                    }
                                    return Ready(util::Out::_1(out));
                                }
                                _ => ::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
                                    &["internal error: entered unreachable code: "],
                                    &match (
                                        &"reaching this means there probably is an off by one bug",
                                    ) {
                                        (arg0,) => [::core::fmt::ArgumentV1::new(
                                            arg0,
                                            ::core::fmt::Display::fmt,
                                        )],
                                    },
                                )),
                            }
                        }
                        if is_pending {
                            Pending
                        } else {
                            Ready(util::Out::Disabled)
                        }
                    })
                    .await
                };
                match output {
                    util::Out::_0(_) => {}
                    util::Out::_1(_) => {}
                    util::Out::Disabled => {
                        ::std::rt::begin_panic("internal error: entered unreachable code")
                    }
                    _ => ::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
                        &["internal error: entered unreachable code: "],
                        &match (&"failed to match bind",) {
                            (arg0,) => [::core::fmt::ArgumentV1::new(
                                arg0,
                                ::core::fmt::Display::fmt,
                            )],
                        },
                    )),
                }
            };
            {
                ::std::io::_print(::core::fmt::Arguments::new_v1(
                    &["finished.\n"],
                    &match () {
                        () => [],
                    },
                ));
            };
        }
    })
}

And we actually get:

warning: unreachable statement
  --> src/main.rs:70:37
   |
63 |   ...                   let out = match fut.poll(cx) {
   |  _________________________________-
64 | | ...                       Ready(out) => out,
65 | | ...                       Pending => {
66 | | ...                           is_pending = true;
67 | | ...                           continue;
68 | | ...                       }
69 | | ...                   };
   | |_______________________- any code following this `match` expression is unreachable, as all arms diverge
70 |   ...                   disabled |= mask;
   |                         ^^^^^^^^^^^^^^^^^ unreachable statement

However, making a small adjustment the warning disappears:

7c7
< async fn one() -> ! {
---
> async fn one() {

You can check it here

The expanded version:

❯ cat without-exl/src/main.rs
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;
use std::time::Duration;
async fn one() {
    loop {
        tokio::time::delay_for(Duration::from_secs(1)).await;
        {
            ::std::io::_print(::core::fmt::Arguments::new_v1(
                &["tick\n"],
                &match () {
                    () => [],
                },
            ));
        };
    }
}
async fn two() {
    tokio::time::delay_for(Duration::from_secs(3)).await;
}
fn main() {
    tokio::runtime::Runtime::new().unwrap().block_on(async {
        {
            {
                mod util {
                    pub(super) enum Out<_0, _1> {
                        _0(_0),
                        _1(_1),
                        Disabled,
                    }
                    pub(super) type Mask = u8;
                }
                use ::tokio::macros::support::Future;
                use ::tokio::macros::support::Pin;
                use ::tokio::macros::support::Poll::{Ready, Pending};
                const BRANCHES: u32 = 2;
                let mut disabled: util::Mask = Default::default();
                if !true {
                    let mask = 1 << 0;
                    disabled |= mask;
                }
                if !true {
                    let mask = 1 << 1;
                    disabled |= mask;
                }
                let mut output = {
                    let mut futures = (one(), two());
                    ::tokio::macros::support::poll_fn(|cx| {
                        let mut is_pending = false;
                        let start = ::tokio::macros::support::thread_rng_n(BRANCHES);
                        for i in 0..BRANCHES {
                            let branch = (start + i) % BRANCHES;
                            match branch {
                                0 => {
                                    let mask = 1 << branch;
                                    if disabled & mask == mask {
                                        continue;
                                    }
                                    let (fut, ..) = &mut futures;
                                    let mut fut = unsafe { Pin::new_unchecked(fut) };
                                    let out = match fut.poll(cx) {
                                        Ready(out) => out,
                                        Pending => {
                                            is_pending = true;
                                            continue;
                                        }
                                    };
                                    disabled |= mask;
                                    #[allow(unused_variables)]
                                    match &out {
                                        _ => {}
                                        _ => continue,
                                    }
                                    return Ready(util::Out::_0(out));
                                }
                                1 => {
                                    let mask = 1 << branch;
                                    if disabled & mask == mask {
                                        continue;
                                    }
                                    let (_, fut, ..) = &mut futures;
                                    let mut fut = unsafe { Pin::new_unchecked(fut) };
                                    let out = match fut.poll(cx) {
                                        Ready(out) => out,
                                        Pending => {
                                            is_pending = true;
                                            continue;
                                        }
                                    };
                                    disabled |= mask;
                                    #[allow(unused_variables)]
                                    match &out {
                                        _ => {}
                                        _ => continue,
                                    }
                                    return Ready(util::Out::_1(out));
                                }
                                _ => ::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
                                    &["internal error: entered unreachable code: "],
                                    &match (
                                        &"reaching this means there probably is an off by one bug",
                                    ) {
                                        (arg0,) => [::core::fmt::ArgumentV1::new(
                                            arg0,
                                            ::core::fmt::Display::fmt,
                                        )],
                                    },
                                )),
                            }
                        }
                        if is_pending {
                            Pending
                        } else {
                            Ready(util::Out::Disabled)
                        }
                    })
                    .await
                };
                match output {
                    util::Out::_0(_) => {}
                    util::Out::_1(_) => {}
                    util::Out::Disabled => {
                        ::std::rt::begin_panic("internal error: entered unreachable code")
                    }
                    _ => ::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
                        &["internal error: entered unreachable code: "],
                        &match (&"failed to match bind",) {
                            (arg0,) => [::core::fmt::ArgumentV1::new(
                                arg0,
                                ::core::fmt::Display::fmt,
                            )],
                        },
                    )),
                }
            };
            {
                ::std::io::_print(::core::fmt::Arguments::new_v1(
                    &["finished.\n"],
                    &match () {
                        () => [],
                    },
                ));
            };
        }
    })
}

So honestly, I'm in doubt whether this is a tokio or rust issue.

@blasrodri
Copy link
Contributor

Seems that the issue comes from yielding the future's output value, when being Ready. This happens in the following line, on the macro select!:

Ready(out) => out,

So what is happening is that the return type of one() is !. So because out is of type !, then Rust assumes that the rest of the code is not reachable. This explains why, also when changing the signature to being async fn one() -> (), the warning disappears.

Any ideas on how to move forward? @Darksonn

@Darksonn
Copy link
Contributor

I think that we just want an #[allow(unreachable_code)] in the right place.

blasrodri pushed a commit to blasrodri/tokio that referenced this issue Jul 22, 2020
Solves tokio-rs#2665 by adding #[allow(unreachable_code)] inside a branch
matching arm.
blasrodri added a commit to blasrodri/tokio that referenced this issue Jul 22, 2020
Solves tokio-rs#2665 by adding #[allow(unreachable_code)] inside a branch
matching arm.
blasrodri added a commit to blasrodri/tokio that referenced this issue Jul 22, 2020
Solves tokio-rs#2665 by adding #[allow(unreachable_code)] inside a branch
matching arm.
blasrodri added a commit to blasrodri/tokio that referenced this issue Jul 22, 2020
Solves tokio-rs#2665 by adding #[allow(unreachable_code)] inside a branch
matching arm.
carllerche pushed a commit that referenced this issue Jul 28, 2020
Solves #2665 by adding #[allow(unreachable_code)] inside a branch
matching arm.

Co-authored-by: Alice Ryhl <alice@ryhl.io>
@taiki-e
Copy link
Member

taiki-e commented Oct 5, 2020

Closing -- fixed in #2678

@taiki-e taiki-e closed this as completed Oct 5, 2020
@Darksonn Darksonn removed the E-help-wanted Call for participation: Help is requested to fix this issue. label Dec 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-tokio Area: The main tokio crate C-bug Category: This is a bug. M-macros Module: macros in the main Tokio crate
Projects
None yet
Development

No branches or pull requests

4 participants