In the preview2 WASI implementation I'd like the ability for child resources to obtain mutable access to their parent. For most cases I can achieve this by storing a Resource<TParent>
in the child resource and then, within the child methods, use self.table_mut()
to get a mutable reference to its parent. One roadblock, however, is Subscribe
. The current design of that trait prohibits access the ResourceTable from within the ready
implementation.
I have a refactor in mind that would enable this, but this will require a change in the signature of Subscribe
.
From:
#[async_trait::async_trait]
pub trait Subscribe: Send + 'static {
async fn ready(&mut self);
}
To:
pub trait Subscribe: Send + 'static {
fn poll_ready(&mut self, cx: &mut Context<'_>, table: &mut ResourceTable) -> Poll<()>;
}
Would this be OK?
What is the need for Context<'_>
?
What I have in mind is something like this: (abbreviated)
#[async_trait::async_trait]
impl<T: WasiView> poll::Host for T {
async fn poll(&mut self, pollables: Vec<Resource<PollableResource>>) -> Result<Vec<u32>> {
futures::future::poll_fn(move |cx| {
let mut results = Vec::new();
for (pollable, i) in pollables.iter_mut() {
match pollable.poll_ready(cx, table) { // HERE
Poll::Ready(()) => {
results.push(*i);
}
Poll::Pending => {}
}
}
if results.is_empty() {
Poll::Pending
} else {
Poll::Ready(results)
}
})
.await
}
}
Giving access to the table I think would make sense yeah, but not being able to use async
would I think be a pretty big thing to overcome for consumers. While it's possible to translate all the existing things to manual state machines it's pretty nice being able to use async
.
My knee-jerk to this was "Could &ResourceTable
be passed as an argument to the async fn
? Given the recent de-Sync
-ification though I think that would mean that the returned future wouldn't be Send
so that probably wouldn't work.
That being said this is definitely something we've felt, there's internal Arc
s/etc to handle this ownership boundary.
"Could &ResourceTable be passed as an argument to the async fn? Given the recent de-Sync-ification though I think that would mean that the returned future wouldn't be Send so that probably wouldn't work.
Also, then you wouldn't be able to poll more than one pollable. Because the mutable reference would be moved into the first future.
not being able to use async would I think be a pretty big thing to overcome for consumers.
poll_ready is lower level than async so it should be pretty straightforward to build async
implementations on top of poll_ready. E.g.
#[async_trait::async_trait]
pub trait Subscribe: Send + 'static {
fn poll_ready(&mut self, cx: &mut Context<'_>, table: &mut ResourceTable) -> Poll<()> {
self.ready().as_mut().poll(cx)
}
async fn ready(&mut self) {}
}
Either within one single trait, or as two separate traits
rust doesn't really do the pattern where you must implement at most one method well unfortunately, b/c if callers call ready
instead of poll_ready
they'll get the wrong behavior if you override poll_ready
only
Yeah, so then as two separate traits
I could see something like that working yeha
where if you want async
you can and if you don't you don't have to
Roughly:
pub trait Pollable: Send + 'static {
fn poll_ready(&mut self, cx: &mut Context<'_>, table: &mut ResourceTable) -> Poll<()>;
}
#[async_trait::async_trait]
pub trait Subscribe: Send + 'static {
async fn ready(&mut self);
}
where Subscribe
is a convenience interface over Pollable
yeah that looks reasonable to me
and an adapter to make a Pollable
from a Subscribe
or something like that
ok thanks i missed the change from async fn
to -> Poll
, so yeah monday morning coffee problem
i think the design you just came to with alex sounds right?
ive found myself wondering if theres some better way to hide the implementation detail of ResourceTable so that we can have much more straightforward references back and forth between children/parents
"sure would be nice to have Gc<T>
" heh
things of that nature
Last updated: Dec 23 2024 at 14:03 UTC