spockwang
spockwang

Reputation: 917

How `tokio::spawn` handles the situation when the future can't be moved?

The function tokio::spawn is defined as follows:

pub fn spawn<T>(future: T) -> JoinHandle<T::Output>

The future does not implement Copy trait, so it is moved into the function. But some futures can't be moved because it has pointers pointing to itself. My question is: how does tokio::spawn handle this?

Upvotes: 0

Views: 1622

Answers (1)

Aitch
Aitch

Reputation: 1697

Referential data should be created by either:

  • unsafe new() -> Self: documentation has to justify unsafe that the caller must not move it or any internal unsafe pointer operations will have undefined behavior
  • a new() -> Pin<Box<Self>>: moving is okay, because data is on the heap and will never move.

In the case of a Future containing self-referential data, it has to be a Pin<Box<Fut>> where Fut can be !Unpin. I think this is the best way, because you have to move. There might be other structs where your wrapper type is Unpin although the inner type is !Unpin.

use std::{
    marker::PhantomPinned,
    pin::Pin,
    task::{Context, Poll},
};
use tokio;

#[tokio::main]
async fn main() {
    tokio::spawn(CrazyStruct::new("GOOD")).await.unwrap();
    tokio::spawn(unsafe { CrazyStruct::new_unpinned("BAD") }) // invariant violation, by moving here!
        .await
        .unwrap();

    // let cs = CrazyStruct::new("TEST").as_mut().get_mut(); // thanks to `PhantomPinned` that this fails
}

/// Can be safely send between threads. (this comes from pointer are not Send)
unsafe impl Send for CrazyStruct {}

struct CrazyStruct {
    name: &'static str,
    value: u8,

    /// self-referential pointer to `self.value`
    value_ptr: Option<*const u8>,

    _pinned: PhantomPinned,
}

impl CrazyStruct {
    /// Don't move! Contains self-referential data.
    unsafe fn new_unpinned(name: &'static str) -> Self {
        let mut me = CrazyStruct {
            name,
            value: 42,
            value_ptr: None,
            _pinned: PhantomPinned,
        };

        me.value_ptr = Some(&me.value);
        me
    }

    fn new(name: &'static str) -> Pin<Box<Self>> {
        let mut me = Box::pin(CrazyStruct {
            name,
            value: 42,
            value_ptr: None,
            _pinned: PhantomPinned,
        });

        // SAFETY: we don't move
        unsafe {
            me.as_mut().get_unchecked_mut().value_ptr = Some(&me.value);
        }

        me
    }
}

impl std::future::Future for CrazyStruct {
    type Output = ();

    fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
        assert!(
            self.value_ptr == Some(&self.value),
            "Did you move '{}'???",
            self.name
        );
        std::task::Poll::Ready(())
    }
}

// thread 'tokio-runtime-worker' panicked at 'Did you move 'BAD'???', src/main.rs:66:9
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
// thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: JoinError::Panic(Id(10), ...)', src/main.rs:13:10

Upvotes: 1

Related Questions