1use std::sync::atomic::{AtomicBool, Ordering};
9use std::sync::Arc;
10
11#[derive(Clone)]
15pub struct CancelToken {
16 flag: Arc<AtomicBool>,
17}
18
19impl CancelToken {
20 pub fn new() -> Self {
22 Self {
23 flag: Arc::new(AtomicBool::new(false)),
24 }
25 }
26
27 pub fn cancel(&self) {
29 self.flag.store(true, Ordering::Release);
30 }
31
32 pub fn is_cancelled(&self) -> bool {
34 self.flag.load(Ordering::Acquire)
35 }
36}
37
38impl Default for CancelToken {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44pub(crate) fn install_cancel_hook(
56 lua: &mlua::Lua,
57 token: CancelToken,
58 interval: u32,
59) -> Result<(), crate::IsleError> {
60 lua.set_hook(
61 mlua::HookTriggers::new().every_nth_instruction(interval),
62 move |_lua, _debug| {
63 if token.is_cancelled() {
64 Err(mlua::Error::runtime("__isle_cancelled__"))
65 } else {
66 Ok(mlua::VmState::Continue)
67 }
68 },
69 )
70 .map_err(crate::IsleError::from)
71}
72
73pub(crate) fn remove_hook(lua: &mlua::Lua) {
75 lua.remove_hook();
76}
77
78#[cfg(feature = "tokio")]
84pub(crate) fn install_cancel_hook_on_thread(
85 thread: &mlua::Thread,
86 token: CancelToken,
87 interval: u32,
88) -> Result<(), crate::IsleError> {
89 thread
90 .set_hook(
91 mlua::HookTriggers::new().every_nth_instruction(interval),
92 move |_lua, _debug| {
93 if token.is_cancelled() {
94 Err(mlua::Error::runtime("__isle_cancelled__"))
95 } else {
96 Ok(mlua::VmState::Continue)
97 }
98 },
99 )
100 .map_err(crate::IsleError::from)
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn token_default_not_cancelled() {
109 let token = CancelToken::new();
110 assert!(!token.is_cancelled());
111 }
112
113 #[test]
114 fn token_cancel_sets_flag() {
115 let token = CancelToken::new();
116 let clone = token.clone();
117 token.cancel();
118 assert!(clone.is_cancelled());
119 }
120
121 #[test]
122 fn hook_interrupts_lua_loop() {
123 let lua = mlua::Lua::new();
124 let token = CancelToken::new();
125 install_cancel_hook(&lua, token.clone(), 100).unwrap();
126
127 let t = token.clone();
129 std::thread::spawn(move || {
130 std::thread::sleep(std::time::Duration::from_millis(10));
131 t.cancel();
132 });
133
134 let result: mlua::Result<()> = lua.load("while true do end").exec();
135 assert!(result.is_err());
136 let err_msg = result.unwrap_err().to_string();
137 assert!(
138 err_msg.contains("__isle_cancelled__"),
139 "expected cancellation sentinel, got: {err_msg}"
140 );
141 }
142}