@@ -696,56 +696,91 @@ impl Lua {
696696 }
697697 }
698698
699- /// Sets a thread event callback that will be called when a thread is created or destroyed.
699+ /// Sets a thread creation callback that will be called when a thread is created.
700+ #[cfg(any(feature = "luau", doc))]
701+ #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
702+ pub fn set_thread_creation_callback<F>(&self, callback: F)
703+ where
704+ F: Fn(&Lua, Thread) -> Result<()> + MaybeSend + 'static,
705+ {
706+ let lua = self.lock();
707+ unsafe {
708+ (*lua.extra.get()).thread_creation_callback = Some(XRc::new(callback));
709+ (*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc);
710+ }
711+ }
712+
713+ /// Sets a thread collection callback that will be called when a thread is destroyed.
700714 ///
701- /// The callback is called with a [`Value`] argument that is either:
702- /// - A [`Thread`] object when thread is created
703- /// - A [`LightUserData`] when thread is destroyed
715+ /// Luau GC does not support exceptions during collection, so the callback must be
716+ /// non-panicking. If the callback panics, the program will be aborted.
704717 #[cfg(any(feature = "luau", doc))]
705718 #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
706- pub fn set_thread_event_callback <F>(&self, callback: F)
719+ pub fn set_thread_collection_callback <F>(&self, callback: F)
707720 where
708- F: Fn(&Lua, Value) -> Result<()> + MaybeSend + 'static,
721+ F: Fn(crate::LightUserData) + MaybeSend + 'static,
709722 {
710- unsafe extern "C-unwind" fn userthread_proc(parent: *mut ffi::lua_State, child: *mut ffi::lua_State) {
711- let extra = ExtraData::get(child);
712- let thread_cb = match (*extra).userthread_callback {
723+ let lua = self.lock();
724+ unsafe {
725+ (*lua.extra.get()).thread_collection_callback = Some(XRc::new(callback));
726+ (*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc);
727+ }
728+ }
729+
730+ #[cfg(feature = "luau")]
731+ unsafe extern "C-unwind" fn userthread_proc(parent: *mut ffi::lua_State, child: *mut ffi::lua_State) {
732+ let extra = ExtraData::get(child);
733+ if !parent.is_null() {
734+ // Thread is created
735+ let callback = match (*extra).thread_creation_callback {
713736 Some(ref cb) => cb.clone(),
714737 None => return,
715738 };
716- if XRc::strong_count(&thread_cb ) > 2 {
739+ if XRc::strong_count(&callback ) > 2 {
717740 return; // Don't allow recursion
718741 }
719- let value = match parent.is_null() {
720- // Thread is about to be destroyed, pass light userdata
721- true => Value::LightUserData(crate::LightUserData(child as _)),
722- false => {
723- // Thread is created, pass thread object
724- ffi::lua_pushthread(child);
725- ffi::lua_xmove(child, (*extra).ref_thread, 1);
726- Value::Thread(Thread((*extra).raw_lua().pop_ref_thread(), child))
727- }
728- };
742+ ffi::lua_pushthread(child);
743+ ffi::lua_xmove(child, (*extra).ref_thread, 1);
744+ let value = Thread((*extra).raw_lua().pop_ref_thread(), child);
745+ let _guard = StateGuard::new((*extra).raw_lua(), parent);
729746 callback_error_ext((*extra).raw_lua().state(), extra, false, move |extra, _| {
730- thread_cb ((*extra).lua(), value)
747+ callback ((*extra).lua(), value)
731748 })
732- }
749+ } else {
750+ // Thread is about to be collected
751+ let callback = match (*extra).thread_collection_callback {
752+ Some(ref cb) => cb.clone(),
753+ None => return,
754+ };
733755
734- // Set thread callback
735- let lua = self.lock();
736- unsafe {
737- (*lua.extra.get()).userthread_callback = Some(XRc::new(callback));
738- (*ffi::lua_callbacks(lua.main_state())).userthread = Some(userthread_proc);
756+ // We need to wrap the callback call in non-unwind function as it's not safe to unwind when
757+ // Luau GC is running.
758+ // This will trigger `abort()` if the callback panics.
759+ unsafe extern "C" fn run_callback(
760+ callback: *const crate::types::ThreadCollectionCallback,
761+ value: *mut ffi::lua_State,
762+ ) {
763+ (*callback)(crate::LightUserData(value as _));
764+ }
765+
766+ (*extra).running_gc = true;
767+ run_callback(&callback, child);
768+ (*extra).running_gc = false;
739769 }
740770 }
741771
742- /// Removes any thread event callback previously set by `set_thread_event_callback`.
772+ /// Removes any thread creation or collection callbacks previously set by
773+ /// [`Lua::set_thread_creation_callback`] or [`Lua::set_thread_collection_callback`].
774+ ///
775+ /// This function has no effect if a thread callbacks were not previously set.
743776 #[cfg(any(feature = "luau", doc))]
744777 #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
745- pub fn remove_thread_event_callback (&self) {
778+ pub fn remove_thread_callbacks (&self) {
746779 let lua = self.lock();
747780 unsafe {
748- (*lua.extra.get()).userthread_callback = None;
781+ let extra = lua.extra.get();
782+ (*extra).thread_creation_callback = None;
783+ (*extra).thread_collection_callback = None;
749784 (*ffi::lua_callbacks(lua.main_state())).userthread = None;
750785 }
751786 }
@@ -2039,8 +2074,8 @@ impl Lua {
20392074 pub(crate) fn lock(&self) -> ReentrantMutexGuard<RawLua> {
20402075 let rawlua = self.raw.lock();
20412076 #[cfg(feature = "luau")]
2042- if unsafe { (*rawlua.extra.get()).running_userdata_gc } {
2043- panic!("Luau VM is suspended while userdata destructor is running");
2077+ if unsafe { (*rawlua.extra.get()).running_gc } {
2078+ panic!("Luau VM is suspended while GC is running");
20442079 }
20452080 rawlua
20462081 }
@@ -2066,8 +2101,8 @@ impl WeakLua {
20662101 pub(crate) fn lock(&self) -> LuaGuard {
20672102 let guard = LuaGuard::new(self.0.upgrade().expect("Lua instance is destroyed"));
20682103 #[cfg(feature = "luau")]
2069- if unsafe { (*guard.extra.get()).running_userdata_gc } {
2070- panic!("Luau VM is suspended while userdata destructor is running");
2104+ if unsafe { (*guard.extra.get()).running_gc } {
2105+ panic!("Luau VM is suspended while GC is running");
20712106 }
20722107 guard
20732108 }
0 commit comments