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

rustc fails to prove Sendness for an async block where a !Send value is dropped before an .await point #128095

Open
frank-king opened this issue Jul 23, 2024 · 6 comments · May be fixed by #128846
Assignees
Labels
A-async-await Area: Async & Await AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug. F-coroutines `#![feature(coroutines)]`

Comments

@frank-king
Copy link
Contributor

frank-king commented Jul 23, 2024

I tried this code: (playground)

#[tokio::main]
async fn main() {
    let (tx, rx) = tokio::sync::watch::channel(vec![1, 1, 4, 5, 1, 4]);
    let mut binding = rx.clone();
    tokio::spawn(async move {
        let value = binding.borrow();
        let len = value.len();
        drop(value);
        println!("len: {}", len);
        binding.changed().await
            .expect("watch error");
    }).await;
    println!("{:?}", rx.borrow());
}

I expected to see this happen: the code passes compilation.

Instead, this happened:

error: future cannot be sent between threads safely
   --> src/main.rs:5:5
    |
5   | /     tokio::spawn(async move {
6   | |         let value = binding.borrow();
7   | |         let len = value.len();
8   | |         drop(value);
...   |
11  | |             .expect("watch error");
12  | |     }).await;
    | |______^ future created by async block is not `Send`
    |
    = help: within `{async block@src/main.rs:5:18: 5:28}`, the trait `Send` is not implemented for `std::sync::RwLockReadGuard<'_, Vec<i32>>`, which is required by `{async block@src/main.rs:5:18: 5:28}: Send`
note: future is not `Send` as this value is used across an await
   --> src/main.rs:10:27
    |
6   |         let value = binding.borrow();
    |             ----- has type `tokio::sync::watch::Ref<'_, Vec<i32>>` which is not `Send`
...
10  |         binding.changed().await
    |                           ^^^^^ await occurs here, with `value` maybe used later
note: required by a bound in `tokio::spawn`
   --> /playground/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.38.0/src/task/spawn.rs:166:21
    |
164 |     pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
    |            ----- required by a bound in this function
165 |     where
166 |         F: Future + Send + 'static,
    |                     ^^^^ required by this bound in `spawn`

error: future cannot be sent between threads safely
   --> src/main.rs:5:5
    |
5   | /     tokio::spawn(async move {
6   | |         let value = binding.borrow();
7   | |         let len = value.len();
8   | |         drop(value);
...   |
11  | |             .expect("watch error");
12  | |     }).await;
    | |______^ future created by async block is not `Send`
    |
    = help: within `{async block@src/main.rs:5:18: 5:28}`, the trait `Send` is not implemented for `*mut ()`, which is required by `{async block@src/main.rs:5:18: 5:28}: Send`
note: future is not `Send` as this value is used across an await
   --> src/main.rs:10:27
    |
6   |         let value = binding.borrow();
    |             ----- has type `tokio::sync::watch::Ref<'_, Vec<i32>>` which is not `Send`
...
10  |         binding.changed().await
    |                           ^^^^^ await occurs here, with `value` maybe used later
note: required by a bound in `tokio::spawn`
   --> /playground/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.38.0/src/task/spawn.rs:166:21
    |
164 |     pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
    |            ----- required by a bound in this function
165 |     where
166 |         F: Future + Send + 'static,
    |                     ^^^^ required by this bound in `spawn`

However, this code works fine: (playground)

#[tokio::main]
async fn main() {
    let (tx, rx) = tokio::sync::watch::channel(vec![1, 1, 4, 5, 1, 4]);
    let mut binding = rx.clone();
    tokio::spawn(async move {
        let len = {
            let value = binding.borrow();
            value.len()
        };
        println!("len: {}", len);
        binding.changed().await
            .expect("watch error");
    }).await;
    println!("{:?}", rx.borrow());
}

Meta: nightly rust (1.81.0-nightly 2024-07-12) and stable rust (1.79.0) behave similarly.

I have constructed a minimum reproducible version:

#![allow(unused)]

struct NotSend(std::marker::PhantomData<*mut ()>);

impl NotSend {
    fn new() -> Self {
        Self(std::marker::PhantomData)
    }
}

unsafe impl Sync for NotSend {}

#[derive(Clone)]
struct WatchRecv;

impl WatchRecv {
    fn borrow(&self) -> Ref<'_> {
        Ref(self, NotSend::new())
    }
}

struct Ref<'a>(&'a WatchRecv, NotSend);

fn assert_send<F: std::future::Future + Send>(f: F) -> F { f }

impl Ref<'_> {
    fn len(&self) -> usize { 0 }
}

async fn another_future() {
    loop {}
}

