-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wasi-io: Reimplement wasi-io/poll using a Pollable trait #7812
base: main
Are you sure you want to change the base?
Conversation
… Preview2 resources. Removed the _mut suffixes to align with WasiHttpView.
…er they were preopened or opened using open_at. This fixes build errors regarding overlapping mutable lifetimes introduced in the previous commit.
…is lower level, doesn't require heap allocations to poll, has mutable access to the WasiView, and can be used as a standalone resource without a parent. The Subscribe trait is kept intact, but this is now a utility interface, implemented in terms of Pollable.
…` implementation. And its is now superseded by take&restore
Subscribe to Label Actioncc @peterhuene
This issue or pull request has been labeled: "wasi", "wasmtime:api"
Thus the following users have been cc'd because of the following labels:
To subscribe or unsubscribe from this label, edit the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this is excellent work. The new internal interface is definitely superior to the old one, and I appreciate the new tests as well. The lease system for resource table is a much better interface than iter_entries.
I believe that all of the Slot
and SlotIdentity
implementation makes sense and is correct (especially because, surprisingly to me, the only unsafe is in unsafe impl Send
), but I wanted to tag @alexcrichton to double-check that part because I do not feel super confident in my ability to assess that code. If he is happy with that, this can land.
/// value, this function traps. | ||
/// This function traps if either: | ||
/// - the list is empty, or: | ||
/// - the list contains more elements than can be indexed with a `u32` value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree we need this change in the wits, lets just be sure to upstream these to the spec repo as well. We will come up with some process for how we keep the docs evolving and improving while assuring that the interface itself doesn't change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crates/wasi/src/preview2/poll.rs
Outdated
Ok(table.push_child(pollable, &resource)?) | ||
} | ||
|
||
/// A host representation of the `wasi:io/poll.pollable` resource. | ||
pub struct PollableResource { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very bikesheddy suggestion, so feel free to disregard it, but is BoxPollable
a better name for this? That way Resource<PollableResource>
isnt repeating the word.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The design has changed in the meantime. Now it's not just a box anymore, so I went with PollableHandle
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this! I've left a few comments but it's getting a bit later here so I'm going to head out. I want to read more about the Lease<T>
though as that looks quite subtle and I need to think more about it.
crates/wasi/src/preview2/poll.rs
Outdated
impl<T: Subscribe> Pollable for T { | ||
fn poll_ready(&mut self, cx: &mut Context<'_>, _view: &mut dyn WasiView) -> Poll<()> { | ||
self.ready().as_mut().poll(cx) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This I think is actually subtly incorrect because it drops the future after the call to poll
which signals that the future should be cancelled rather than keeping it alive until the whole poll is done. I think that means that we may not be guaranteed to get wakeups from cancelled futures, although we might get those for now given how the code is currently constructed. I think that this'll need to be a bit fancier with the adapter instead of having a blanket impl
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests were failing precisely because of that. To fix it, I ended up rougly back at the Subscribe
design, but this time returning a custom WasiFuture type that has an additional WasiView
parameter on its poll
method. To prevent scope creep of this PR, I kept Subscribe
alive for now (as PollableAsync
). But in the long run, I don't think there's much value in havin them both, as Subscribe
can be trivially converted to Pollable
from:
#[async_trait::async_trait]
impl PollableAsync for HostFutureIncomingResponse {
async fn ready(&mut self) {
if let Self::Pending(handle) = self {
*self = Self::Ready(handle.await);
}
}
}
to:
impl Pollable for HostFutureIncomingResponse {
fn ready<'a>(&'a mut self) -> Pin<Box<dyn WasiFuture<Output = ()> + Send + 'a>> {
Box::pin(async {
if let Self::Pending(handle) = self {
*self = Self::Ready(handle.await);
}
})
}
}
One obvious downside is the added visual noise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I've gotten a chance now to take a closer look at Lease<T>
and the changes ResourceTable
. Given this new API design for the pollable trait something along these lines is required (e.g. iter_children
can't work any more). The specific implementation here I think has a drawback where it's very "panicky" if you get it wrong. Most other aspects of WASI are "error-y" in that they try to return traps if anything is gotten wrong. This I think is pretty important for not accidentally becoming a DoS vector for embeddings. For example a panicking Drop
implementation means that an early-return in an embedder function might accidentally take down the whole process where a wasm trap would only take down a single instance.
Given that my main thought on this is that this should ideally not ever panic and instead should switch to returning errors where possible. I might also recommending going a little bit further perhaps with a scheme such as:
- Leave
TableEntry::entry
asBox<dyn Any>
. - Change
take
to returningBox<T>
. This would replaceentry
with something likeBox::new(Tombstone)
which is a private type to this module. - Change
restore
to takingBox<T>
andResource<T>
.
That way it's largely up to embedders to "get everything right" but they'd already be required to do so with this current API. Additionally any failed downcasts can additionally add a check for Tombstone
to perhaps return a more precise error other than ResourceTableError::WrongType
with a new variant such as TakenValue
.
Thanks for the feedback. One thing that's not in this PR, but I assume will most likely be added at some point, are untyped Anyway, I'm fine with your suggestions. I just wanted to make sure the trade-offs are known. |
Hm ok, for that I think you have a good point about accidentally messing up these APIs. I think this may still be surmountable perhaps with some trickery, but I'd also need to see the usage of |
… are now nearly the same.
I chose to go with a hybrid approach. For the public API, I changed it to what you suggested. Internally, I removed Lease & SlotIdentity. But I kept Slot to perform the resource type check. Also, as part of the updated design (see above) I needed |
Reading over this and see how this all turned out, I'm personally starting to get second thoughts on this. We're effectively reimplementing our own Now that's all easy to say but this PR is still solving a concrete problem which is letting implementations access resources while polling, so I don't think simply closing this PR is an option. That being said after having read over this I wonder if there's perhaps an alternative implementation route that we can take. Originally when designing To solve the mutability problem I realized that the How would you feel about something like that? We could still preserve While I also realize though that you've probably already put in a great deal of work to this PR with 2 versions now so I'm hesitant to ask for a third. I'd be happy to help sketch this out and do some of the refactoring work to see if I feel like it's going to pay off. |
I think you mean changing
to
right? That doesn;t work because |
Good point, yes, I'm more-or-less saying we should do that. (either that or use a task-local but I think that still captures Mind trying make |
I understand your concerns, yet I'd rather not go for round three right now, which would include reverting #7802. So instead, I've changed the questionable types to be private to the Hope that's OK for you |
Wanted to say I have not forgotten about this, I have been looking for time to write up something longer-form, which I hope to get to by tomorrow. Is this blocking anything though that it would be prudent to land now rather than later? If so I think it's good to go as-is, but otherwise I'd like to take some more time to write up longer-form thoughts. |
There's no immediate rush from my side, so feel free to take your time. |
Ok thanks again for your patience here, very much appreciated! I've gotten some time to think and work on this. I was leaning towards merging this, but then I realized that I'd prefer to avoid a situation where we land this and then later revert most of it towards a different strategy. In that sense I wanted, time permitting, to take a moment and figure out if alternative strategies would work. I'm getting a growing sense of unease with this direction as it's more-or-less a custom So assuming that the main goal of this PR is to get access to After talking a bit more with @pchickey, however, I'm growing more fond of the idea of using
The major consequences of this decision, however, are:
Personally I'm inclined to take a route that looks like this, namely threading arguments through |
also cc @elliottt since you've touched a lot of WASI internals and you probably want to take a look too |
Prior discussion: https://bytecodealliance.zulipchat.com/#narrow/stream/217126-wasmtime/topic/Change.20Subscribe.20trait
Pollable
struct toPollableResource
Pollable
trait which is lower level, doesn't require heap allocations to poll, has mutable access to the WasiView, and can be used as a standalone resource without a parent. The Subscribe trait is kept intact, but this is now a utility interface, implemented in terms of Pollable.take
&restore
as a general purpose replacement foriter_entries
. That one was used only by the oldpoll
implementation.Additionally: