Skip to main content

wasmtime/runtime/store/
async_.rs

1#[cfg(feature = "call-hook")]
2use crate::CallHook;
3use crate::fiber::{self};
4use crate::prelude::*;
5#[cfg(feature = "gc")]
6use crate::runtime::vm::VMStore;
7use crate::store::{AsContextMut, Asyncness, ResourceLimiterInner, StoreInner, StoreOpaque};
8use crate::{Store, StoreContextMut, UpdateDeadline};
9
10/// An object that can take callbacks when the runtime enters or exits hostcalls.
11#[cfg(feature = "call-hook")]
12#[async_trait::async_trait]
13pub trait CallHookHandler<T>: Send {
14    /// A callback to run when wasmtime is about to enter a host call, or when about to
15    /// exit the hostcall.
16    async fn handle_call_event(&self, t: crate::StoreHookState<'_, T>, ch: CallHook) -> Result<()>;
17}
18
19impl<T> Store<T> {
20    /// Configures the [`ResourceLimiterAsync`](crate::ResourceLimiterAsync)
21    /// used to limit resource creation within this [`Store`].
22    ///
23    /// This method is an asynchronous variant of the [`Store::limiter`] method
24    /// where the embedder can block the wasm request for more resources with
25    /// host `async` execution of futures.
26    ///
27    /// By using a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
28    /// with a [`Store`], you can no longer use
29    /// [`Memory::new`](`crate::Memory::new`),
30    /// [`Memory::grow`](`crate::Memory::grow`),
31    /// [`Table::new`](`crate::Table::new`), and
32    /// [`Table::grow`](`crate::Table::grow`). Instead, you must use their
33    /// `async` variants: [`Memory::new_async`](`crate::Memory::new_async`),
34    /// [`Memory::grow_async`](`crate::Memory::grow_async`),
35    /// [`Table::new_async`](`crate::Table::new_async`), and
36    /// [`Table::grow_async`](`crate::Table::grow_async`).
37    ///
38    /// Note that this limiter is only used to limit the creation/growth of
39    /// resources in the future, this does not retroactively attempt to apply
40    /// limits to the [`Store`].
41    ///
42    /// After configuring this method it's required that synchronous APIs in
43    /// Wasmtime are no longer used, such as [`Func::call`](crate::Func::call).
44    /// Instead APIs such as [`Func::call_async`](crate::Func::call_async) must
45    /// be used instead.
46    pub fn limiter_async(
47        &mut self,
48        mut limiter: impl (FnMut(&mut T) -> &mut dyn crate::ResourceLimiterAsync)
49        + Send
50        + Sync
51        + 'static,
52    ) {
53        // Apply the limits on instances, tables, and memory given by the limiter:
54        let inner = &mut self.inner;
55        let (instance_limit, table_limit, memory_limit) = {
56            let l = limiter(inner.data_mut());
57            (l.instances(), l.tables(), l.memories())
58        };
59        let innermost = &mut inner.inner;
60        innermost.instance_limit = instance_limit;
61        innermost.table_limit = table_limit;
62        innermost.memory_limit = memory_limit;
63
64        // Save the limiter accessor function:
65        inner.limiter = Some(ResourceLimiterInner::Async(Box::new(limiter)));
66        inner.set_async_required(Asyncness::Yes);
67    }
68
69    /// Configures an async function that runs on calls and returns between
70    /// WebAssembly and host code. For the non-async equivalent of this method,
71    /// see [`Store::call_hook`].
72    ///
73    /// The function is passed a [`CallHook`] argument, which indicates which
74    /// state transition the VM is making.
75    ///
76    /// This function's future may return a [`Trap`]. If a trap is returned
77    /// when an import was called, it is immediately raised as-if the host
78    /// import had returned the trap. If a trap is returned after wasm returns
79    /// to the host then the wasm function's result is ignored and this trap is
80    /// returned instead.
81    ///
82    /// After this function returns a trap, it may be called for subsequent
83    /// returns to host or wasm code as the trap propagates to the root call.
84    ///
85    /// [`Trap`]: crate::Trap
86    #[cfg(feature = "call-hook")]
87    pub fn call_hook_async(&mut self, hook: impl CallHookHandler<T> + Send + Sync + 'static) {
88        self.inner.call_hook = Some(crate::store::CallHookInner::Async(Box::new(hook)));
89        self.inner.set_async_required(Asyncness::Yes);
90    }
91
92    /// Perform garbage collection asynchronously.
93    ///
94    /// Note that it is not required to actively call this function. GC will
95    /// automatically happen according to various internal heuristics. This is
96    /// provided if fine-grained control over the GC is desired.
97    ///
98    /// This method is only available when the `gc` Cargo feature is enabled.
99    #[cfg(feature = "gc")]
100    pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()>
101    where
102        T: Send,
103    {
104        StoreContextMut(&mut self.inner).gc_async(why).await
105    }
106
107    /// Manually grow the GC heap by at least `bytes` bytes.
108    ///
109    /// For more information, see the documentation of [`Store::gc_heap_grow`].
110    #[cfg(feature = "gc")]
111    pub async fn gc_heap_grow_async(&mut self, bytes: u64) -> Result<()> {
112        StoreContextMut(&mut self.inner)
113            .gc_heap_grow_async(bytes)
114            .await
115    }
116
117    /// Configures epoch-deadline expiration to yield to the async
118    /// caller and the update the deadline.
119    ///
120    /// When epoch-interruption-instrumented code is executed on this
121    /// store and the epoch deadline is reached before completion,
122    /// with the store configured in this way, execution will yield
123    /// (the future will return `Pending` but re-awake itself for
124    /// later execution) and, upon resuming, the store will be
125    /// configured with an epoch deadline equal to the current epoch
126    /// plus `delta` ticks.
127    ///
128    /// This setting is intended to allow for cooperative timeslicing
129    /// of multiple CPU-bound Wasm guests in different stores, all
130    /// executing under the control of an async executor. To drive
131    /// this, stores should be configured to "yield and update"
132    /// automatically with this function, and some external driver (a
133    /// thread that wakes up periodically, or a timer
134    /// signal/interrupt) should call
135    /// [`Engine::increment_epoch()`](crate::Engine::increment_epoch).
136    ///
137    /// See documentation on
138    /// [`Config::epoch_interruption()`](crate::Config::epoch_interruption)
139    /// for an introduction to epoch-based interruption.
140    #[cfg(target_has_atomic = "64")]
141    pub fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
142        self.inner.epoch_deadline_async_yield_and_update(delta);
143    }
144
145    /// Sets the maximum number of simultaneously-live fibers permitted in this
146    /// store.
147    ///
148    /// Async entrypoints in Wasmtime, such as [`Func::call_async`] will
149    /// allocate fibers to execute WebAssembly code on. Fibers are additionally
150    /// used for implementing component model async-related primitives. Fibers
151    /// represent an allocated chunk of the host process's virtual address space
152    /// for use as a temporary native stack. Due to the cost of a fiber
153    /// allocation they're limited by default per-store with the ability for
154    /// embedders to tune limits as desired.
155    ///
156    /// Attempts to exceed this limit will be met with an immediate error. For
157    /// example [`Func::call_async`] will return an error when this limit is
158    /// reached.
159    ///
160    /// The limit counts live fibers, so a fiber returns budget once it
161    /// completes. Lowering it below the current live count does not affect
162    /// existing fibers, only blocks new ones until the count falls back under
163    /// it. A limit of 0 makes every async entry point fail.
164    ///
165    /// The default value for this is 64 fibers.
166    ///
167    /// [`Engine`]: crate::Engine
168    /// [`Func::call_async`]: crate::Func::call_async
169    pub fn set_max_fibers(&mut self, limit: u32) {
170        self.as_context_mut().set_max_fibers(limit)
171    }
172}
173
174impl<'a, T> StoreContextMut<'a, T> {
175    /// See [`Store::set_max_fibers`].
176    pub fn set_max_fibers(&mut self, limit: u32) {
177        self.0.async_state.max_fibers = limit;
178    }
179
180    /// Perform garbage collection of `ExternRef`s.
181    ///
182    /// Same as [`Store::gc`].
183    ///
184    /// This method is only available when the `gc` Cargo feature is enabled.
185    #[cfg(feature = "gc")]
186    pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()>
187    where
188        T: Send + 'static,
189    {
190        let (mut limiter, store) = self.0.resource_limiter_and_store_opaque();
191        store
192            .gc(
193                limiter.as_mut(),
194                None,
195                why.map(|e| e.bytes_needed()),
196                crate::store::Asyncness::Yes,
197            )
198            .await?;
199        Ok(())
200    }
201
202    /// Manually grow the GC heap by at least `bytes` bytes.
203    ///
204    /// For more information, see the documentation of [`Store::gc_heap_grow`].
205    #[cfg(feature = "gc")]
206    pub async fn gc_heap_grow_async(&mut self, bytes: u64) -> Result<()> {
207        let (mut limiter, store) = self.0.resource_limiter_and_store_opaque();
208        store
209            .grow_gc_heap(limiter.as_mut(), bytes, crate::store::Asyncness::Yes)
210            .await
211    }
212
213    /// Configures epoch-deadline expiration to yield to the async
214    /// caller and the update the deadline.
215    ///
216    /// For more information see
217    /// [`Store::epoch_deadline_async_yield_and_update`].
218    #[cfg(target_has_atomic = "64")]
219    pub fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
220        self.0.epoch_deadline_async_yield_and_update(delta);
221    }
222}
223
224impl<T> StoreInner<T> {
225    #[cfg(target_has_atomic = "64")]
226    fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
227        // All future entrypoints must be async to handle the case that an epoch
228        // changes and a yield is required.
229        self.set_async_required(Asyncness::Yes);
230
231        self.epoch_deadline_behavior =
232            Some(Box::new(move |_store| Ok(UpdateDeadline::Yield(delta))));
233    }
234}
235
236#[doc(hidden)]
237impl StoreOpaque {
238    pub(crate) fn allocate_fiber_stack(&mut self) -> Result<wasmtime_fiber::FiberStack> {
239        if self.async_state.live_fibers >= self.async_state.max_fibers {
240            bail!("store limit of fibers has been reached");
241        }
242        let stack = match self.async_state.last_fiber_stack().take() {
243            Some(stack) => stack,
244            None => self.engine().allocator().allocate_fiber_stack()?,
245        };
246        self.async_state.live_fibers += 1;
247        Ok(stack)
248    }
249
250    pub(crate) fn deallocate_fiber_stack(&mut self, stack: wasmtime_fiber::FiberStack) {
251        self.async_state.live_fibers -= 1;
252        self.flush_fiber_stack();
253        *self.async_state.last_fiber_stack() = Some(stack);
254    }
255
256    /// Releases the last fiber stack to the underlying instance allocator, if
257    /// present.
258    pub fn flush_fiber_stack(&mut self) {
259        if let Some(stack) = self.async_state.last_fiber_stack().take() {
260            unsafe {
261                self.engine.allocator().deallocate_fiber_stack(stack);
262            }
263        }
264    }
265}
266
267impl<T> StoreContextMut<'_, T> {
268    /// Executes a synchronous computation `func` asynchronously on a new fiber.
269    pub(crate) async fn on_fiber<R: Send + Sync>(
270        &mut self,
271        func: impl FnOnce(&mut StoreContextMut<'_, T>) -> R + Send + Sync,
272    ) -> Result<R> {
273        fiber::on_fiber(self.0, |me| func(&mut StoreContextMut(me))).await
274    }
275}