1use std::collections::HashMap ;
2use std::sync::Arc ;
3use futures::future::BoxFuture ;
4use futures::lock::Mutex ;
5use futures::task::{ FutureObj, Spawn };
6use thiserror::Error ;
7use wasmtime::component::{ Instance, Val };
8use wasmtime::Store ;
9
10use crate::{ Function, PluginContext, Remap, ReturnKind };
11use crate::resource_wrapper::{ ResourceCreationError, ResourceReceiveError };
12
13type CallLimiter<Ctx> = Box<dyn FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send>;
14
15
16pub struct PluginInstanceSync<Ctx: 'static> {
21 state: PluginState<Ctx>,
22}
23
24pub struct PluginInstanceAsync<Ctx: 'static> {
31 state: Arc<Mutex<PluginState<Ctx>>>,
32 executor: Arc<dyn Spawn + Send + Sync>,
33}
34
35struct PluginState<Ctx: 'static> {
36 store: Store<Ctx>,
37 instance: Instance,
38 interface_remaps: HashMap<String, Remap>,
39 fuel_limiter: Option<CallLimiter<Ctx>>,
40 epoch_limiter: Option<CallLimiter<Ctx>>,
41}
42
43impl<Ctx: std::fmt::Debug + 'static> std::fmt::Debug for PluginInstanceSync<Ctx> {
44 fn fmt( &self, f: &mut std::fmt::Formatter<'_> ) -> std::result::Result<(), std::fmt::Error> {
45 f.debug_struct( "PluginInstanceSync" )
46 .field( "data", &self.state.store.data() )
47 .field( "store", &self.state.store )
48 .field( "interface_remaps", &self.state.interface_remaps )
49 .field( "fuel_limiter", &self.state.fuel_limiter.as_ref().map(| _ | "<closure>" ))
50 .field( "epoch_limiter", &self.state.epoch_limiter.as_ref().map(| _ | "<closure>" ))
51 .finish_non_exhaustive()
52 }
53}
54
55impl<Ctx: 'static> std::fmt::Debug for PluginInstanceAsync<Ctx> {
56 fn fmt( &self, f: &mut std::fmt::Formatter<'_> ) -> std::result::Result<(), std::fmt::Error> {
57 f.debug_struct( "PluginInstanceAsync" )
58 .field( "state", &"<serialized store>" )
59 .field( "executor", &"<executor>" )
60 .finish_non_exhaustive()
61 }
62}
63
64#[derive( Error, Debug )]
70pub enum DispatchError {
71 #[error( "Lock Rejected" )] LockRejected,
73 #[error( "Invalid Interface Path: {0}" )] InvalidInterfacePath( String ),
75 #[error( "Invalid Function: {0}" )] InvalidFunction( String ),
77 #[error( "Missing Response" )] MissingResponse,
79 #[error( "Runtime Exception" )] RuntimeException( wasmtime::Error ),
81 #[error( "Invalid Argument List" )] InvalidArgumentList,
83 #[error( "Unsupported type: {0}" )] UnsupportedType( String ),
85 #[error( "Async executor unavailable" )] ExecutorUnavailable,
87 #[error( "Resource Create Error: {0}" )] ResourceCreationError( #[from] ResourceCreationError ),
89 #[error( "Resource Receive Error: {0}" )] ResourceReceiveError( #[from] ResourceReceiveError ),
91}
92
93impl From<DispatchError> for Val {
94 fn from( error: DispatchError ) -> Val { match error {
95 DispatchError::LockRejected => Val::Variant( "lock-rejected".to_string(), None ),
96 DispatchError::InvalidInterfacePath( package ) => Val::Variant( "invalid-interface-path".to_string(), Some( Box::new( Val::String( package )))),
97 DispatchError::InvalidFunction( function ) => Val::Variant( "invalid-function".to_string(), Some( Box::new( Val::String( function )))),
98 DispatchError::MissingResponse => Val::Variant( "missing-response".to_string(), None ),
99 DispatchError::RuntimeException( exception ) => Val::Variant( "runtime-exception".to_string(), Some( Box::new( Val::String( exception.to_string() )))),
100 DispatchError::InvalidArgumentList => Val::Variant( "invalid-argument-list".to_string(), None ),
101 DispatchError::UnsupportedType( name ) => Val::Variant( "unsupported-type".to_string(), Some( Box::new( Val::String( name )))),
102 DispatchError::ExecutorUnavailable => Val::Variant( "executor-unavailable".to_string(), None ),
103 DispatchError::ResourceCreationError( err ) => err.into(),
104 DispatchError::ResourceReceiveError( err ) => err.into(),
105 }}
106}
107
108impl<Ctx: PluginContext + 'static> PluginInstanceSync<Ctx> {
109 pub(crate) fn new_sync(
110 store: Store<Ctx>,
111 instance: Instance,
112 interface_remaps: HashMap<String, Remap>,
113 fuel_limiter: Option<CallLimiter<Ctx>>,
114 epoch_limiter: Option<CallLimiter<Ctx>>,
115 ) -> Self {
116 Self { state: PluginState {
117 store,
118 instance,
119 interface_remaps,
120 fuel_limiter,
121 epoch_limiter,
122 }}
123 }
124
125 pub(crate) fn dispatch(
126 &mut self,
127 package_name: &str,
128 interface_name: &str,
129 function_name: &str,
130 function: &Function,
131 data: &[Val],
132 ) -> Result<Val, DispatchError> {
133 self.state.dispatch( package_name, interface_name, function_name, function, data )
134 }
135}
136
137impl<Ctx: PluginContext + 'static> PluginInstanceAsync<Ctx> {
138 pub(crate) fn new(
139 store: Store<Ctx>,
140 instance: Instance,
141 interface_remaps: HashMap<String, Remap>,
142 fuel_limiter: Option<CallLimiter<Ctx>>,
143 epoch_limiter: Option<CallLimiter<Ctx>>,
144 executor: impl Spawn + Send + Sync + 'static,
145 ) -> Self {
146 Self {
147 state: Arc::new( Mutex::new( PluginState {
148 store,
149 instance,
150 interface_remaps,
151 fuel_limiter,
152 epoch_limiter,
153 })),
154 executor: Arc::new( executor ),
155 }
156 }
157
158 pub(crate) async fn dispatch_async(
159 &self,
160 package_name: &str,
161 interface_name: &str,
162 function_name: &str,
163 function: &Function,
164 data: &[Val],
165 ) -> Result<Val, DispatchError> {
166 let state = Arc::clone( &self.state );
167 let package_name = package_name.to_string();
168 let interface_name = interface_name.to_string();
169 let function_name = function_name.to_string();
170 let function = function.clone();
171 let data = data.to_vec();
172 let ( response, result ) = futures::channel::oneshot::channel();
173 let task: BoxFuture<'static, ()> = Box::pin( async move {
174 let result = state.lock().await.dispatch_async(
175 &package_name,
176 &interface_name,
177 &function_name,
178 &function,
179 &data,
180 ).await;
181 let _ = response.send( result );
182 });
183 self.executor.spawn_obj( FutureObj::new( task ))
184 .map_err(| _ | DispatchError::ExecutorUnavailable )?;
185 result.await.map_err(| _ | DispatchError::ExecutorUnavailable )?
186 }
187
188}
189
190impl<Ctx: PluginContext + 'static> PluginState<Ctx> {
191 const PLACEHOLDER_VAL: Val = Val::Option( None );
192 const VOID_RETURN_VAL: Val = Val::Option( None );
193
194 fn dispatch(
195 &mut self,
196 package_name: &str,
197 interface_name: &str,
198 function_name: &str,
199 function: &Function,
200 data: &[Val],
201 ) -> Result<Val, DispatchError> {
202 ensure_supported_values( data )?;
203 let mut buffer = self.prepare_call( package_name, interface_name, function_name, function )?;
204 let ( exported_interface_path, exported_function_name ) = self.resolve_export( package_name, interface_name, function_name );
205 let func = self.function( &exported_interface_path, &exported_function_name )?;
206 let call_result = func.call( &mut self.store, data, &mut buffer );
207 Self::finish_call( function, buffer, call_result )
208 }
209
210 async fn dispatch_async(
211 &mut self,
212 package_name: &str,
213 interface_name: &str,
214 function_name: &str,
215 function: &Function,
216 data: &[Val],
217 ) -> Result<Val, DispatchError> {
218 ensure_supported_values( data )?;
219 let mut buffer = self.prepare_call( package_name, interface_name, function_name, function )?;
220 let ( exported_interface_path, exported_function_name ) = self.resolve_export( package_name, interface_name, function_name );
221 let func = self.function( &exported_interface_path, &exported_function_name )?;
222 let call_result = func.call_async( &mut self.store, data, &mut buffer ).await;
223 Self::finish_call( function, buffer, call_result )
224 }
225
226 fn prepare_call(
227 &mut self,
228 package_name: &str,
229 interface_name: &str,
230 function_name: &str,
231 function: &Function,
232 ) -> Result<Vec<Val>, DispatchError> {
233 let canonical_interface_path = format!( "{}/{}", package_name, interface_name );
234 if let Some( mut limiter ) = self.fuel_limiter.take() {
235 let fuel = limiter( &mut self.store, &canonical_interface_path, function_name, function );
236 self.fuel_limiter = Some( limiter );
237 self.store.set_fuel( fuel ).map_err( DispatchError::RuntimeException )?;
238 }
239 if let Some( mut limiter ) = self.epoch_limiter.take() {
240 let ticks = limiter( &mut self.store, &canonical_interface_path, function_name, function );
241 self.epoch_limiter = Some( limiter );
242 self.store.set_epoch_deadline( ticks );
243 }
244 Ok( match function.return_kind() != ReturnKind::Void {
245 true => vec![ Self::PLACEHOLDER_VAL ],
246 false => Vec::with_capacity( 0 ),
247 })
248 }
249
250 fn function( &mut self, interface_path: &str, function_name: &str ) -> Result<wasmtime::component::Func, DispatchError> {
251 let interface_index = self.instance
252 .get_export_index( &mut self.store, None, interface_path )
253 .ok_or_else(|| DispatchError::InvalidInterfacePath( interface_path.to_string() ))?;
254 let func_index = self.instance
255 .get_export_index( &mut self.store, Some( &interface_index ), function_name )
256 .ok_or_else(|| DispatchError::InvalidFunction( format!( "{interface_path}:{function_name}" )))?;
257 self.instance
258 .get_func( &mut self.store, func_index )
259 .ok_or_else(|| DispatchError::InvalidFunction( format!( "{interface_path}:{function_name}" )))
260 }
261
262 fn finish_call(
263 function: &Function,
264 mut buffer: Vec<Val>,
265 call_result: Result<(), wasmtime::Error>,
266 ) -> Result<Val, DispatchError> {
267 call_result.map_err( DispatchError::RuntimeException )?;
268 let result = match function.return_kind() != ReturnKind::Void {
269 true => buffer.pop().ok_or( DispatchError::MissingResponse )?,
270 false => Self::VOID_RETURN_VAL,
271 };
272 ensure_supported_value( &result )?;
273 Ok( result )
274 }
275
276 fn resolve_export( &self, package_name: &str, interface_name: &str, function_name: &str ) -> (String, String) {
277 match self.interface_remaps.get( interface_name ) {
278 Some( remap ) => (
279 format!( "{}/{}", package_name, remap.interface_name( interface_name )),
280 remap.item_name( function_name ).to_string(),
281 ),
282 None => (
283 format!( "{}/{}", package_name, interface_name ),
284 function_name.to_string(),
285 ),
286 }
287 }
288
289}
290
291fn ensure_supported_values( values: &[Val] ) -> Result<(), DispatchError> {
292 values.iter().try_for_each( ensure_supported_value )
293}
294
295fn ensure_supported_value( value: &Val ) -> Result<(), DispatchError> {
296 match value {
297 Val::List( values ) | Val::Tuple( values ) => ensure_supported_values( values ),
298 Val::Map( values ) => values.iter().try_for_each(|( key, value )| {
299 ensure_supported_value( key )?;
300 ensure_supported_value( value )
301 }),
302 Val::Record( values ) => values.iter().try_for_each(|( _, value )| ensure_supported_value( value )),
303 Val::Variant( _, Some( value ))
304 | Val::Option( Some( value ))
305 | Val::Result( Ok( Some( value )))
306 | Val::Result( Err( Some( value ))) => ensure_supported_value( value ),
307 Val::Future( _ ) => Err( DispatchError::UnsupportedType( "future".to_string() )),
308 Val::Stream( _ ) => Err( DispatchError::UnsupportedType( "stream".to_string() )),
309 Val::ErrorContext( _ ) => Err( DispatchError::UnsupportedType( "error-context".to_string() )),
310 _ => Ok(()),
311 }
312}
313
314#[cfg(test)] mod tests { include!( "plugin_instance_tests.rs" ); }