From 352a73767df911ff839eb2ad85f003714ba6b60f Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 27 Aug 2023 22:06:08 +0200 Subject: [PATCH] Add explanation --- src/borrow_unchecked.rs | 17 +++++++++++++++++ src/event_loop.rs | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/borrow_unchecked.rs b/src/borrow_unchecked.rs index 0aeda1c..e84b951 100644 --- a/src/borrow_unchecked.rs +++ b/src/borrow_unchecked.rs @@ -1,5 +1,22 @@ // Borrowed from https://github.com/jeremyBanks/you-can +// What's going on here? Unsafe borrows??? +// NO: this is actually 100% safe, and here's why. +// +// This is needed because of https://github.com/danog/php-tokio/blob/master/src/event_loop.rs#L72 +// +// Rust thinks we're Sending the Future to another thread (tokio's event loop), +// where it may be used even after its lifetime expires in the main (PHP) thread. +// +// In reality, the Future is only used by Tokio until the result is ready. +// +// Rust does not understand that when we suspend the current fiber in suspend_on, +// we basically keep alive the the entire stack, +// including the Rust stack and the Future on it, until the result of the future is ready. +// +// Once the result of the Future is ready, tokio doesn't need it anymore, +// the suspend_on function is resumed, and we safely drop the Future upon exiting. + use nicelocal_ext_php_rs::binary_slice::{BinarySlice, PackSlice}; diff --git a/src/event_loop.rs b/src/event_loop.rs index 278bb09..fa09e8f 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -61,6 +61,21 @@ impl EventLoop { } pub fn suspend_on + Send + 'static>(future: F) -> T { + // What's going on here? Unsafe borrows??? + // NO: this is actually 100% safe, and here's why. + // + // Rust thinks we're Sending the Future to another thread (tokio's event loop), + // where it may be used even after its lifetime expires in the main (PHP) thread. + // + // In reality, the Future is only used by Tokio until the result is ready. + // + // Rust does not understand that when we suspend the current fiber in suspend_on, + // we basically keep alive the the entire stack, + // including the Rust stack and the Future on it, until the result of the future is ready. + // + // Once the result of the Future is ready, tokio doesn't need it anymore, + // the suspend_on function is resumed, and we safely drop the Future upon exiting. + // let (future, get_current_suspension) = EVENTLOOP.with_borrow_mut(move |c| { let c = c.as_mut().unwrap(); let idx = c.fibers.len() as u64; @@ -79,7 +94,10 @@ impl EventLoop { }) }); + // We suspend the fiber here, the Rust stack is kept alive until the result is ready. call_user_func!(get_current_suspension).unwrap().try_call_method("suspend", vec![]).unwrap(); + + // We've resumed, the `future` is already resolved and is not used by the tokio thread, it's safe to drop it. return RUNTIME.block_on(future).unwrap(); }