Skip to main content

wasmtime/runtime/component/concurrent/
func.rs

1use crate::component::concurrent::{self, PreparedCall, TaskGroupId, TaskId};
2use crate::component::func::LowerContext;
3use crate::component::{AsAccessor, ComponentNamedList, Func, Lift, Lower, TypedFunc, Val};
4use crate::prelude::*;
5use crate::runtime::vm::SendSyncPtr;
6use crate::{AsContextMut, StoreContextMut, ValRaw};
7use core::marker;
8use core::mem::MaybeUninit;
9use core::ptr::NonNull;
10use wasmtime_environ::component::{InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS};
11
12/// Returned from [`Func::start_call_concurrent`] to represent a
13/// pending-but-not-yet-resolved call into wasm.
14pub struct FuncCallConcurrent<'a, T> {
15    pub(crate) call: concurrent::StagedCall<Vec<Val>>,
16    results: &'a mut [Val],
17    _marker: marker::PhantomData<fn(T)>,
18}
19
20impl Func {
21    /// Start a concurrent call to this function.
22    ///
23    /// Concurrency is achieved by relying on the [`Accessor`] argument, which
24    /// can be obtained by calling [`StoreContextMut::run_concurrent`].
25    ///
26    /// Unlike [`Self::call`] and [`Self::call_async`] (both of which require
27    /// exclusive access to the store until the completion of the call), calls
28    /// made using this method may run concurrently with other calls to the same
29    /// instance.  In addition, the runtime will call the `post-return` function
30    /// (if any) automatically when the guest task completes.
31    ///
32    /// # Progress
33    ///
34    /// For the wasm task being created in `call_concurrent` to make progress it
35    /// must be run within the scope of [`run_concurrent`]. If there are no
36    /// active calls to [`run_concurrent`] then the wasm task will appear as
37    /// stalled. This is typically not a concern as an [`Accessor`] is bound
38    /// by default to a scope of [`run_concurrent`].
39    ///
40    /// One situation in which this can arise, for example, is that if a
41    /// [`run_concurrent`] computation finishes its async closure before all
42    /// wasm tasks have completed, then there will be no scope of
43    /// [`run_concurrent`] anywhere. In this situation the wasm tasks that have
44    /// not yet completed will not make progress until [`run_concurrent`] is
45    /// called again.
46    ///
47    /// Embedders will need to ensure that this future is `await`'d within the
48    /// scope of [`run_concurrent`] to ensure that the value can be produced
49    /// during the `await` call.
50    ///
51    /// # Cancellation
52    ///
53    /// Cancelling an async task created via `call_concurrent`, at this time, is
54    /// only possible by dropping the store that the computation runs within.
55    /// With [#11833] implemented then it will be possible to request
56    /// cancellation of a task, but that is not yet implemented. Hard-cancelling
57    /// a task will only ever be possible by dropping the entire store and it is
58    /// not possible to remove just one task from a store.
59    ///
60    /// This async function behaves more like a "spawn" than a normal Rust async
61    /// function. When this function is invoked then metadata for the function
62    /// call is recorded in the store connected to the `accessor` argument and
63    /// the wasm invocation is from then on connected to the store. If the
64    /// future created by this function is dropped it does not cancel the
65    /// in-progress execution of the wasm task. Dropping the future
66    /// relinquishes the host's ability to learn about the result of the task
67    /// but the task will still progress and invoke callbacks and such until
68    /// completion.
69    ///
70    /// This function will return an error if [`Config::concurrency_support`] is
71    /// disabled.
72    ///
73    /// [`Config::concurrency_support`]: crate::Config::concurrency_support
74    /// [`run_concurrent`]: crate::Store::run_concurrent
75    /// [#11833]: https://github.com/bytecodealliance/wasmtime/issues/11833
76    /// [`Accessor`]: crate::component::Accessor
77    ///
78    /// # Panics
79    ///
80    /// Panics if the store that the [`Accessor`] is derived from does not own
81    /// this function.
82    ///
83    /// # Example
84    ///
85    /// Using [`StoreContextMut::run_concurrent`] to get an [`Accessor`]:
86    ///
87    /// ```
88    /// # use {
89    /// #   wasmtime::{
90    /// #     error::{Result},
91    /// #     component::{Component, Linker, ResourceTable},
92    /// #     Config, Engine, Store
93    /// #   },
94    /// # };
95    /// #
96    /// # struct Ctx { table: ResourceTable }
97    /// #
98    /// # async fn foo() -> Result<()> {
99    /// # let mut config = Config::new();
100    /// # let engine = Engine::new(&config)?;
101    /// # let mut store = Store::new(&engine, Ctx { table: ResourceTable::new() });
102    /// # let mut linker = Linker::new(&engine);
103    /// # let component = Component::new(&engine, "")?;
104    /// # let instance = linker.instantiate_async(&mut store, &component).await?;
105    /// let my_func = instance.get_func(&mut store, "my_func").unwrap();
106    /// store.run_concurrent(async |accessor| -> wasmtime::Result<_> {
107    ///    my_func.call_concurrent(accessor, &[], &mut Vec::new()).await?;
108    ///    Ok(())
109    /// }).await??;
110    /// # Ok(())
111    /// # }
112    /// ```
113    pub async fn call_concurrent(
114        self,
115        accessor: impl AsAccessor<Data: Send>,
116        params: &[Val],
117        results: &mut [Val],
118    ) -> Result<()> {
119        let accessor = accessor.as_accessor();
120        let call = accessor.with(|store| self.start_call_concurrent(store, params, results))?;
121        self.finish_call_concurrent(accessor, call).await
122    }
123
124    /// Performs preparatory work for invoking this function with `params`,
125    /// returning a [`FuncCallConcurrent`]
126    /// which can be passed to [`Func::finish_call_concurrent`] to resolve
127    /// the call.
128    ///
129    /// For more information see [`Func::call_concurrent`].
130    pub fn start_call_concurrent<'a, T: Send + 'static>(
131        self,
132        mut store: impl AsContextMut<Data = T>,
133        params: &'a [Val],
134        results: &'a mut [Val],
135    ) -> Result<FuncCallConcurrent<'a, T>> {
136        let result = self.do_start_call_concurrent(&mut store, params, results);
137
138        if result.is_err() {
139            store.as_context_mut().0.set_trapped();
140        }
141
142        result
143    }
144
145    fn do_start_call_concurrent<'a, T: Send + 'static>(
146        self,
147        mut store: impl AsContextMut<Data = T>,
148        params: &'a [Val],
149        results: &'a mut [Val],
150    ) -> Result<FuncCallConcurrent<'a, T>> {
151        self.check_params_results(store.as_context_mut(), params, results)?;
152        let prepared = self.prepare_call_dynamic(store.as_context_mut(), params.to_vec())?;
153        let call = concurrent::StagedCall::new(store.as_context_mut(), prepared)?;
154        Ok(FuncCallConcurrent {
155            call,
156            results,
157            _marker: marker::PhantomData,
158        })
159    }
160
161    /// Completes a call that was initiated via
162    /// [`Func::start_call_concurrent`].
163    pub async fn finish_call_concurrent<T: Send + 'static>(
164        self,
165        accessor: impl AsAccessor<Data = T>,
166        call: FuncCallConcurrent<'_, T>,
167    ) -> Result<()> {
168        // Intentionally not used today, but left here for future API
169        // compatibility with using this.
170        let _ = accessor;
171        let FuncCallConcurrent { call, results, .. } = call;
172        let run_results = call.await?;
173        assert_eq!(run_results.len(), results.len());
174        for (result, slot) in run_results.into_iter().zip(results) {
175            *slot = result;
176        }
177        Ok(())
178    }
179
180    /// Calls `concurrent::prepare_call` with monomorphized functions for
181    /// lowering the parameters and lifting the result.
182    fn prepare_call_dynamic<'a, T: Send + 'static>(
183        self,
184        mut store: StoreContextMut<'a, T>,
185        params: Vec<Val>,
186    ) -> Result<PreparedCall<Vec<Val>>> {
187        let store = store.as_context_mut();
188        let (options, _, ty, raw_options) = self.abi_info(store.0);
189        let async_ = raw_options.async_;
190        let instance = self.instance();
191
192        concurrent::prepare_call(
193            store,
194            self,
195            MAX_FLAT_PARAMS,
196            move |store, params_out| {
197                Func::with_lower_context(instance, store, options, ty, |cx, ty| {
198                    Self::lower_args(cx, &params, ty, params_out)
199                })
200            },
201            move |store, results| {
202                let max_flat = if async_ {
203                    MAX_FLAT_PARAMS
204                } else {
205                    MAX_FLAT_RESULTS
206                };
207                let results = Func::with_lift_context(instance, store, options, ty, |cx, ty| {
208                    Self::lift_results(cx, ty, results, max_flat)?.collect::<Result<Vec<_>>>()
209                })?;
210                Ok(Box::new(results))
211            },
212        )
213    }
214}
215
216/// Returned from [`TypedFunc::start_call_concurrent`] to represent a
217/// pending-but-not-yet-resolved call into wasm.
218pub struct TypedFuncCallConcurrent<T, P, R> {
219    pub(crate) call: concurrent::StagedCall<R>,
220    _marker: marker::PhantomData<fn(T, P)>,
221}
222
223impl<Params, Return> TypedFunc<Params, Return>
224where
225    Params: ComponentNamedList + Lower,
226    Return: ComponentNamedList + Lift,
227{
228    pub(crate) async fn call_async_concurrent(
229        &self,
230        mut store: impl AsContextMut<Data: Send>,
231        params: Params,
232    ) -> Result<Return>
233    where
234        Return: 'static,
235    {
236        let mut store = store.as_context_mut();
237        // See `Func::call_async` for why this is checked up front.
238        store.0.check_event_loop_not_running()?;
239        let ptr = SendSyncPtr::from(NonNull::from(&params).cast::<u8>());
240        let prepared = self.prepare_call(store.as_context_mut(), move |cx, ty, dst| {
241            // SAFETY: The goal here is to get `Params`, a non-`'static`
242            // value, to live long enough to the lowering of the
243            // parameters. We're guaranteed that `Params` lives in the
244            // future of the outer function (we're in an `async fn`) so it'll
245            // stay alive as long as the future itself. That is distinct,
246            // for example, from the signature of `call_concurrent` below.
247            //
248            // Here a pointer to `Params` is smuggled to this location
249            // through a `SendSyncPtr<u8>` to thwart the `'static` check
250            // of rustc and the signature of `prepare_call`.
251            //
252            // Note the use of `SignalOnDrop` in the code that follows
253            // this closure, which removes this closure from its task when
254            // the containing `Future` is dropped, so long as the parameters
255            // have not yet been lowered (see `TaskId::host_future_dropped`).
256            // Since this closure is also removed from the task when the
257            // parameters are lowered, it will never be called after the
258            // containing `Future` is dropped.
259            let params = unsafe { ptr.cast::<Params>().as_ref() };
260            Self::lower_args(cx, ty, dst, params)
261        })?;
262
263        struct SignalOnDrop<'a, T: 'static> {
264            store: StoreContextMut<'a, T>,
265            task: TaskId,
266        }
267
268        impl<'a, T> Drop for SignalOnDrop<'a, T> {
269            fn drop(&mut self) {
270                self.task.host_future_dropped(self.store.0).unwrap();
271            }
272        }
273
274        // This will increment the task's refcount, and the
275        // `TaskId::host_future_dropped` function called from
276        // `SignalOnDrop::drop` wiil decrement it.
277        let task = prepared.acquire_task_id(store.0)?;
278
279        let mut wrapper = SignalOnDrop { store, task };
280
281        let result = concurrent::StagedCall::new(wrapper.store.as_context_mut(), prepared)?;
282        wrapper
283            .store
284            .as_context_mut()
285            .run_concurrent_trap_on_idle(async |_| result.await)
286            .await?
287    }
288
289    /// Start a concurrent call to this function.
290    ///
291    /// Concurrency is achieved by relying on the [`Accessor`] argument, which
292    /// can be obtained by calling [`StoreContextMut::run_concurrent`].
293    ///
294    /// Unlike [`Self::call`] and [`Self::call_async`] (both of which require
295    /// exclusive access to the store until the completion of the call), calls
296    /// made using this method may run concurrently with other calls to the same
297    /// instance.  In addition, the runtime will call the `post-return` function
298    /// (if any) automatically when the guest task completes.
299    ///
300    /// This function will return an error if [`Config::concurrency_support`] is
301    /// disabled.
302    ///
303    /// [`Config::concurrency_support`]: crate::Config::concurrency_support
304    ///
305    /// # Progress and Cancellation
306    ///
307    /// For more information about how to make progress on the wasm task or how
308    /// to cancel the wasm task see the documentation for
309    /// [`Func::call_concurrent`].
310    ///
311    /// [`Func::call_concurrent`]: crate::component::Func::call_concurrent
312    ///
313    /// # Panics
314    ///
315    /// Panics if the store that the [`Accessor`] is derived from does not own
316    /// this function.
317    ///
318    /// [`Accessor`]: crate::component::Accessor
319    ///
320    /// # Example
321    ///
322    /// Using [`StoreContextMut::run_concurrent`] to get an [`Accessor`]:
323    ///
324    /// ```
325    /// # use {
326    /// #   wasmtime::{
327    /// #     error::{Result},
328    /// #     component::{Component, Linker, ResourceTable},
329    /// #     Config, Engine, Store
330    /// #   },
331    /// # };
332    /// #
333    /// # struct Ctx { table: ResourceTable }
334    /// #
335    /// # async fn foo() -> Result<()> {
336    /// # let mut config = Config::new();
337    /// # let engine = Engine::new(&config)?;
338    /// # let mut store = Store::new(&engine, Ctx { table: ResourceTable::new() });
339    /// # let mut linker = Linker::new(&engine);
340    /// # let component = Component::new(&engine, "")?;
341    /// # let instance = linker.instantiate_async(&mut store, &component).await?;
342    /// let my_typed_func = instance.get_typed_func::<(), ()>(&mut store, "my_typed_func")?;
343    /// store.run_concurrent(async |accessor| -> wasmtime::Result<_> {
344    ///    my_typed_func.call_concurrent(accessor, ()).await?;
345    ///    Ok(())
346    /// }).await??;
347    /// # Ok(())
348    /// # }
349    /// ```
350    pub async fn call_concurrent(
351        self,
352        accessor: impl AsAccessor<Data: Send>,
353        params: Params,
354    ) -> Result<Return>
355    where
356        Params: 'static,
357        Return: 'static,
358    {
359        let call = accessor
360            .as_accessor()
361            .with(|store| self.start_call_concurrent(store, params))?;
362        self.finish_call_concurrent(accessor, call).await
363    }
364
365    /// Performs preparatory work for invoking this function with `params`,
366    /// returning a [`TypedFuncCallConcurrent`]
367    /// which can be passed to [`TypedFunc::finish_call_concurrent`] to resolve
368    /// the call.
369    ///
370    /// For more information see [`TypedFunc::call_concurrent`].
371    pub fn start_call_concurrent<T>(
372        self,
373        mut store: impl AsContextMut<Data = T>,
374        params: Params,
375    ) -> Result<TypedFuncCallConcurrent<T, Params, Return>>
376    where
377        T: Send + 'static,
378        Params: 'static,
379        Return: 'static,
380    {
381        let mut store = store.as_context_mut();
382        let mut store = store.as_context_mut();
383        ensure!(
384            store.0.concurrency_support(),
385            "cannot use `call_concurrent` Config::concurrency_support disabled",
386        );
387
388        let prepared = self.prepare_call(store.as_context_mut(), move |cx, ty, dst| {
389            Self::lower_args(cx, ty, dst, &params)
390        })?;
391        let call = concurrent::StagedCall::new(store, prepared)?;
392        Ok(TypedFuncCallConcurrent {
393            call,
394            _marker: marker::PhantomData,
395        })
396    }
397
398    /// Completes a call that was initiated via
399    /// [`TypedFunc::start_call_concurrent`].
400    pub async fn finish_call_concurrent<T>(
401        self,
402        accessor: impl AsAccessor<Data = T>,
403        call: TypedFuncCallConcurrent<T, Params, Return>,
404    ) -> Result<Return>
405    where
406        T: Send + 'static,
407        Params: 'static,
408        Return: 'static,
409    {
410        // Intentionally not used today, but left here for future API
411        // compatibility with using this.
412        let _ = accessor;
413        call.call.await
414    }
415
416    /// Calls `concurrent::prepare_call` with monomorphized functions for
417    /// lowering the parameters and lifting the result according to the number
418    /// of core Wasm parameters and results in the signature of the function to
419    /// be called.
420    fn prepare_call<T>(
421        self,
422        store: StoreContextMut<'_, T>,
423        lower: impl FnOnce(
424            &mut LowerContext<T>,
425            InterfaceType,
426            &mut [MaybeUninit<ValRaw>],
427        ) -> Result<()>
428        + Send
429        + Sync
430        + 'static,
431    ) -> Result<PreparedCall<Return>>
432    where
433        Return: 'static,
434    {
435        use crate::component::storage::slice_to_storage;
436        debug_assert!(store.0.concurrency_support());
437
438        let param_count = if Params::flatten_count() <= MAX_FLAT_PARAMS {
439            Params::flatten_count()
440        } else {
441            1
442        };
443        let (options, _, ty, raw_options) = self.func().abi_info(store.0);
444        let instance = self.func().instance();
445        let max_results = if raw_options.async_ {
446            MAX_FLAT_PARAMS
447        } else {
448            MAX_FLAT_RESULTS
449        };
450
451        concurrent::prepare_call(
452            store,
453            *self.func(),
454            param_count,
455            move |store, params_out| {
456                Func::with_lower_context(instance, store, options, ty, |cx, ty| {
457                    lower(cx, ty, params_out)
458                })
459            },
460            move |store, results| {
461                let result = if Return::flatten_count() <= max_results {
462                    Func::with_lift_context(instance, store, options, ty, |cx, ty| {
463                        // SAFETY: Per the safety requirements documented for the
464                        // `ComponentType` trait, `Return::Lower` must be
465                        // compatible at the binary level with a `[ValRaw; N]`,
466                        // where `N` is `mem::size_of::<Return::Lower>() /
467                        // mem::size_of::<ValRaw>()`.  And since this function
468                        // is only used when `Return::flatten_count() <=
469                        // MAX_FLAT_RESULTS` and `MAX_FLAT_RESULTS == 1`, `N`
470                        // can only either be 0 or 1.
471                        //
472                        // See `ComponentInstance::exit_call` for where we use
473                        // the result count passed from
474                        // `wasmtime_environ::fact::trampoline`-generated code
475                        // to ensure the slice has the correct length, and also
476                        // `concurrent::start_call` for where we conservatively
477                        // use a slice length of 1 unconditionally.  Also note
478                        // that, as of this writing `slice_to_storage`
479                        // double-checks the slice length is sufficient.
480                        let results: &Return::Lower = unsafe { slice_to_storage(results) };
481                        Self::lift_stack_result(cx, ty, results)
482                    })?
483                } else {
484                    Func::with_lift_context(instance, store, options, ty, |cx, ty| {
485                        Self::lift_heap_result(cx, ty, &results[0])
486                    })?
487                };
488                Ok(Box::new(result))
489            },
490        )
491    }
492}
493
494impl<T> FuncCallConcurrent<'_, T> {
495    /// Returns the task group that this invocation corresponds to.
496    ///
497    /// This can be later correlated with calls to `TaskGroupHook` if the
498    /// `task-group-hook` feature is enabled.
499    pub fn group(&self) -> TaskGroupId {
500        self.call.group
501    }
502}
503
504impl<T, P, R> TypedFuncCallConcurrent<T, P, R> {
505    /// Returns the task group that this invocation corresponds to.
506    ///
507    /// This can be later correlated with calls to `TaskGroupHook` if the
508    /// `task-group-hook` feature is enabled.
509    pub fn group(&self) -> TaskGroupId {
510        self.call.group
511    }
512}