smol-rs/async-task

spawn_local for no_std ?

Closed this issue · 4 comments

Hi,

I'm looking to build a small executor with async-task that runs in the idle task of the RTIC (formerly RTFM) framework.

I would like to run non-Send futures in this guaranteed single-thread environment. This would be a very similar function to the current spawn_local, but with no thread id checks and only references to core and alloc crates.

Do you think this can work ?
If so, I will submit a PR soon.

That is possible, but the API would need to be unsafe.

Note that you can already use async_task::spawn() in a guaranteed single-thread environment if you apply this combinator to your !Send types:

struct AssertSend<T>(T);

unsafe impl<T> Send for AssertSend<T> {}

impl AssertSend {
    unsafe fn new(t: T) {
        AssertSend(t)
    }
}

The async_task::spawn_local() is just a wrapper around async_task::spawn() that checks "am I on the same thread that spawned this?" at runtime whenever a !Send type is touched.

Alright, I'm going with your approach. My executor handle is !Send and !Sync so it can AssertSend safely.

Right now, I'm running into the problem that my implementation now needs the Future result to be Send because spawn has the bound R: Send + 'static. I can't seem to properly AssertSend on R.

My code so far:

struct AssertSend<T>(T);

unsafe impl<T> Send for AssertSend<T> {}

impl <F: Future, R> Future for AssertSend<F>
where
        F: Future<Output = R> + 'static,
        R: 'static,
{
    type Output = R;

    fn poll(self: Pin<&mut Self>, context: &mut Context) -> Poll<Self::Output> {
        let fut = unsafe { self.map_unchecked_mut(|s| &mut s.0) };
        fut.poll(context)
    }
}

`

If finally managed to wrap things, the only downside is that JoinHandle.await returns the newtype, which I can live with.

    type Output = AssertSend<R>;

    fn poll(self: Pin<&mut Self>, context: &mut Context) -> Poll<Self::Output> {
        let fut = unsafe { self.map_unchecked_mut(|s| &mut s.0) };
        fut.poll(context).map(AssertSend)
    }

Doing let future = async move { future.await.0 }; inside the executor spawn function is not enough to convince the compiler.

I might do some #[repr(transparent)] with a rather unsafe transmute on the JoinHandle.

I'm closing this, I'm happy with AssertSend. Thanks for the help !