Reputation: 917
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
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 behaviornew() -> 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