async fn real_main() {
    let rx = WatchRecv;
    let mut binding = rx.clone();
    assert_send(async move {
        // /*
        // This doesn't works.
        let value = binding.borrow();
        let len = value.len();
        drop(value);
        println!("len: {len}");
        // */
        /*
        // This works.
        let len = {
            let value = binding.borrow();
            value.len()
        };
        */
        another_future().await;
    }).await;
    println!("{:?}", rx.borrow().len());
}
@frank-king frank-king added the C-bug Category: This is a bug. label Jul 23, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Jul 23, 2024
@frank-king frank-king changed the title rustc fials to prove Sendness for an async block within a !Send value dropped before an .await point rustc fails to prove Sendness for an async block within a !Send value dropped before an .await point Jul 23, 2024
@frank-king
Copy link
Contributor Author

I dag into rustc_trait_selection for a while but didn't find a clear answer. Is it because the trait obligation selection is based on lexical scopes instead of non-lexical lifetimes?

@frank-king frank-king changed the title rustc fails to prove Sendness for an async block within a !Send value dropped before an .await point rustc fails to prove Sendness for an async block where a !Send value is dropped before an .await point Jul 24, 2024
@compiler-errors
Copy link
Member

This isn't anything to do with trait selection. I believe this has to do with the temporary that we create on the autoderef when calling the .len() method. Here's a minimal example:

async fn yield_point() {}

struct Lock;
impl Lock {
    fn borrow(&self) -> LockGuard<'_> { todo!() }
}

struct LockGuard<'a>(*mut (), &'a ());
impl std::ops::Deref for LockGuard<'_> {
    type Target = Inner;
    fn deref(&self) -> &Self::Target { todo!() }
}

struct Inner;
impl Inner {
    fn foo(&self) -> i32 { 0 }
}

fn main() {
    let lock = Lock;

    fn is_send(_: impl Send) {}
    is_send(async {
        let guard = lock.borrow();
        let _len = guard.foo(); // Comment this out and it works.
        drop(guard);
        yield_point().await;
    });
}

@compiler-errors
Copy link
Member

compiler-errors commented Jul 24, 2024

Actually, here's a more minimal example:

#![feature(coroutines)]

struct S(*mut ());
impl S {
    fn do_something(&self) {}
}

fn main() {
    fn is_send(_: impl Send) {}
    is_send(#[coroutine] static || {
        // Make a new `S`.
        let s = S::new();
        // Borrow it by calling a method on it.
        s.do_something();
        // Drop `s` by moving it.
        drop(s);
        // Closure analysis keeps thinking that it's live since it has been
        //  borrowed and there's no `StorageDrop` on `s`'s local.
        yield;
    });
}

It seems to do with the fact that we don't consider the variable s to be dead even though it's unconditionally moved from.

@compiler-errors
Copy link
Member

cc #112279 which fixes this

@yhx-12243
Copy link

#104883 (comment)

@jieyouxu jieyouxu added A-async-await Area: Async & Await F-coroutines `#![feature(coroutines)]` labels Aug 5, 2024
@traviscross
Copy link
Contributor

@rustbot labels +AsyncAwait-Triaged

We discussed this in our meeting today. Looks like it's being fixed.

@rustbot rustbot added the AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. label Aug 8, 2024
@compiler-errors compiler-errors self-assigned this Aug 8, 2024
bors added a commit to rust-lang-ci/rust that referenced this issue Aug 8, 2024
Stop considering moved-out locals when computing auto traits for generators (rebased)

This PR revives rust-lang#112279. I wanted to reopen this to gauge `@cjgillot's` thoughts about this, since it's been a while since `-Zdrop-tracking-mir` has landed.

If this PR looks OK, I can flesh it out and write up an FCP report -- I think this is a T-types FCP, since this has to do with the types that are considered live for auto traits, and doesn't otherwise affect the layout of coroutines.

Open questions:
* **"Why do we require storage to be live if the locals is not initialized?"** (rust-lang#112279 (comment))
    * I agree that we should eventually fix the storage analysis for coroutines, but this seems like a problem that can be fixed after we fix witnesses for *the purposes of traits* here.
    * As far as I could tell, fixing the problem of moved locals for *storage* would require insertion of additional `StorageDead` statements, or would require further entangling the type system with the operational semantics in ways that T-opsem seemed uncomfortable with [when I asked](https://rust-lang.zulipchat.com/#narrow/stream/189540-t-compiler.2Fwg-mir-opt/topic/Inserting.20.60StorageDrop.60.20after.20unconditional.20moves). cc `@RalfJung`
    * Relevant: rust-lang/unsafe-code-guidelines#188

Fixes rust-lang#128095
Fixes rust-lang#94067

r? `@cjgillot`
@saethlin saethlin removed the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Aug 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-async-await Area: Async & Await AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug. F-coroutines `#![feature(coroutines)]`
Projects
None yet
7 participants