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};
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}
38
39impl WasmtimeEngineProviderAsyncPre {
40 #[cfg(feature = "wasi")]
41 pub(crate) fn new(
42 engine: Engine,
43 module: Module,
44 wasi: Option<WasiParams>,
45 epoch_deadlines: Option<EpochDeadlines>,
46 ) -> Result<Self> {
47 let mut linker: Linker<WapcStoreAsync> = Linker::new(&engine);
48
49 let wasi_params = wasi.unwrap_or_default();
50 wasmtime_wasi::p1::add_to_linker_async(&mut linker, |s: &mut WapcStoreAsync| &mut s.wasi_ctx).unwrap();
51 wasmtime_wasi::p0::add_to_linker_async(&mut linker, |s: &mut WapcStoreAsync| &mut s.wasi_ctx).unwrap();
54
55 callbacks_async::add_to_linker(&mut linker)?;
57
58 let instance_pre = linker.instantiate_pre(&module)?;
59
60 Ok(Self {
61 module,
62 wasi_params,
63 engine,
64 linker,
65 instance_pre,
66 epoch_deadlines,
67 })
68 }
69
70 #[cfg(not(feature = "wasi"))]
71 pub(crate) fn new(engine: Engine, module: Module, epoch_deadlines: Option<EpochDeadlines>) -> Result<Self> {
72 let mut linker: Linker<WapcStoreAsync> = Linker::new(&engine);
73
74 callbacks_async::add_to_linker(&mut linker)?;
76
77 let instance_pre = linker.instantiate_pre(&module)?;
78
79 Ok(Self {
80 module,
81 engine,
82 linker,
83 instance_pre,
84 epoch_deadlines,
85 })
86 }
87
88 pub fn rehydrate(&self) -> Result<WasmtimeEngineProviderAsync> {
93 let engine = self.engine.clone();
94
95 #[cfg(feature = "wasi")]
96 let wapc_store = WapcStoreAsync::new(&self.wasi_params, None)?;
97 #[cfg(not(feature = "wasi"))]
98 let wapc_store = WapcStoreAsync::new(None);
99
100 let store = Store::new(&engine, wapc_store);
101
102 Ok(WasmtimeEngineProviderAsync {
103 module: self.module.clone(),
104 inner: None,
105 engine,
106 epoch_deadlines: self.epoch_deadlines,
107 linker: self.linker.clone(),
108 instance_pre: self.instance_pre.clone(),
109 store,
110 #[cfg(feature = "wasi")]
111 wasi_params: self.wasi_params.clone(),
112 })
113 }
114}
115
116#[allow(missing_debug_implementations)]
170pub struct WasmtimeEngineProviderAsync {
171 module: Module,
172 #[cfg(feature = "wasi")]
173 wasi_params: WasiParams,
174 inner: Option<EngineInner>,
175 engine: Engine,
176 linker: Linker<WapcStoreAsync>,
177 store: Store<WapcStoreAsync>,
178 instance_pre: InstancePre<WapcStoreAsync>,
179 epoch_deadlines: Option<EpochDeadlines>,
180}
181
182impl Clone for WasmtimeEngineProviderAsync {
183 fn clone(&self) -> Self {
184 let engine = self.engine.clone();
185
186 #[cfg(feature = "wasi")]
187 let wapc_store = WapcStoreAsync::new(&self.wasi_params, None).unwrap();
188 #[cfg(not(feature = "wasi"))]
189 let wapc_store = WapcStoreAsync::new(None);
190
191 let store = Store::new(&engine, wapc_store);
192
193 match &self.inner {
194 Some(state) => {
195 let mut new = Self {
196 module: self.module.clone(),
197 inner: None,
198 engine,
199 epoch_deadlines: self.epoch_deadlines,
200 linker: self.linker.clone(),
201 instance_pre: self.instance_pre.clone(),
202 store,
203 #[cfg(feature = "wasi")]
204 wasi_params: self.wasi_params.clone(),
205 };
206
207 tokio::runtime::Handle::current().block_on(async {
208 new.init(state.host.clone()).await.unwrap();
209 });
210
211 new
212 }
213 None => Self {
214 module: self.module.clone(),
215 inner: None,
216 engine,
217 epoch_deadlines: self.epoch_deadlines,
218 linker: self.linker.clone(),
219 instance_pre: self.instance_pre.clone(),
220 store,
221 #[cfg(feature = "wasi")]
222 wasi_params: self.wasi_params.clone(),
223 },
224 }
225 }
226}
227
228#[async_trait]
229impl WebAssemblyEngineProviderAsync for WasmtimeEngineProviderAsync {
230 async fn init(
231 &mut self,
232 host: Arc<ModuleStateAsync>,
233 ) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
234 #[cfg(feature = "wasi")]
236 let wapc_store = WapcStoreAsync::new(&self.wasi_params, Some(host.clone()))?;
237 #[cfg(not(feature = "wasi"))]
238 let wapc_store = WapcStoreAsync::new(Some(host.clone()));
239
240 self.store = Store::new(&self.engine, wapc_store);
241
242 let instance = self.instance_pre.instantiate_async(&mut self.store).await?;
243
244 let instance_ref = Arc::new(RwLock::new(instance));
245 let gc = guest_call_fn(&mut self.store, &instance_ref)?;
246 self.inner = Some(EngineInner {
247 instance: instance_ref,
248 guest_call_fn: gc,
249 host,
250 });
251 self.initialize().await?;
252 Ok(())
253 }
254
255 async fn call(
256 &mut self,
257 op_length: i32,
258 msg_length: i32,
259 ) -> std::result::Result<i32, Box<dyn std::error::Error + Send + Sync>> {
260 if let Some(deadlines) = &self.epoch_deadlines {
261 self.store.set_epoch_deadline(deadlines.wapc_func);
263 }
264
265 let engine_inner = self.inner.as_ref().unwrap();
266 let call = engine_inner
267 .guest_call_fn
268 .call_async(&mut self.store, (op_length, msg_length))
269 .await;
270
271 match call {
272 Ok(result) => Ok(result),
273 Err(err) => {
274 error!("Failure invoking guest module handler: {err:?}");
275 let mut guest_error = err.to_string();
276 if let Some(trap) = err.downcast_ref::<wasmtime::Trap>() {
277 if matches!(trap, wasmtime::Trap::Interrupt) {
278 "guest code interrupted, execution deadline exceeded".clone_into(&mut guest_error);
279 }
280 }
281 engine_inner.host.set_guest_error(guest_error).await;
282 Ok(0)
283 }
284 }
285 }
286
287 async fn replace(&mut self, module: &[u8]) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
288 info!(
289 "HOT SWAP - Replacing existing WebAssembly module with new buffer, {} bytes",
290 module.len()
291 );
292
293 let module = Module::new(&self.engine, module)?;
294 self.module = module;
295 self.instance_pre = self.linker.instantiate_pre(&self.module)?;
296 let new_instance = self.instance_pre.instantiate_async(&mut self.store).await?;
297 if let Some(inner) = self.inner.as_mut() {
298 *inner.instance.write() = new_instance;
299 let gc = guest_call_fn(&mut self.store, &inner.instance)?;
300 inner.guest_call_fn = gc;
301 }
302
303 Ok(self.initialize().await?)
304 }
305}
306
307impl WasmtimeEngineProviderAsync {
308 async fn initialize(&mut self) -> Result<()> {
309 for starter in wapc_functions::REQUIRED_STARTS.iter() {
310 if let Some(deadlines) = &self.epoch_deadlines {
311 self.store.set_epoch_deadline(deadlines.wapc_init);
313 }
314
315 let engine_inner = self.inner.as_ref().unwrap();
316 if engine_inner
317 .instance
318 .read()
319 .get_export(&mut self.store, starter)
320 .is_some()
321 {
322 let starter_func: TypedFunc<(), ()> = engine_inner.instance.read().get_typed_func(&mut self.store, starter)?;
327
328 if let Err(err) = starter_func.call_async(&mut self.store, ()).await {
329 trace!(function = starter, ?err, "handling error returned by init function");
330 if let Some(trap) = err.downcast_ref::<wasmtime::Trap>() {
331 if matches!(trap, wasmtime::Trap::Interrupt) {
332 return Err(Error::InitializationFailedTimeout((*starter).to_owned()));
333 }
334 return Err(Error::InitializationFailed(err.to_string()));
335 }
336
337 #[cfg(feature = "wasi")]
345 if let Some(exit_err) = err.downcast_ref::<wasmtime_wasi::I32Exit>() {
346 if exit_err.0 != 0 {
347 return Err(Error::InitializationFailed(err.to_string()));
348 }
349 trace!("ignoring successful exit trap generated by WASI");
350 continue;
351 }
352
353 return Err(Error::InitializationFailed(err.to_string()));
354 };
355 }
356 }
357 Ok(())
358 }
359}
360
361fn guest_call_fn(store: impl AsContextMut, instance: &Arc<RwLock<Instance>>) -> Result<TypedFunc<(i32, i32), i32>> {
364 instance
365 .read()
366 .get_typed_func::<(i32, i32), i32>(store, wapc_functions::GUEST_CALL)
367 .map_err(|_| Error::GuestCallNotFound)
368}