@@ -6,6 +6,7 @@ use crate::{define_rb_intern, helpers::SymbolEnum};
66use lazy_static:: lazy_static;
77use magnus:: block:: Proc ;
88use magnus:: value:: ReprValue ;
9+ use magnus:: Class ;
910use magnus:: {
1011 class, function, gc:: Marker , method, typed_data:: Obj , value:: Opaque , DataTypeFunctions , Error ,
1112 IntoValue , Module , Object , RArray , RHash , RString , Ruby , Symbol , TryConvert , TypedData , Value ,
@@ -19,14 +20,49 @@ use std::future::Future;
1920use std:: net:: SocketAddr ;
2021use std:: path:: Path ;
2122use std:: pin:: Pin ;
22- use std:: sync:: Arc ;
23+ use std:: sync:: { Arc , Mutex } ;
2324use std:: { fs:: File , path:: PathBuf } ;
2425use wasmtime_wasi:: cli:: { InputFile , OutputFile } ;
2526use wasmtime_wasi:: p1:: WasiP1Ctx ;
2627use wasmtime_wasi:: p2:: pipe:: MemoryInputPipe ;
2728use wasmtime_wasi:: sockets:: SocketAddrUse ;
2829use wasmtime_wasi:: { DirPerms , FilePerms , WasiCtx , WasiCtxBuilder } ;
2930
31+ /// Storage for errors that occur in socket_addr_check callbacks.
32+ /// We store (class_name, message) tuples since magnus::Error is not Send+Sync.
33+ /// Storing the class name allows us to reconstruct the original exception type.
34+ type SocketErrorStorage = Arc < Mutex < Option < ( String , String ) > > > ;
35+
36+ /// Container for data that needs to be retained by the Store for WASI functionality.
37+ /// This includes Ruby procs (for GC marking) and error storages (for error propagation).
38+ #[ derive( TypedData ) ]
39+ #[ magnus( class = "Wasmtime::WasiRetainedData" , mark, free_immediately) ]
40+ pub struct WasiRetainedData {
41+ proc : Option < Opaque < Proc > > ,
42+ error_storage : Option < SocketErrorStorage > ,
43+ }
44+
45+ impl DataTypeFunctions for WasiRetainedData {
46+ fn mark ( & self , marker : & Marker ) {
47+ if let Some ( proc) = self . proc {
48+ marker. mark ( proc) ;
49+ }
50+ }
51+ }
52+
53+ impl WasiRetainedData {
54+ pub fn new ( proc : Option < Proc > , error_storage : Option < SocketErrorStorage > ) -> Self {
55+ Self {
56+ proc : proc. map ( |p| p. into ( ) ) ,
57+ error_storage,
58+ }
59+ }
60+
61+ pub fn error_storage ( & self ) -> Option < & SocketErrorStorage > {
62+ self . error_storage . as_ref ( )
63+ }
64+ }
65+
3066define_rb_intern ! (
3167 READ => "read" ,
3268 WRITE => "write" ,
@@ -106,6 +142,7 @@ impl MappedDirectory {
106142
107143struct SocketAddrProc {
108144 proc : Proc ,
145+ error_storage : SocketErrorStorage ,
109146}
110147
111148impl SocketAddrProc {
@@ -118,8 +155,18 @@ impl SocketAddrProc {
118155
119156 match self . proc . call :: < _ , Value > ( ( addr_str, use_sym) ) {
120157 Ok ( result) => bool:: try_convert ( result) . unwrap_or ( false ) ,
121- Err ( _) => {
122- // Exception in Ruby block, deny access
158+ Err ( error) => {
159+ // Store both class name and message for later retrieval
160+ if let Ok ( mut storage) = self . error_storage . lock ( ) {
161+ // Get the exception class name from the error's value
162+ let class_name = if let Some ( exception_value) = error. value ( ) {
163+ unsafe { exception_value. class ( ) . name ( ) . into_owned ( ) }
164+ } else {
165+ "RuntimeError" . to_string ( )
166+ } ;
167+ * storage = Some ( ( class_name, error. to_string ( ) ) ) ;
168+ }
169+ // Deny access when an exception occurs
123170 false
124171 }
125172 }
@@ -459,21 +506,22 @@ impl WasiConfig {
459506 }
460507
461508 pub fn build_p1 ( & self , ruby : & Ruby ) -> Result < ( WasiP1Ctx , Option < Value > ) , Error > {
462- let ( mut builder, proc_value ) = self . build_impl ( ruby) ?;
509+ let ( mut builder, retained_data ) = self . build_impl ( ruby) ?;
463510 let ctx = builder. build_p1 ( ) ;
464- Ok ( ( ctx, proc_value ) )
511+ Ok ( ( ctx, retained_data ) )
465512 }
466513
467514 pub fn build ( & self , ruby : & Ruby ) -> Result < ( WasiCtx , Option < Value > ) , Error > {
468- let ( mut builder, proc_value ) = self . build_impl ( ruby) ?;
515+ let ( mut builder, retained_data ) = self . build_impl ( ruby) ?;
469516 let ctx = builder. build ( ) ;
470- Ok ( ( ctx, proc_value ) )
517+ Ok ( ( ctx, retained_data ) )
471518 }
472519
473520 fn build_impl ( & self , ruby : & Ruby ) -> Result < ( WasiCtxBuilder , Option < Value > ) , Error > {
474521 let mut builder = WasiCtxBuilder :: new ( ) ;
475522 let inner = self . inner . borrow ( ) ;
476523 let mut proc_to_retain = None ;
524+ let mut error_storage_to_retain = None ;
477525
478526 if let Some ( stdin) = inner. stdin . as_ref ( ) {
479527 match stdin {
@@ -550,16 +598,21 @@ impl WasiConfig {
550598
551599 if let Some ( check_proc) = inner. socket_addr_check . as_ref ( ) {
552600 let proc = ruby. get_inner ( * check_proc) ;
553- let socket_addr_proc = Arc :: new ( SocketAddrProc { proc } ) ;
601+ let error_storage = Arc :: new ( Mutex :: new ( None ) ) ;
602+ let socket_addr_proc = Arc :: new ( SocketAddrProc {
603+ proc,
604+ error_storage : error_storage. clone ( ) ,
605+ } ) ;
554606
555607 builder. socket_addr_check ( move |addr, use_| {
556608 let socket_addr_proc = socket_addr_proc. clone ( ) ;
557609 Box :: pin ( async move { socket_addr_proc. call ( addr, use_) } )
558610 as Pin < Box < dyn Future < Output = bool > + Send + Sync > >
559611 } ) ;
560612
561- // Store the Proc as a Value so the Store can retain it
562- proc_to_retain = Some ( proc. as_value ( ) ) ;
613+ // Store the Proc and error storage together
614+ proc_to_retain = Some ( proc) ;
615+ error_storage_to_retain = Some ( error_storage) ;
563616 }
564617
565618 for mapped_dir in & inner. mapped_directories {
@@ -578,7 +631,15 @@ impl WasiConfig {
578631 . map_err ( |e| error ! ( "{}" , e) ) ?;
579632 }
580633
581- Ok ( ( builder, proc_to_retain) )
634+ // Wrap retained data in a single Ruby object if we have anything to retain
635+ let retained_value = if proc_to_retain. is_some ( ) || error_storage_to_retain. is_some ( ) {
636+ let retained_data = WasiRetainedData :: new ( proc_to_retain, error_storage_to_retain) ;
637+ Some ( ruby. obj_wrap ( retained_data) . as_value ( ) )
638+ } else {
639+ None
640+ } ;
641+
642+ Ok ( ( builder, retained_value) )
582643 }
583644
584645 fn check_determinism ( & self ) -> Result < ( ) , Error > {
@@ -589,7 +650,9 @@ impl WasiConfig {
589650 || inner. allow_ip_name_lookup == Some ( true ) ;
590651
591652 if inner. deterministic && has_network_enabled {
592- Err ( error ! ( "Sources of indeterminism cannot be combined with determinism" ) )
653+ Err ( error ! (
654+ "Sources of indeterminism cannot be combined with determinism"
655+ ) )
593656 } else {
594657 Ok ( ( ) )
595658 }
@@ -608,6 +671,9 @@ pub fn file_w(path: RString) -> Result<File, Error> {
608671}
609672
610673pub fn init ( ruby : & Ruby ) -> Result < ( ) , Error > {
674+ // Register internal WasiRetainedData class (not exposed to Ruby API)
675+ root ( ) . define_class ( "WasiRetainedData" , ruby. class_object ( ) ) ?;
676+
611677 let class = root ( ) . define_class ( "WasiConfig" , ruby. class_object ( ) ) ?;
612678 class. define_singleton_method ( "new" , function ! ( WasiConfig :: new, 0 ) ) ?;
613679
0 commit comments