@@ -11,10 +11,14 @@ use crate::quickjsruntimeadapter::{
1111use crate :: quickjsvalueadapter:: QuickJsValueAdapter ;
1212use crate :: reflection;
1313use crate :: values:: JsValueFacade ;
14+ use either:: { Either , Left , Right } ;
1415use hirofa_utils:: eventloop:: EventLoop ;
1516use hirofa_utils:: task_manager:: TaskManager ;
1617use libquickjs_sys as q;
18+ use lru:: LruCache ;
19+ use std:: cell:: RefCell ;
1720use std:: future:: Future ;
21+ use std:: num:: NonZeroUsize ;
1822use std:: pin:: Pin ;
1923use std:: rc:: Rc ;
2024use std:: sync:: { Arc , Weak } ;
@@ -76,7 +80,7 @@ impl QuickjsRuntimeFacadeInner {
7680 pub fn add_rt_task_to_event_loop < C , R : Send + ' static > (
7781 & self ,
7882 consumer : C ,
79- ) -> impl Future < Output = R >
83+ ) -> impl Future < Output = R >
8084 where
8185 C : FnOnce ( & QuickJsRuntimeAdapter ) -> R + Send + ' static ,
8286 {
@@ -121,7 +125,7 @@ impl QuickjsRuntimeFacadeInner {
121125 } )
122126 }
123127
124- pub fn add_task_to_event_loop < C , R : Send + ' static > ( & self , task : C ) -> impl Future < Output = R >
128+ pub fn add_task_to_event_loop < C , R : Send + ' static > ( & self , task : C ) -> impl Future < Output = R >
125129 where
126130 C : FnOnce ( ) -> R + Send + ' static ,
127131 {
@@ -291,7 +295,7 @@ impl QuickJsRuntimeFacade {
291295 self . exe_task_in_event_loop ( || {
292296 let context_ids = QuickJsRuntimeAdapter :: get_context_ids ( ) ;
293297 for id in context_ids {
294- QuickJsRuntimeAdapter :: remove_context ( id. as_str ( ) ) ;
298+ let _ = QuickJsRuntimeAdapter :: remove_context ( id. as_str ( ) ) ;
295299 }
296300 } ) ;
297301 }
@@ -312,7 +316,7 @@ impl QuickJsRuntimeFacade {
312316 self . inner . exe_task_in_event_loop ( task)
313317 }
314318
315- pub fn add_task_to_event_loop < C , R : Send + ' static > ( & self , task : C ) -> impl Future < Output = R >
319+ pub fn add_task_to_event_loop < C , R : Send + ' static > ( & self , task : C ) -> impl Future < Output = R >
316320 where
317321 C : FnOnce ( ) -> R + Send + ' static ,
318322 {
@@ -333,7 +337,7 @@ impl QuickJsRuntimeFacade {
333337 pub fn add_rt_task_to_event_loop < C , R : Send + ' static > (
334338 & self ,
335339 task : C ,
336- ) -> impl Future < Output = R >
340+ ) -> impl Future < Output = R >
337341 where
338342 C : FnOnce ( & QuickJsRuntimeAdapter ) -> R + Send + ' static ,
339343 {
@@ -422,8 +426,8 @@ impl QuickJsRuntimeFacade {
422426 ) -> Result < ( ) , JsError >
423427 where
424428 F : Fn ( & QuickJsRealmAdapter , Vec < JsValueFacade > ) -> Result < JsValueFacade , JsError >
425- + Send
426- + ' static ,
429+ + Send
430+ + ' static ,
427431 {
428432 let name = name. to_string ( ) ;
429433
@@ -479,9 +483,9 @@ impl QuickJsRuntimeFacade {
479483 }
480484
481485 /// add an async task the the "helper" thread pool
482- pub fn add_helper_task_async < R : Send + ' static , T : Future < Output = R > + Send + ' static > (
486+ pub fn add_helper_task_async < R : Send + ' static , T : Future < Output = R > + Send + ' static > (
483487 task : T ,
484- ) -> impl Future < Output = Result < R , JoinError > > {
488+ ) -> impl Future < Output = Result < R , JoinError > > {
485489 log:: trace!( "adding an async helper task" ) ;
486490 HELPER_TASKS . add_task_async ( task)
487491 }
@@ -506,59 +510,96 @@ impl QuickJsRuntimeFacade {
506510 }
507511
508512 /// drop a context which was created earlier with a call to [create_context()](struct.EsRuntime.html#method.create_context)
509- pub fn drop_context ( & self , id : & str ) {
513+ pub fn drop_context ( & self , id : & str ) -> anyhow :: Result < ( ) > {
510514 let id = id. to_string ( ) ;
511515 self . inner
512516 . event_loop
513517 . exe ( move || QuickJsRuntimeAdapter :: remove_context ( id. as_str ( ) ) )
514518 }
515519}
516520
521+ thread_local ! {
522+ // Each thread has its own LRU cache with capacity 128 to limit the number of auto-created contexts
523+ // todo make this configurable via env..
524+ static REALM_ID_LRU_CACHE : RefCell <LruCache <String , ( ) >> = RefCell :: new( LruCache :: new( NonZeroUsize :: new( 8 ) . unwrap( ) ) ) ;
525+ }
526+
517527fn loop_realm_func <
518528 R : Send + ' static ,
519529 C : FnOnce ( & QuickJsRuntimeAdapter , & QuickJsRealmAdapter ) -> R + Send + ' static ,
520530> (
521531 realm_name : Option < String > ,
522532 consumer : C ,
523533) -> R {
524- let res = QuickJsRuntimeAdapter :: do_with ( |q_js_rt| {
534+ // housekeeping, lru map for realms
535+ // the problem with doing those in drop in a realm is that finalizers cant find the realm anymore
536+ // so we need to actively delete realms here instead of just making runtimeadapter::context a lru cache
537+
538+ if let Some ( realm_str) = realm_name. as_ref ( ) {
539+ REALM_ID_LRU_CACHE . with ( |cache_cell| {
540+ let mut cache = cache_cell. borrow_mut ( ) ;
541+ // it's ok if this str does not yet exist
542+ cache. promote ( realm_str) ;
543+ } ) ;
544+ }
545+
546+ // run in existing realm
547+
548+ let res: Either < R , C > = QuickJsRuntimeAdapter :: do_with ( |q_js_rt| {
525549 if let Some ( realm_str) = realm_name. as_ref ( ) {
526550 if let Some ( realm) = q_js_rt. get_realm ( realm_str) {
527- ( Some ( consumer ( q_js_rt, realm) ) , None )
551+ Left ( consumer ( q_js_rt, realm) )
528552 } else {
529- ( None , Some ( consumer) )
553+ Right ( consumer)
530554 }
531555 } else {
532- ( Some ( consumer ( q_js_rt, q_js_rt. get_main_realm ( ) ) ) , None )
556+ Left ( consumer ( q_js_rt, q_js_rt. get_main_realm ( ) ) )
533557 }
534558 } ) ;
535559
536- if let Some ( res) = res. 0 {
537- res
538- } else {
539- // create realm first
540- let consumer = res. 1 . unwrap ( ) ;
541- let realm_str = realm_name. expect ( "invalid state" ) ;
560+ match res {
561+ Left ( r) => r,
562+ Right ( consumer) => {
563+ // create realm first
564+ // if more than max present, drop the least used realm
565+
566+ let realm_str = realm_name. expect ( "invalid state" ) ;
567+
568+ REALM_ID_LRU_CACHE . with ( |cache_cell| {
569+ let mut cache = cache_cell. borrow_mut ( ) ;
570+ // it's ok if this str does not yet exist
571+ if cache. len ( ) == cache. cap ( ) . get ( ) {
572+ if let Some ( ( evicted_key, _evicted_value) ) = cache. pop_lru ( ) {
573+ // cleanup evicted key
574+ QuickJsRuntimeAdapter :: remove_context ( evicted_key. as_str ( ) ) . expect ( "could not destroy realm" ) ;
575+ }
576+ }
577+ cache. put ( realm_str. to_string ( ) , ( ) ) ;
578+ } ) ;
579+
580+ // create realm
542581
543- QuickJsRuntimeAdapter :: do_with_mut ( |m_rt| {
544- let ctx = QuickJsRealmAdapter :: new ( realm_str. to_string ( ) , m_rt) ;
545- m_rt. contexts . insert ( realm_str. to_string ( ) , ctx) ;
546- } ) ;
547582
548- QuickJsRuntimeAdapter :: do_with ( |q_js_rt| {
549- let realm = q_js_rt
550- . get_realm ( realm_str. as_str ( ) )
551- . expect ( "invalid state" ) ;
552- let hooks = & * q_js_rt. context_init_hooks . borrow ( ) ;
553- for hook in hooks {
554- let res = hook ( q_js_rt, realm) ;
555- if res. is_err ( ) {
556- panic ! ( "realm init hook failed: {}" , res. err( ) . unwrap( ) ) ;
583+ QuickJsRuntimeAdapter :: do_with_mut ( |m_rt| {
584+ let ctx = QuickJsRealmAdapter :: new ( realm_str. to_string ( ) , m_rt) ;
585+ m_rt. contexts . insert ( realm_str. to_string ( ) , ctx) ;
586+ } ) ;
587+
588+ QuickJsRuntimeAdapter :: do_with ( |q_js_rt| {
589+ let realm = q_js_rt
590+ . get_realm ( realm_str. as_str ( ) )
591+ . expect ( "invalid state" ) ;
592+ let hooks = & * q_js_rt. context_init_hooks . borrow ( ) ;
593+ for hook in hooks {
594+ let res = hook ( q_js_rt, realm) ;
595+ if res. is_err ( ) {
596+ panic ! ( "realm init hook failed: {}" , res. err( ) . unwrap( ) ) ;
597+ }
557598 }
558- }
559599
560- consumer ( q_js_rt, realm)
561- } )
600+ consumer ( q_js_rt, realm)
601+ } )
602+ }
562603 }
563604}
564605
@@ -570,16 +611,9 @@ impl QuickJsRuntimeFacade {
570611 . exe ( move || QuickJsRuntimeAdapter :: create_context ( name. as_str ( ) ) )
571612 }
572613
573- pub fn destroy_realm ( & self , name : & str ) -> Result < ( ) , JsError > {
614+ pub fn destroy_realm ( & self , name : & str ) -> anyhow :: Result < ( ) > {
574615 let name = name. to_string ( ) ;
575- self . exe_task_in_event_loop ( move || {
576- QuickJsRuntimeAdapter :: do_with_mut ( |rt| {
577- if rt. get_realm ( name. as_str ( ) ) . is_some ( ) {
578- rt. remove_realm ( name. as_str ( ) ) ;
579- }
580- Ok ( ( ) )
581- } )
582- } )
616+ self . exe_task_in_event_loop ( move || QuickJsRuntimeAdapter :: remove_context ( name. as_str ( ) ) )
583617 }
584618
585619 pub fn has_realm ( & self , name : & str ) -> Result < bool , JsError > {
@@ -613,7 +647,7 @@ impl QuickJsRuntimeFacade {
613647 > (
614648 & self ,
615649 consumer : C ,
616- ) -> Pin < Box < dyn Future < Output = R > + Send > > {
650+ ) -> Pin < Box < dyn Future < Output = R > + Send > > {
617651 Box :: pin ( self . add_rt_task_to_event_loop ( consumer) )
618652 }
619653
@@ -644,7 +678,7 @@ impl QuickJsRuntimeFacade {
644678 & self ,
645679 realm_name : Option < & str > ,
646680 consumer : C ,
647- ) -> Pin < Box < dyn Future < Output = R > > > {
681+ ) -> Pin < Box < dyn Future < Output = R > > > {
648682 let realm_name = realm_name. map ( |s| s. to_string ( ) ) ;
649683 Box :: pin ( self . add_task_to_event_loop ( || loop_realm_func ( realm_name, consumer) ) )
650684 }
@@ -679,7 +713,7 @@ impl QuickJsRuntimeFacade {
679713 & self ,
680714 realm_name : Option < & str > ,
681715 script : Script ,
682- ) -> Pin < Box < dyn Future < Output = Result < JsValueFacade , JsError > > > > {
716+ ) -> Pin < Box < dyn Future < Output = Result < JsValueFacade , JsError > > > > {
683717 self . loop_realm ( realm_name, |_rt, realm| {
684718 let res = realm. eval ( script) ;
685719 match res {
@@ -752,7 +786,7 @@ impl QuickJsRuntimeFacade {
752786 & self ,
753787 realm_name : Option < & str > ,
754788 script : Script ,
755- ) -> Pin < Box < dyn Future < Output = Result < JsValueFacade , JsError > > > > {
789+ ) -> Pin < Box < dyn Future < Output = Result < JsValueFacade , JsError > > > > {
756790 self . loop_realm ( realm_name, |_rt, realm| {
757791 let res = realm. eval_module ( script) ?;
758792 realm. to_js_value_facade ( & res)
@@ -868,7 +902,7 @@ impl QuickJsRuntimeFacade {
868902 namespace : & [ & str ] ,
869903 method_name : & str ,
870904 args : Vec < JsValueFacade > ,
871- ) -> Pin < Box < dyn Future < Output = Result < JsValueFacade , JsError > > > > {
905+ ) -> Pin < Box < dyn Future < Output = Result < JsValueFacade , JsError > > > > {
872906 let movable_namespace: Vec < String > = namespace. iter ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ;
873907 let movable_method_name = method_name. to_string ( ) ;
874908
@@ -951,7 +985,6 @@ lazy_static! {
951985
952986#[ cfg( test) ]
953987pub mod tests {
954-
955988 use crate :: facades:: QuickJsRuntimeFacade ;
956989 use crate :: jsutils:: modules:: { NativeModuleLoader , ScriptModuleLoader } ;
957990 use crate :: jsutils:: JsError ;
@@ -1319,7 +1352,7 @@ pub mod abstraction_tests {
13191352 . to_js_value_facade ( & value_adapter)
13201353 . expect ( "conversion failed" )
13211354 } )
1322- . await
1355+ . await
13231356 }
13241357
13251358 #[ test]
@@ -1393,8 +1426,8 @@ pub mod abstraction_tests {
13931426 "# ,
13941427 ) ,
13951428 )
1396- . await
1397- . expect ( "script failed" ) ;
1429+ . await
1430+ . expect ( "script failed" ) ;
13981431
13991432 // create a user obj
14001433 let test_user_input = User {
@@ -1446,8 +1479,8 @@ pub mod abstraction_tests {
14461479 "# ,
14471480 ) ,
14481481 )
1449- . await
1450- . expect ( "script failed" ) ;
1482+ . await
1483+ . expect ( "script failed" ) ;
14511484
14521485 // create a user obj
14531486 let test_user_input = User {
@@ -1477,4 +1510,33 @@ pub mod abstraction_tests {
14771510 assert_eq ! ( user_output. name. as_str( ) , "proc_Mister" ) ;
14781511 assert_eq ! ( user_output. last_name. as_str( ) , "proc_Anderson" ) ;
14791512 }
1513+
1514+ #[ tokio:: test]
1515+ async fn test_realm_lifetime ( ) -> anyhow:: Result < ( ) > {
1516+ let rt = QuickJsRuntimeBuilder :: new ( ) . build ( ) ;
1517+
1518+ rt. add_rt_task_to_event_loop ( |rt| {
1519+ println ! ( "ctx list: [{}]" , rt. list_contexts( ) . join( "," ) . as_str( ) ) ;
1520+ } ) . await ;
1521+
1522+ for x in 0 ..10240 {
1523+ let rid = format ! ( "x_{x}" ) ;
1524+ let _ = rt. eval ( Some ( rid. as_str ( ) ) , Script :: new ( "x.js" , "const a = 1;" ) ) . await ;
1525+ }
1526+
1527+ rt. add_rt_task_to_event_loop ( |rt| {
1528+ println ! ( "ctx list: [{}]" , rt. list_contexts( ) . join( "," ) . as_str( ) ) ;
1529+ } ) . await ;
1530+
1531+ for x in 0 ..8 {
1532+ let rid = format ! ( "x_{x}" ) ;
1533+ let _ = rt. eval ( Some ( rid. as_str ( ) ) , Script :: new ( "x.js" , "const a = 1;" ) ) . await ;
1534+ }
1535+
1536+ rt. add_rt_task_to_event_loop ( |rt| {
1537+ println ! ( "ctx list: [{}]" , rt. list_contexts( ) . join( "," ) . as_str( ) ) ;
1538+ } ) . await ;
1539+
1540+ Ok ( ( ) )
1541+ }
14801542}
0 commit comments