use std::{
ffi::c_void,
future::Future,
marker::PhantomData,
pin::Pin,
sync::{Arc, Mutex},
task::{Context, Poll, Waker},
};
use async_once_cell::OnceCell;
pub type LoadCallbackFn = unsafe extern "C" fn(*const c_void, bool) -> ();
pub type LoadFn = unsafe extern "C" fn(LoadCallbackFn, *const c_void) -> ();
#[derive(Debug, Clone)]
pub struct SplitLoaderError {
_not_send_sync: PhantomData<*const u8>,
}
impl std::fmt::Display for SplitLoaderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("failed to load split module")
}
}
impl std::error::Error for SplitLoaderError {}
pub struct LazySplitLoader {
load: LoadFn,
cell: OnceCell<()>,
}
impl LazySplitLoader {
pub const unsafe fn new(load: LoadFn) -> Self {
Self {
load,
cell: OnceCell::new(),
}
}
}
pub async fn ensure_loaded(loader: Pin<&LazySplitLoader>) -> Result<(), SplitLoaderError> {
let loader = loader.get_ref();
loader
.cell
.get_or_try_init(SplitLoaderFuture::new(loader.load))
.await
.map(|_| ())
}
unsafe extern "C" fn load_callback(loader: *const c_void, success: bool) {
unsafe { Arc::<SplitLoader>::from_raw(loader.cast()) }.complete(success);
}
enum SplitLoaderFutState {
Deferred(LoadFn),
Pending(Arc<SplitLoader>),
}
struct SplitLoaderFuture {
state: SplitLoaderFutState,
}
impl SplitLoaderFuture {
const fn new(load: LoadFn) -> Self {
SplitLoaderFuture {
state: SplitLoaderFutState::Deferred(load),
}
}
}
impl Future for SplitLoaderFuture {
type Output = Result<(), SplitLoaderError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), SplitLoaderError>> {
match self.state {
SplitLoaderFutState::Deferred(load) => {
let shared_state = Arc::new(SplitLoader {
state: Mutex::new(SplitLoaderState::Waiting(cx.waker().clone())),
});
unsafe {
load(
load_callback,
Arc::<SplitLoader>::into_raw(shared_state.clone()).cast(),
)
};
self.state = SplitLoaderFutState::Pending(shared_state);
Poll::Pending
}
SplitLoaderFutState::Pending(ref state) => state.update(|state| match state {
SplitLoaderState::Done(value) => Poll::Ready(load_result(*value)),
SplitLoaderState::Waiting(ref mut waker) => {
*waker = cx.waker().clone();
Poll::Pending
}
}),
}
}
}
fn load_result(success: bool) -> Result<(), SplitLoaderError> {
if success {
Ok(())
} else {
Err(SplitLoaderError {
_not_send_sync: PhantomData,
})
}
}
struct SplitLoader {
state: Mutex<SplitLoaderState>,
}
impl SplitLoader {
fn update<R>(&self, f: impl FnOnce(&mut SplitLoaderState) -> R) -> R {
let mut state = self.state.lock().expect("lock poisoned");
f(&mut state)
}
fn complete(&self, value: bool) {
self.update(|state| {
if let SplitLoaderState::Waiting(waker) =
std::mem::replace(state, SplitLoaderState::Done(value))
{
waker.wake();
}
});
}
}
enum SplitLoaderState {
Waiting(Waker),
Done(bool),
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::task::{RawWaker, RawWakerVTable};
fn block_on<F: Future>(fut: F) -> F::Output {
fn noop_raw_waker() -> RawWaker {
fn no_op(_: *const ()) {}
fn clone(_: *const ()) -> RawWaker {
noop_raw_waker()
}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, no_op, no_op, no_op);
RawWaker::new(std::ptr::null(), &VTABLE)
}
let waker = unsafe { Waker::from_raw(noop_raw_waker()) };
let mut cx = Context::from_waker(&waker);
let mut fut = std::pin::pin!(fut);
for _ in 0..1000 {
if let Poll::Ready(out) = fut.as_mut().poll(&mut cx) {
return out;
}
}
panic!("future did not complete");
}
static TEST_LOCK: Mutex<()> = Mutex::new(());
static CALLS: AtomicUsize = AtomicUsize::new(0);
static SUCCEED_FROM_CALL: AtomicUsize = AtomicUsize::new(0);
unsafe extern "C" fn scripted_load(cb: LoadCallbackFn, data: *const c_void) {
let call = CALLS.fetch_add(1, Ordering::SeqCst) + 1;
let success = call >= SUCCEED_FROM_CALL.load(Ordering::SeqCst);
unsafe { cb(data, success) };
}
fn reset_script(succeed_from_call: usize) {
CALLS.store(0, Ordering::SeqCst);
SUCCEED_FROM_CALL.store(succeed_from_call, Ordering::SeqCst);
}
#[test]
fn immediate_success_loads_once() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_script(1);
let loader = unsafe { LazySplitLoader::new(scripted_load) };
assert!(block_on(ensure_loaded(Pin::new(&loader))).is_ok());
assert!(block_on(ensure_loaded(Pin::new(&loader))).is_ok());
assert_eq!(CALLS.load(Ordering::SeqCst), 1);
}
#[test]
fn failed_load_is_not_cached_and_a_later_call_retries() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_script(2); let loader = unsafe { LazySplitLoader::new(scripted_load) };
assert!(
block_on(ensure_loaded(Pin::new(&loader))).is_err(),
"first call should fail"
);
assert_eq!(CALLS.load(Ordering::SeqCst), 1);
assert!(
block_on(ensure_loaded(Pin::new(&loader))).is_ok(),
"second call should succeed"
);
assert_eq!(CALLS.load(Ordering::SeqCst), 2);
assert!(
block_on(ensure_loaded(Pin::new(&loader))).is_ok(),
"third call should still succeed"
);
assert_eq!(CALLS.load(Ordering::SeqCst), 2);
}
#[test]
fn fallible_wrapper_shape_short_circuits_then_recovers() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_script(2); let loader = unsafe { LazySplitLoader::new(scripted_load) };
async fn fallible_wrapper(
loader: Pin<&LazySplitLoader>,
body: impl FnOnce() -> u32,
) -> Result<u32, SplitLoaderError> {
ensure_loaded(loader).await?;
Ok(body())
}
let body_runs = AtomicUsize::new(0);
let first = block_on(fallible_wrapper(Pin::new(&loader), || {
body_runs.fetch_add(1, Ordering::SeqCst);
42
}));
assert!(first.is_err(), "first call should fail");
assert_eq!(
body_runs.load(Ordering::SeqCst),
0,
"the function body must not run when the load fails"
);
let second = block_on(fallible_wrapper(Pin::new(&loader), || {
body_runs.fetch_add(1, Ordering::SeqCst);
42
}));
assert!(matches!(second, Ok(42)), "second call should succeed");
assert_eq!(body_runs.load(Ordering::SeqCst), 1);
}
}