Skip to main content

wasm_link/
plugin_instance.rs

1use thiserror::Error ;
2use wasmtime::component::{ Instance, Val };
3use wasmtime::Store ;
4
5use crate::{ Function, PluginContext, ReturnKind };
6use crate::resource_wrapper::{ ResourceCreationError, ResourceReceiveError };
7
8
9
10/// An instantiated plugin with its store and instance, ready for dispatch.
11///
12/// Created by calling [`Plugin::instantiate`]( crate::Plugin::instantiate ) or
13/// [`Plugin::link`]( crate::Plugin::link ). The plugin holds its wasmtime [`Store`]
14/// and can execute function calls.
15pub struct PluginInstance<Ctx: 'static> {
16	pub(crate) store: Store<Ctx>,
17	pub(crate) instance: Instance,
18	#[allow( clippy::type_complexity )]
19	pub(crate) fuel_limiter: Option<Box<dyn FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send>>,
20	#[allow( clippy::type_complexity )]
21	pub(crate) epoch_limiter: Option<Box<dyn FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send>>,
22}
23
24impl<Ctx: std::fmt::Debug + 'static> std::fmt::Debug for PluginInstance<Ctx> {
25	fn fmt( &self, f: &mut std::fmt::Formatter<'_> ) -> std::result::Result<(), std::fmt::Error> {
26		f.debug_struct( "PluginInstance" )
27			.field( "data", &self.store.data() )
28			.field( "store", &self.store )
29			.field( "fuel_limiter", &self.fuel_limiter.as_ref().map(| _ | "<closure>" ))
30			.field( "epoch_limiter", &self.epoch_limiter.as_ref().map(| _ | "<closure>" ))
31			.finish_non_exhaustive()
32	}
33}
34
35/// Errors that can occur when dispatching a function call to plugins.
36///
37/// Returned inside a cardinality wrapper from
38/// [`Binding::dispatch`]( crate::binding::Binding::dispatch )
39/// when a function call fails at runtime.
40#[derive( Error, Debug )]
41pub enum DispatchError {
42	/// Failed to acquire lock on plugin instance (another call is in progress).
43	#[error( "Lock Rejected" )] LockRejected,
44	/// The specified interface path doesn't match any known interface.
45	#[error( "Invalid Interface Path: {0}" )] InvalidInterfacePath( String ),
46	/// The specified function doesn't exist on the interface.
47	#[error( "Invalid Function: {0}" )] InvalidFunction( String ),
48	/// Function was expected to return a value but didn't.
49	#[error( "Missing Response" )] MissingResponse,
50	/// The WASM function threw an exception during execution.
51	#[error( "Runtime Exception" )] RuntimeException( wasmtime::Error ),
52	/// The provided arguments don't match the function signature.
53	#[error( "Invalid Argument List" )] InvalidArgumentList,
54	/// Async types (`Future`, `Stream`, `ErrorContext`) are not yet supported for cross-plugin transfer.
55	#[error( "Unsupported type: {0}" )] UnsupportedType( String ),
56	/// Failed to create a resource handle for cross-plugin transfer.
57	#[error( "Resource Create Error: {0}" )] ResourceCreationError( #[from] ResourceCreationError ),
58	/// Failed to receive a resource handle from another plugin.
59	#[error( "Resource Receive Error: {0}" )] ResourceReceiveError( #[from] ResourceReceiveError ),
60}
61
62impl From<DispatchError> for Val {
63	fn from( error: DispatchError ) -> Val { match error {
64		DispatchError::LockRejected => Val::Variant( "lock-rejected".to_string(), None ),
65		DispatchError::InvalidInterfacePath( package ) => Val::Variant( "invalid-interface-path".to_string(), Some( Box::new( Val::String( package )))),
66		DispatchError::InvalidFunction( function ) => Val::Variant( "invalid-function".to_string(), Some( Box::new( Val::String( function )))),
67		DispatchError::MissingResponse => Val::Variant( "missing-response".to_string(), None ),
68		DispatchError::RuntimeException( exception ) => Val::Variant( "runtime-exception".to_string(), Some( Box::new( Val::String( exception.to_string() )))),
69		DispatchError::InvalidArgumentList => Val::Variant( "invalid-argument-list".to_string(), None ),
70		DispatchError::UnsupportedType( name ) => Val::Variant( "unsupported-type".to_string(), Some( Box::new( Val::String( name )))),
71		DispatchError::ResourceCreationError( err ) => err.into(),
72		DispatchError::ResourceReceiveError( err ) => err.into(),
73	}}
74}
75
76impl<Ctx: PluginContext + 'static> PluginInstance<Ctx> {
77
78	const PLACEHOLDER_VAL: Val = Val::Tuple( vec![] );
79
80	pub(crate) fn dispatch(
81		&mut self,
82		interface_path: &str,
83		function_name: &str,
84		function: &Function,
85		data: &[Val],
86	) -> Result<Val, DispatchError> {
87
88		let mut buffer = match function.return_kind() != ReturnKind::Void {
89			true => vec![ Self::PLACEHOLDER_VAL ],
90			false => Vec::with_capacity( 0 ),
91		};
92
93		let fuel_was_set = if let Some( mut limiter ) = self.fuel_limiter.take() {
94			let fuel = limiter( &mut self.store, interface_path, function_name, function );
95			self.fuel_limiter = Some( limiter );
96			self.store.set_fuel( fuel ).map_err( DispatchError::RuntimeException )?;
97			true
98		} else { false };
99
100		if let Some( mut limiter ) = self.epoch_limiter.take() {
101			let ticks = limiter( &mut self.store, interface_path, function_name, function );
102			self.epoch_limiter = Some( limiter );
103			self.store.set_epoch_deadline( ticks );
104		}
105
106		let interface_index = self.instance
107			.get_export_index( &mut self.store, None, interface_path )
108			.ok_or( DispatchError::InvalidInterfacePath( interface_path.to_string() ))?;
109		let func_index = self.instance
110			.get_export_index( &mut self.store, Some( &interface_index ), function_name )
111			.ok_or( DispatchError::InvalidFunction( format!( "{}:{}", interface_path, function_name )))?;
112		let func = self.instance
113			.get_func( &mut self.store, func_index )
114			.ok_or( DispatchError::InvalidFunction( format!( "{}:{}", interface_path, function_name )))?;
115		let call_result = func.call( &mut self.store, data, &mut buffer );
116
117		// Reset fuel to 0 after call to prevent leakage to subsequent calls
118		if fuel_was_set { let _ = self.store.set_fuel( 0 ); }
119
120		call_result.map_err( DispatchError::RuntimeException )?;
121		let _ = func.post_return( &mut self.store );
122
123		Ok( match function.return_kind() != ReturnKind::Void {
124			true => buffer.pop().ok_or( DispatchError::MissingResponse )?,
125			false => Self::PLACEHOLDER_VAL,
126		})
127
128	}
129
130}