1use std::sync::Arc;
2
3use async_trait::async_trait;
4use log::{error, info};
5use parking_lot::RwLock;
6use tracing::trace;
7#[cfg(feature = "wasi")]
8use wapc::WasiParams;
9use wapc::{wapc_functions, ModuleStateAsync, WebAssemblyEngineProviderAsync};
10use wasmtime::{AsContextMut, Engine, Instance, InstancePre, Linker, Module, Store, TypedFunc};
11
12use crate::errors::{Error, Result};
13use crate::store_async::WapcStoreAsync;
14use crate::{callbacks_async, EpochDeadlines, ResourceLimits};
15
16struct EngineInner {
17 instance: Arc<RwLock<Instance>>,
18 guest_call_fn: TypedFunc<(i32, i32), i32>,
19 host: Arc<ModuleStateAsync>,
20}
21
22#[allow(missing_debug_implementations)]
28#[derive(Clone)]
29pub struct WasmtimeEngineProviderAsyncPre {
30 module: Module,
31 #[cfg(feature = "wasi")]
32 wasi_params: WasiParams,
33 engine: Engine,
34 linker: Linker<WapcStoreAsync>,
35 instance_pre: InstancePre<WapcStoreAsync>,
36 epoch_deadlines: Option<EpochDeadlines>,
37 resource_limits: Option<ResourceLimits>,
38}
39
40impl WasmtimeEngineProviderAsyncPre {
41 #[cfg(feature = "wasi")]
42 pub(crate) fn new(
43 engine: Engine,
44 module: Module,
45 wasi: Option<WasiParams>,
46 epoch_deadlines: Option<EpochDeadlines>,
47 resource_limits: Option<ResourceLimits>,
48 ) -> Result<Self> {
49 let mut linker: Linker<WapcStoreAsync> = Linker::new(&engine);
50
51 let wasi_params = wasi.unwrap_or_default();
52 wasmtime_wasi::p1::add_to_linker_async(&mut linker, |s: &mut WapcStoreAsync| &mut s.wasi_ctx).unwrap();
53 wasmtime_wasi::p0::add_to_linker_async(&mut linker, |s: &mut WapcStoreAsync| &mut s.wasi_ctx).unwrap();
56
57 callbacks_async::add_to_linker(&mut linker)?;
59
60 let instance_pre = linker.instantiate_pre(&module)?;
61
62 Ok(Self {
63 module,
64 wasi_params,
65 engine,
66 linker,
67 instance_pre,
68 epoch_deadlines,
69 resource_limits,
70 })
71 }
72
73 #[cfg(not(feature = "wasi"))]
74 pub(crate) fn new(
75 engine: Engine,
76 module: Module,
77 epoch_deadlines: Option<EpochDeadlines>,
78 resource_limits: Option<ResourceLimits>,
79 ) -> Result<Self> {
80 let mut linker: Linker<WapcStoreAsync> = Linker::new(&engine);
81
82 callbacks_async::add_to_linker(&mut linker)?;
84
85 let instance_pre = linker.instantiate_pre(&module)?;
86
87 Ok(Self {
88 module,
89 engine,
90 linker,
91 instance_pre,
92 epoch_deadlines,
93 resource_limits,
94 })
95 }
96
97 pub fn rehydrate(&self) -> Result<WasmtimeEngineProviderAsync> {
102 let engine = self.engine.clone();
103
104 #[cfg(feature = "wasi")]
105 let wapc_store = WapcStoreAsync::new(&self.wasi_params, None, self.resource_limits)?;
106 #[cfg(not(feature = "wasi"))]
107 let wapc_store = WapcStoreAsync::new(None, self.resource_limits);
108
109 let mut store = Store::new(&engine, wapc_store);
110 store.limiter(|s| &mut s.limits);
111
112 Ok(WasmtimeEngineProviderAsync {
113 module: self.module.clone(),
114 inner: None,
115 engine,
116 epoch_deadlines: self.epoch_deadlines,
117 linker: self.linker.clone(),
118 instance_pre: self.instance_pre.clone(),
119 store,
120 #[cfg(feature = "wasi")]
121 wasi_params: self.wasi_params.clone(),
122 resource_limits: self.resource_limits,
123 })
124 }
125}
126
127#[allow(missing_debug_implementations)]
181pub struct WasmtimeEngineProviderAsync {
182 module: Module,
183 #[cfg(feature = "wasi")]
184 wasi_params: WasiParams,
185 inner: Option<EngineInner>,
186 engine: Engine,
187 linker: Linker<WapcStoreAsync>,
188 store: Store<WapcStoreAsync>,
189 instance_pre: InstancePre<WapcStoreAsync>,
190 epoch_deadlines: Option<EpochDeadlines>,
191 resource_limits: Option<ResourceLimits>,
192}
193
194impl Clone for WasmtimeEngineProviderAsync {
195 fn clone(&self) -> Self {
196 let engine = self.engine.clone();
197
198 #[cfg(feature = "wasi")]
199 let wapc_store = WapcStoreAsync::new(&self.wasi_params, None, self.resource_limits).unwrap();
200 #[cfg(not(feature = "wasi"))]
201 let wapc_store = WapcStoreAsync::new(None, self.resource_limits);
202
203 let mut store = Store::new(&engine, wapc_store);
204 store.limiter(|s| &mut s.limits);
205
206 match &self.inner {
207 Some(state) => {
208 let mut new = Self {
209 module: self.module.clone(),
210 inner: None,
211 engine,
212 epoch_deadlines: self.epoch_deadlines,
213 linker: self.linker.clone(),
214 instance_pre: self.instance_pre.clone(),
215 store,
216 #[cfg(feature = "wasi")]
217 wasi_params: self.wasi_params.clone(),
218 resource_limits: self.resource_limits,
219 };
220
221 tokio::runtime::Handle::current().block_on(async {
222 new.init(state.host.clone()).await.unwrap();
223 });
224
225 new
226 }
227 None => Self {
228 module: self.module.clone(),
229 inner: None,
230 engine,
231 epoch_deadlines: self.epoch_deadlines,
232 linker: self.linker.clone(),
233 instance_pre: self.instance_pre.clone(),
234 store,
235 #[cfg(feature = "wasi")]
236 wasi_params: self.wasi_params.clone(),
237 resource_limits: self.resource_limits,
238 },
239 }
240 }
241}
242
243#[async_trait]
244impl WebAssemblyEngineProviderAsync for WasmtimeEngineProviderAsync {
245 async fn init(
246 &mut self,
247 host: Arc<ModuleStateAsync>,
248 ) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
249 #[cfg(feature = "wasi")]
251 let wapc_store = WapcStoreAsync::new(&self.wasi_params, Some(host.clone()), self.resource_limits)?;
252 #[cfg(not(feature = "wasi"))]
253 let wapc_store = WapcStoreAsync::new(Some(host.clone()), self.resource_limits);
254
255 self.store = Store::new(&self.engine, wapc_store);
256 self.store.limiter(|s| &mut s.limits);
257
258 let instance = self.instance_pre.instantiate_async(&mut self.store).await?;
259
260 let instance_ref = Arc::new(RwLock::new(instance));
261 let gc = guest_call_fn(&mut self.store, &instance_ref)?;
262 self.inner = Some(EngineInner {
263 instance: instance_ref,
264 guest_call_fn: gc,
265 host,
266 });
267 self.initialize().await?;
268 Ok(())
269 }
270
271 async fn call(
272 &mut self,
273 op_length: i32,
274 msg_length: i32,
275 ) -> std::result::Result<i32, Box<dyn std::error::Error + Send + Sync>> {
276 if let Some(deadlines) = &self.epoch_deadlines {
277 self.store.set_epoch_deadline(deadlines.wapc_func);
279 }
280
281 let engine_inner = self.inner.as_ref().unwrap();
282 let call = engine_inner
283 .guest_call_fn
284 .call_async(&mut self.store, (op_length, msg_length))
285 .await;
286
287 match call {
288 Ok(result) => Ok(result),
289 Err(err) => {
290 error!("Failure invoking guest module handler: {err:?}");
291 let mut guest_error = err.to_string();
292 if let Some(trap) = err.downcast_ref::<wasmtime::Trap>() {
293 if matches!(trap, wasmtime::Trap::Interrupt) {
294 "guest code interrupted, execution deadline exceeded".clone_into(&mut guest_error);
295 }
296 }
297 engine_inner.host.set_guest_error(guest_error).await;
298 Ok(0)
299 }
300 }
301 }
302
303 async fn replace(&mut self, module: &[u8]) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
304 info!(
305 "HOT SWAP - Replacing existing WebAssembly module with new buffer, {} bytes",
306 module.len()
307 );
308
309 let module = Module::new(&self.engine, module)?;
310 self.module = module;
311 self.instance_pre = self.linker.instantiate_pre(&self.module)?;
312 let new_instance = self.instance_pre.instantiate_async(&mut self.store).await?;
313 if let Some(inner) = self.inner.as_mut() {
314 *inner.instance.write() = new_instance;
315 let gc = guest_call_fn(&mut self.store, &inner.instance)?;
316 inner.guest_call_fn = gc;
317 }
318
319 Ok(self.initialize().await?)
320 }
321}
322
323impl WasmtimeEngineProviderAsync {
324 async fn initialize(&mut self) -> Result<()> {
325 for starter in wapc_functions::REQUIRED_STARTS.iter() {
326 if let Some(deadlines) = &self.epoch_deadlines {
327 self.store.set_epoch_deadline(deadlines.wapc_init);
329 }
330
331 let engine_inner = self.inner.as_ref().unwrap();
332 if engine_inner
333 .instance
334 .read()
335 .get_export(&mut self.store, starter)
336 .is_some()
337 {
338 let starter_func: TypedFunc<(), ()> = engine_inner.instance.read().get_typed_func(&mut self.store, starter)?;
343
344 if let Err(err) = starter_func.call_async(&mut self.store, ()).await {
345 trace!(function = starter, ?err, "handling error returned by init function");
346 if let Some(trap) = err.downcast_ref::<wasmtime::Trap>() {
347 if matches!(trap, wasmtime::Trap::Interrupt) {
348 return Err(Error::InitializationFailedTimeout((*starter).to_owned()));
349 }
350 return Err(Error::InitializationFailed(err.to_string()));
351 }
352
353 #[cfg(feature = "wasi")]
361 if let Some(exit_err) = err.downcast_ref::<wasmtime_wasi::I32Exit>() {
362 if exit_err.0 != 0 {
363 return Err(Error::InitializationFailed(err.to_string()));
364 }
365 trace!("ignoring successful exit trap generated by WASI");
366 continue;
367 }
368
369 return Err(Error::InitializationFailed(err.to_string()));
370 };
371 }
372 }
373 Ok(())
374 }
375}
376
377fn guest_call_fn(store: impl AsContextMut, instance: &Arc<RwLock<Instance>>) -> Result<TypedFunc<(i32, i32), i32>> {
380 instance
381 .read()
382 .get_typed_func::<(i32, i32), i32>(store, wapc_functions::GUEST_CALL)
383 .map_err(|_| Error::GuestCallNotFound)
384}