@@ -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