Skip to content

awaiting a spawn_local JoinHandle is not Send even if the Output is Send #960

Open
@Fishrock123

Description

@Fishrock123

To be clear - I am not 100% sure if this is supposed to work, but I think I have reason to believe it reasonably should work.

I have an example at https://gist.github.com/Fishrock123/82e742454a844914513be49738e4a34e where a Mutex inside an Arc is moved into a spawn_local from an outer task::spawn, but fails to compile with this error:

`std::sync::MutexGuard<'_, Inner>` cannot be sent between threads safely

This is strange, because I am going through the effort to prevent exactly this, by only accessing the Mutex from within a spawn_local. This seems to only occur if the Mutex'd struct has an async function called from it. In my case, that's happening with &mut self. I believe that an async function at this point should not require Send, since it is all running within the same thread, absent any other task::spawns, but I guess it does, for some reason? The error is particularly bizarre, and probably also a compiler output bug, because it seems to refer to the wrong things in general.

The example also compiles if the outer task::spawn is also spawn_local, which is undesirable in the actual code this example is based off of (I want a thread / threadpool for a queue reading client).

Activity

yoshuawuyts

yoshuawuyts commented on Apr 2, 2021

@yoshuawuyts
Contributor

@Fishrock123 the title of this issue mentions spawn_blocking but the example uses spawn_local. Which API did you intend to open this issue on?

yoshuawuyts

yoshuawuyts commented on Apr 2, 2021

@yoshuawuyts
Contributor

The error is particularly bizarre, and probably also a compiler output bug, because it seems to refer to the wrong things in general.

Hmm, yeah looking at your example now and I'm really unsure what's causing this. This indeed seems like the compiler may be incorrectly passing along the Send bound from the outer future to the inner future. Given that bounds on async {} blocks are inferred from the futures they're wrapping, this may just be an issue in the compiler.

This is probably worth opening on issue for on the compiler so the working group can triage it!

changed the title [-]spawn_blocking does not properly guard mutable references[/-] [+]spawn_local does not properly guard mutable references[/+] on Apr 2, 2021
Fishrock123

Fishrock123 commented on Apr 2, 2021

@Fishrock123
MemberAuthor

the title of this issue mentions spawn_blocking but the example uses spawn_local. Which API did you intend to open this issue on?

Sorry, I must have mistyped. This is about spawn_local.

jbr

jbr commented on Apr 2, 2021

@jbr
Contributor

relevant: rust-lang/rust#71072 — is the goal specifically to hold a std::sync::MutexGuard across an await boundary, or is that just how you're building a !Send future? The problem goes away if you use async_std::sync::Mutex. Is it safe to reduce this example to just "any !Send future?"

jbr

jbr commented on Apr 2, 2021

@jbr
Contributor

if it's just about a !Send future, here's a simpler repro:

use async_std::future::ready;
use async_std::task::{self, block_on, spawn, spawn_local};
use std::rc::Rc;

pub fn main() {
    block_on(async {
        spawn(async {
            spawn_local(async {
                ready(Rc::new(())).await;
            })
            .await
        })
        .await;
    })
}
Fishrock123

Fishrock123 commented on Apr 2, 2021

@Fishrock123
MemberAuthor

Oh, huh. I never thought to try async_std::sync::Mutex, I didn't realize it would have differences in Send bounds.

Fishrock123

Fishrock123 commented on Apr 2, 2021

@Fishrock123
MemberAuthor

My specific problem was regarding a MutexGuard in specific, but I think this is probably also the general issue because that output is not good.

jbr

jbr commented on Apr 2, 2021

@jbr
Contributor

Agreed on the output, plus I think it should be possible to await the task representing a !Send future from within a Send future, but maybe that hasn't been implemented yet. I think it'd require some indirection like using a channel instead of directly awaiting the inner task. We can achieve that result that in user code like this, but it seems like something async-std could do:

use async_std::channel;
use async_std::future::ready;
use async_std::task::{block_on, spawn, spawn_local};
use std::rc::Rc;

pub fn main() {
    block_on(async {
        spawn(async {
            let (tx, rx) = channel::bounded(1);
            spawn_local(async move {
                ready(Rc::new(())).await;
                tx.send(()).await.unwrap();
            });
            rx.recv().await.unwrap();
        })
        .await;
    })
}
changed the title [-]spawn_local does not properly guard mutable references[/-] [+]awaiting a spawn_local Task is not Send even if the Output is Send[/+] on Apr 2, 2021
changed the title [-]awaiting a spawn_local Task is not Send even if the Output is Send[/-] [+]awaiting a spawn_local JoinHandle is not Send even if the Output is Send[/+] on Apr 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @jbr@Fishrock123@yoshuawuyts

        Issue actions

          awaiting a spawn_local JoinHandle is not Send even if the Output is Send · Issue #960 · async-rs/async-std