use std::ffi::c_void;
use std::sync::Mutex;
pub type DispatchFn = extern "C" fn(
engine: *mut c_void,
callback: extern "C" fn(*mut c_void),
user_data: *mut c_void,
) -> bool;
#[derive(Copy, Clone)]
struct Dispatcher {
func: DispatchFn,
engine: *mut c_void,
}
unsafe impl Send for Dispatcher {}
unsafe impl Sync for Dispatcher {}
static DISPATCHER: Mutex<Option<Dispatcher>> = Mutex::new(None);
#[doc(hidden)]
pub fn set_main_thread_dispatcher(func: Option<DispatchFn>, engine: *mut c_void) {
let built = func.map(|func| Dispatcher { func, engine });
if let Ok(mut guard) = DISPATCHER.lock() {
*guard = built;
}
}
pub fn run_on_main_thread<F>(f: F)
where
F: FnOnce() + Send + 'static,
{
let dispatcher = match DISPATCHER.lock().ok().and_then(|g| *g) {
Some(d) => d,
None => {
#[cfg(debug_assertions)]
eprintln!(
"whisker-runtime: run_on_main_thread called before dispatcher \
registration; closure dropped"
);
return;
}
};
let boxed: Box<Box<dyn FnOnce() + Send + 'static>> = Box::new(Box::new(f));
let user_data = Box::into_raw(boxed) as *mut c_void;
let ok = (dispatcher.func)(dispatcher.engine, trampoline, user_data);
if !ok {
let _: Box<Box<dyn FnOnce() + Send + 'static>> =
unsafe { Box::from_raw(user_data as *mut Box<dyn FnOnce() + Send + 'static>) };
}
}
extern "C" fn trampoline(user_data: *mut c_void) {
if user_data.is_null() {
return;
}
let boxed: Box<Box<dyn FnOnce() + Send + 'static>> =
unsafe { Box::from_raw(user_data as *mut Box<dyn FnOnce() + Send + 'static>) };
boxed();
crate::host_wake::wake_runtime();
}
#[doc(hidden)]
pub fn __reset_for_tests() {
if let Ok(mut guard) = DISPATCHER.lock() {
*guard = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
static TEST_LOCK: Mutex<()> = Mutex::new(());
fn lock<'a>() -> MutexGuard<'a, ()> {
TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner())
}
extern "C" fn sync_invoke(
_engine: *mut c_void,
callback: extern "C" fn(*mut c_void),
user_data: *mut c_void,
) -> bool {
callback(user_data);
true
}
extern "C" fn refuse(
_engine: *mut c_void,
_callback: extern "C" fn(*mut c_void),
_user_data: *mut c_void,
) -> bool {
false
}
fn install(func: DispatchFn) {
__reset_for_tests();
set_main_thread_dispatcher(Some(func), std::ptr::null_mut());
}
#[test]
fn closure_runs_when_dispatcher_installed() {
let _guard = lock();
install(sync_invoke);
let ran = Arc::new(AtomicBool::new(false));
let ran_clone = ran.clone();
run_on_main_thread(move || {
ran_clone.store(true, Ordering::SeqCst);
});
assert!(ran.load(Ordering::SeqCst), "closure must have run");
__reset_for_tests();
}
#[test]
fn closure_dropped_when_no_dispatcher() {
let _guard = lock();
__reset_for_tests();
struct DropFlag(Arc<AtomicBool>);
impl Drop for DropFlag {
fn drop(&mut self) {
self.0.store(true, Ordering::SeqCst);
}
}
let dropped = Arc::new(AtomicBool::new(false));
let flag = DropFlag(dropped.clone());
run_on_main_thread(move || {
let _ = &flag;
});
assert!(
dropped.load(Ordering::SeqCst),
"closure (and captured state) must be dropped when no dispatcher is set"
);
}
#[test]
fn closure_dropped_on_dispatch_failure() {
let _guard = lock();
install(refuse);
struct DropFlag(Arc<AtomicBool>);
impl Drop for DropFlag {
fn drop(&mut self) {
self.0.store(true, Ordering::SeqCst);
}
}
let dropped = Arc::new(AtomicBool::new(false));
let flag = DropFlag(dropped.clone());
run_on_main_thread(move || {
let _ = &flag;
});
assert!(
dropped.load(Ordering::SeqCst),
"closure must be dropped when dispatcher refuses"
);
__reset_for_tests();
}
#[test]
fn multiple_dispatches_each_run_once() {
let _guard = lock();
install(sync_invoke);
let counter = Arc::new(AtomicUsize::new(0));
for _ in 0..5 {
let c = counter.clone();
run_on_main_thread(move || {
c.fetch_add(1, Ordering::SeqCst);
});
}
assert_eq!(counter.load(Ordering::SeqCst), 5);
__reset_for_tests();
}
}