11use std:: {
22 ffi:: { c_char, c_void} ,
3- sync:: LazyLock ,
3+ sync:: { LazyLock , Mutex } ,
44} ;
55
66use url:: Url ;
77
88use crate :: { Error , Id } ;
99
10- pub static RUNTIME : LazyLock < tokio:: runtime:: Handle > = LazyLock :: new ( || {
10+ pub static RUNTIME : LazyLock < Mutex < tokio:: runtime:: Handle > > = LazyLock :: new ( || {
1111 let runtime = tokio:: runtime:: Builder :: new_current_thread ( )
1212 . enable_all ( )
1313 . build ( )
@@ -21,15 +21,20 @@ pub static RUNTIME: LazyLock<tokio::runtime::Handle> = LazyLock::new(|| {
2121 } )
2222 . expect ( "failed to spawn runtime thread" ) ;
2323
24- handle
24+ Mutex :: new ( handle)
2525} ) ;
2626
27- /// Runs the provided function while holding the global state and runtime lock .
27+ /// Runs the provided function in the runtime context .
2828/// Additionally, we convert the return code to a C-compatible return value.
29+ ///
30+ /// Uses a mutex to ensure Handle::enter() guards are dropped in LIFO order,
31+ /// as required by tokio to avoid panics in multi-threaded FFI contexts.
2932pub fn enter < C : ReturnCode , F : FnOnce ( ) -> C > ( f : F ) -> i32 {
30- // TODO Do we need a mutex to make sure RUNTIME is dropped in order?
31- // I think this might panic otherwise if libmoq is used in a multi-threaded context.
32- let _guard = RUNTIME . enter ( ) ;
33+ // NOTE: I think we need a mutex because Handle::enter() needs to be dropped in LIFO order.
34+ // If this starts to become a bottleneck, we might have to rethink our runtime model.
35+ let handle = RUNTIME . lock ( ) . unwrap ( ) ;
36+ let _guard = handle. enter ( ) ;
37+
3338 match std:: panic:: catch_unwind ( std:: panic:: AssertUnwindSafe ( f) ) {
3439 Ok ( ret) => ret. code ( ) ,
3540 Err ( _) => Error :: Panic . code ( ) ,
0 commit comments