use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
#[derive(Clone)]
pub struct CancelToken {
flag: Arc<AtomicBool>,
#[cfg(feature = "tokio")]
notify: Arc<tokio::sync::Notify>,
}
impl CancelToken {
pub fn new() -> Self {
Self {
flag: Arc::new(AtomicBool::new(false)),
#[cfg(feature = "tokio")]
notify: Arc::new(tokio::sync::Notify::new()),
}
}
pub fn cancel(&self) {
self.flag.store(true, Ordering::Release);
#[cfg(feature = "tokio")]
self.notify.notify_waiters();
}
pub fn is_cancelled(&self) -> bool {
self.flag.load(Ordering::Acquire)
}
#[cfg(feature = "tokio")]
pub async fn cancelled(&self) {
if self.is_cancelled() {
return;
}
let notified = self.notify.notified();
tokio::pin!(notified);
notified.as_mut().enable();
if self.is_cancelled() {
return;
}
notified.await;
}
}
impl Default for CancelToken {
fn default() -> Self {
Self::new()
}
}
pub(crate) fn install_cancel_hook(
lua: &mlua::Lua,
token: CancelToken,
interval: u32,
) -> Result<(), crate::IsleError> {
lua.set_hook(
mlua::HookTriggers::new().every_nth_instruction(interval),
move |_lua, _debug| {
if token.is_cancelled() {
Err(mlua::Error::runtime("__isle_cancelled__"))
} else {
Ok(mlua::VmState::Continue)
}
},
)
.map_err(crate::IsleError::from)
}
pub(crate) fn remove_hook(lua: &mlua::Lua) {
lua.remove_hook();
}
#[cfg(feature = "tokio")]
pub(crate) fn install_cancel_hook_on_thread(
thread: &mlua::Thread,
token: CancelToken,
interval: u32,
) -> Result<(), crate::IsleError> {
thread
.set_hook(
mlua::HookTriggers::new().every_nth_instruction(interval),
move |_lua, _debug| {
if token.is_cancelled() {
Err(mlua::Error::runtime("__isle_cancelled__"))
} else {
Ok(mlua::VmState::Continue)
}
},
)
.map_err(crate::IsleError::from)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn token_default_not_cancelled() {
let token = CancelToken::new();
assert!(!token.is_cancelled());
}
#[test]
fn token_cancel_sets_flag() {
let token = CancelToken::new();
let clone = token.clone();
token.cancel();
assert!(clone.is_cancelled());
}
#[test]
fn hook_interrupts_lua_loop() {
let lua = mlua::Lua::new();
let token = CancelToken::new();
install_cancel_hook(&lua, token.clone(), 100).unwrap();
let t = token.clone();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(10));
t.cancel();
});
let result: mlua::Result<()> = lua.load("while true do end").exec();
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("__isle_cancelled__"),
"expected cancellation sentinel, got: {err_msg}"
);
}
}