wasmtime_wasi_io/poll.rs
1use alloc::boxed::Box;
2use anyhow::Result;
3use core::any::Any;
4use core::future::Future;
5use core::pin::Pin;
6use wasmtime::component::{Resource, ResourceTable};
7
8pub type DynFuture<'a> = Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
9pub type MakeFuture = for<'a> fn(&'a mut dyn Any) -> DynFuture<'a>;
10
11/// The host representation of the `wasi:io/poll.pollable` resource.
12///
13/// A pollable is not the same thing as a Rust Future: the same pollable may be used to
14/// repeatedly check for readiness of a given condition, e.g. if a stream is readable
15/// or writable. So, rather than containing a Future, which can only become Ready once, a
16/// `DynPollable` contains a way to create a Future in each call to `poll`.
17pub struct DynPollable {
18 pub(crate) index: u32,
19 pub(crate) make_future: MakeFuture,
20 pub(crate) remove_index_on_delete: Option<fn(&mut ResourceTable, u32) -> Result<()>>,
21}
22
23/// The trait used to implement [`DynPollable`] to create a `pollable`
24/// resource in `wasi:io/poll`.
25///
26/// This trait is the internal implementation detail of any pollable resource in
27/// this crate's implementation of WASI. The `ready` function is an `async fn`
28/// which resolves when the implementation is ready. Using native `async` Rust
29/// enables this type's readiness to compose with other types' readiness
30/// throughout the WASI implementation.
31///
32/// This trait is used in conjunction with [`subscribe`] to create a `pollable`
33/// resource.
34///
35/// # Example
36///
37/// This is a simple example of creating a `Pollable` resource from a few
38/// parameters.
39///
40/// ```
41/// # // stub out so we don't need a dep to build the doctests:
42/// # mod tokio { pub mod time { pub use std::time::{Duration, Instant}; pub async fn sleep_until(_:
43/// Instant) {} } }
44/// use tokio::time::{self, Duration, Instant};
45/// use wasmtime_wasi_io::{IoView, poll::{Pollable, subscribe, DynPollable}, async_trait};
46/// use wasmtime::component::Resource;
47/// use wasmtime::Result;
48///
49/// fn sleep(cx: &mut dyn IoView, dur: Duration) -> Result<Resource<DynPollable>> {
50/// let end = Instant::now() + dur;
51/// let sleep = MySleep { end };
52/// let sleep_resource = cx.table().push(sleep)?;
53/// subscribe(cx.table(), sleep_resource)
54/// }
55///
56/// struct MySleep {
57/// end: Instant,
58/// }
59///
60/// #[async_trait]
61/// impl Pollable for MySleep {
62/// async fn ready(&mut self) {
63/// tokio::time::sleep_until(self.end).await;
64/// }
65/// }
66/// ```
67#[async_trait::async_trait]
68pub trait Pollable: Send + 'static {
69 /// An asynchronous function which resolves when this object's readiness
70 /// operation is ready.
71 ///
72 /// This function is invoked as part of `poll` in `wasi:io/poll`. The
73 /// meaning of when this function Returns depends on what object this
74 /// [`Pollable`] is attached to. When the returned future resolves then the
75 /// corresponding call to `wasi:io/poll` will return.
76 ///
77 /// Note that this method does not return an error. Returning an error
78 /// should be done through accessors on the object that this `pollable` is
79 /// connected to. The call to `wasi:io/poll` itself does not return errors,
80 /// only a list of ready objects.
81 async fn ready(&mut self);
82}
83
84/// Creates a `wasi:io/poll/pollable` resource which is subscribed to the provided
85/// `resource`.
86///
87/// If `resource` is an owned resource then it will be deleted when the returned
88/// resource is deleted. Otherwise the returned resource is considered a "child"
89/// of the given `resource` which means that the given resource cannot be
90/// deleted while the `pollable` is still alive.
91pub fn subscribe<T>(
92 table: &mut ResourceTable,
93 resource: Resource<T>,
94) -> Result<Resource<DynPollable>>
95where
96 T: Pollable,
97{
98 fn make_future<'a, T>(stream: &'a mut dyn Any) -> DynFuture<'a>
99 where
100 T: Pollable,
101 {
102 stream.downcast_mut::<T>().unwrap().ready()
103 }
104
105 let pollable = DynPollable {
106 index: resource.rep(),
107 remove_index_on_delete: if resource.owned() {
108 Some(|table, idx| {
109 let resource = Resource::<T>::new_own(idx);
110 table.delete(resource)?;
111 Ok(())
112 })
113 } else {
114 None
115 },
116 make_future: make_future::<T>,
117 };
118
119 Ok(table.push_child(pollable, &resource)?)
120}