-
Notifications
You must be signed in to change notification settings - Fork 1.6k
refactor {Stream,Future}|{Reader,Writer} APIs and internals
#11325
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
refactor {Stream,Future}|{Reader,Writer} APIs and internals
#11325
Conversation
crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs
Outdated
Show resolved
Hide resolved
crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs
Outdated
Show resolved
Hide resolved
crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs
Outdated
Show resolved
Hide resolved
crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs
Outdated
Show resolved
Hide resolved
crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs
Outdated
Show resolved
Hide resolved
crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs
Outdated
Show resolved
Hide resolved
crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs
Outdated
Show resolved
Hide resolved
|
This is going to have conflicts with #11328, and I'll help work through those. |
30756dc to
86e9594
Compare
|
I've addressed all the feedback so far except that we're still lowering writes using same stack the host embedder called us on; I'll address that by using a fiber for lowering. |
86e9594 to
83ed03a
Compare
| impl<T> AccessorTask<T, Ctx, Result<()>> for Task { | ||
| async fn run(self, accessor: &Accessor<T, Ctx>) -> Result<()> { | ||
| let mut tx = self.tx; | ||
| let mut tx = GuardedStreamWriter::new(accessor, self.tx); |
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.
Think about this in the context of rebasing with wasi:sockets -- is this necessary? Tasks can only be cancelled for two reasons:
- The store is dropped - in this case there's no need to do further cleanup
- The
AbortHandleis triggered, but in this case it's statically known we just forget about that and ignore it.
Given that does WASI actually need Guarded*?
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.
Perhaps we don't need it, but it saves us from having to remember to close the handle explicitly before returning, which isn't too onerous for this simple task, but could become a maintenance hazard later.
wasmtime-wasi-http has some pretty complicated control flow, where keeping track of what to close when without RAII would be a real pain.
This makes a several changes to how `{Stream,Future}|{Reader,Writer}` work to
make them more efficient and, in some ways, more ergonomic:
- The background tasks have been removed, allowing reads and writes to complete without task context switching. We now only allocate and use oneshot channels lazily when the other end is not yet ready; this improves real world performance benchmarks (e.g. wasi-http request handling) considerably.
- Instances of `{Stream,Future}Reader` can now be lifted and lowered directly; no need for `Host{Stream,Future}` anymore.
- The type parameter for `Stream{Reader,Writer}` no longer refers to the buffer type -- just the payload type (i.e. `StreamReader<u8>` instead of `StreamReader<Vec<u8>>`), meaning any buffer type may be used for a given read or write operation. This also means the compiler needs help with type inference less often when calling `Instance::stream`.
- Instances of `{Stream,Future}|{Reader,Writer}` now require access to the store in order to be disposed of properly. I've added RAII wrapper structs (`WithAccessor[AndValue]`) to help with this, and also updated `Store::drop` and `Instance::run_concurrent` to ensure the store thread-local is set when dropping futures closing over `&Accessor`s.
- In order to ensure that resources containing `{Stream,Future}|{Reader,Writer}` instances are disposed of properly, I've added `LinkerInstance::resource_concurrent` and have updated `wasmtime-wit-bindgen` to use it. This gives resource drop functions access to a `StoreContextMut` via an `Accessor`, allowing the stream and future handles to be disposed of.
- In order to make this work, I had to change `Accessor::instance` from a `Instance` to an `Option<Instance>`, which is awkward but temporary since we're planning to remove `Accessor::instance` entirely once we've moved concurrent state from `ComponentInstance` to `Store`.
That problem of disposal is definitely the most awkward part of all this. In
simple cases, it's easy enough to ensure that read and write handles are
disposed of properly, but both `wasmtime-wasi` and `wasmtime-wasi-http` have
some pretty complicated functions where handles are passed between tasks and/or
stored inside resources, so it can be tricky to ensure proper disposal on all
code paths. I'm open to ideas for improving this, but I suspect we'll need new
Rust language features (e.g. linear types) to make it truly ergonomic, robust,
and efficient.
While testing the above, I discovered an issue with `Instance::poll_until` such
that it would prematurely give up and return a "deadlock" trap error, believing
that there was no further work to do, even though the future passed to it was
ready to resolve the next time it was polled. I've fixed this by polling it one
last time and only trapping if it returns pending.
Note that I've moved a few associated functions from `ConcurrentState` to
`Instance` (e.g. `guest_drop_writable` and others) since they now need access to
the store; they're unchanged otherwise. Apologies for the diff noise.
Finally, I've tweaked how `wasmtime serve` to poll the guest for content before
handing the response to Hyper, which helps performance by ensuring the first
content chunk can be sent with the same TCP packet as the beginning of the
response.
Signed-off-by: Joel Dice <joel.dice@fermyon.com>
fix wasi p3 build and test failures
Signed-off-by: Joel Dice <joel.dice@fermyon.com>
use `ManuallyDrop` instead of `Option` in `Dropper`
This allows us to drop its `value` field in-place, i.e. without moving it,
thereby upholding the `Pin` guarantee.
Signed-off-by: Joel Dice <joel.dice@fermyon.com>
address review comments
- Remove `DropWithStoreAndValue` and friends; go back to taking a `fn() -> T` parameter in `Instance::future` instead
- Make `DropWithStore::drop[_with]` take `&mut self` instead of `self`
- Make `WithAccessor` and `DropWithStore` private
- Instead, I've added public `Guarded{Stream,Future}{Reader,Writer}` types for RAII
- and also `{Stream,Future}{Reader,Writer}::close[_with]` methods
- Use RAII in `FutureReader::read` and `FutureWriter::write` to ensure handles are dropped if the `Future` is dropped
Signed-off-by: Joel Dice <joel.dice@fermyon.com>
This avoids unsoundness due to guest realloc calls while there are host embedder frames on the stack. Signed-off-by: Joel Dice <joel.dice@fermyon.com>
e4049ee to
0014c19
Compare
Signed-off-by: Joel Dice <joel.dice@fermyon.com>
0014c19 to
74ee080
Compare
|
@alexcrichton I believe I've addressed all your feedback so far. Let me know if you have anything else. BTW, the optimization for lowering flat payloads during host writes is pretty trivial: diff --git a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs
index 2809cb6b12..ed56fd7bce 100644
--- a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs
+++ b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs
@@ -1777,9 +1777,7 @@ impl Instance {
anyhow::Ok(result)
};
- if
- // TODO: Check if payload is "flat"
- false {
+ if T::IS_FLAT_TYPE {
// Optimize flat payloads (i.e. those which do not require
// calling the guest's realloc function) by lowering
// directly instead of using a oneshot::channel and
diff --git a/crates/wasmtime/src/runtime/component/func/typed.rs b/crates/wasmtime/src/runtime/component/func/typed.rs
index ad4328fce4..f27120957a 100644
--- a/crates/wasmtime/src/runtime/component/func/typed.rs
+++ b/crates/wasmtime/src/runtime/component/func/typed.rs
@@ -687,6 +687,9 @@ pub unsafe trait ComponentType: Send + Sync {
#[doc(hidden)]
const IS_RUST_UNIT_TYPE: bool = false;
+ #[doc(hidden)]
+ const IS_FLAT_TYPE: bool = false;
+
/// Returns the number of core wasm abi values will be used to represent
/// this type in its lowered form.
///
@@ -1016,6 +1019,8 @@ macro_rules! integers {
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::$abi;
+ const IS_FLAT_TYPE: bool = true;
+
fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> {
match ty {
InterfaceType::$ty => Ok(()), |
This fixes a minor merge conflict between bytecodealliance#11325 and bytecodealliance#11328 which isn't currently exercised by in-repo WASI bindings but will be soon once wasip3-prototyping is finished merging.
This commit is a refinement of bytecodealliance#11325 to use `.unwrap()` internally instead of ignoring errors from dropping futures and streams. Fallible drop isn't supported in Rust and these shouldn't panic assuming the host is properly matching handles to stores.
This commit is a refinement of bytecodealliance#11325 to use `.unwrap()` internally instead of ignoring errors from dropping futures and streams. Fallible drop isn't supported in Rust and these shouldn't panic assuming the host is properly matching handles to stores.
This is a follow-up to bytecodealliance#11325 with a number of cosmetic changes about the shape of the API and structure of the internals: * `{Stream,Future}{Reader,Writer}::guard` is now an alternative constructor to `Guard*::new` (import fewer types). * Internally `WithAccessor` and `DropWithStore` are removed in favor of direct `Drop for Guard*` impls. * An `Option` is used to replace `ManuallyDrop` and `unsafe` code. * `{Stream,Future}{Reader,Writer}::close{,_with}` now take `&mut self` instead of `self` to be more composable with `&mut self` arguments during `Drop` for other structures (e.g. build-your-own drop-with-store). * The type parameters on `Guard*` are simplified to just `T`, the future or stream payload, and `A: AsAccessor`. This helps cut down on the complexity of signatures. * `Guard*` types now have `into_{stream,future}` as an alternative to `.into()` which doesn't require type annotations.
This is a follow-up to bytecodealliance#11325 with a number of cosmetic changes about the shape of the API and structure of the internals: * `{Stream,Future}{Reader,Writer}::guard` is now an alternative constructor to `Guard*::new` (import fewer types). * Internally `WithAccessor` and `DropWithStore` are removed in favor of direct `Drop for Guard*` impls. * An `Option` is used to replace `ManuallyDrop` and `unsafe` code. * `{Stream,Future}{Reader,Writer}::close{,_with}` now take `&mut self` instead of `self` to be more composable with `&mut self` arguments during `Drop` for other structures (e.g. build-your-own drop-with-store). * The type parameters on `Guard*` are simplified to just `T`, the future or stream payload, and `A: AsAccessor`. This helps cut down on the complexity of signatures. * `Guard*` types now have `into_{stream,future}` as an alternative to `.into()` which doesn't require type annotations.
* Don't support fallible drop in futures_and_streams This commit is a refinement of #11325 to use `.unwrap()` internally instead of ignoring errors from dropping futures and streams. Fallible drop isn't supported in Rust and these shouldn't panic assuming the host is properly matching handles to stores. * Fix build of wasmtime-wasi * Don't empty the table during store destruction Leave it around while futures/fibers are being manually dropped so any destructors associated there get access to the table (as required by streams/futures/etc). * Remove unused import
This is a follow-up to bytecodealliance#11325 with a number of cosmetic changes about the shape of the API and structure of the internals: * `{Stream,Future}{Reader,Writer}::guard` is now an alternative constructor to `Guard*::new` (import fewer types). * Internally `WithAccessor` and `DropWithStore` are removed in favor of direct `Drop for Guard*` impls. * An `Option` is used to replace `ManuallyDrop` and `unsafe` code. * `{Stream,Future}{Reader,Writer}::close{,_with}` now take `&mut self` instead of `self` to be more composable with `&mut self` arguments during `Drop` for other structures (e.g. build-your-own drop-with-store). * The type parameters on `Guard*` are simplified to just `T`, the future or stream payload, and `A: AsAccessor`. This helps cut down on the complexity of signatures. * `Guard*` types now have `into_{stream,future}` as an alternative to `.into()` which doesn't require type annotations.
This is a follow-up to #11325 with a number of cosmetic changes about the shape of the API and structure of the internals: * `{Stream,Future}{Reader,Writer}::guard` is now an alternative constructor to `Guard*::new` (import fewer types). * Internally `WithAccessor` and `DropWithStore` are removed in favor of direct `Drop for Guard*` impls. * An `Option` is used to replace `ManuallyDrop` and `unsafe` code. * `{Stream,Future}{Reader,Writer}::close{,_with}` now take `&mut self` instead of `self` to be more composable with `&mut self` arguments during `Drop` for other structures (e.g. build-your-own drop-with-store). * The type parameters on `Guard*` are simplified to just `T`, the future or stream payload, and `A: AsAccessor`. This helps cut down on the complexity of signatures. * `Guard*` types now have `into_{stream,future}` as an alternative to `.into()` which doesn't require type annotations.
…dealliance#11325) * refactor `{Stream,Future}|{Reader,Writer}` APIs and internals This makes a several changes to how `{Stream,Future}|{Reader,Writer}` work to make them more efficient and, in some ways, more ergonomic: - The background tasks have been removed, allowing reads and writes to complete without task context switching. We now only allocate and use oneshot channels lazily when the other end is not yet ready; this improves real world performance benchmarks (e.g. wasi-http request handling) considerably. - Instances of `{Stream,Future}Reader` can now be lifted and lowered directly; no need for `Host{Stream,Future}` anymore. - The type parameter for `Stream{Reader,Writer}` no longer refers to the buffer type -- just the payload type (i.e. `StreamReader<u8>` instead of `StreamReader<Vec<u8>>`), meaning any buffer type may be used for a given read or write operation. This also means the compiler needs help with type inference less often when calling `Instance::stream`. - Instances of `{Stream,Future}|{Reader,Writer}` now require access to the store in order to be disposed of properly. I've added RAII wrapper structs (`WithAccessor[AndValue]`) to help with this, and also updated `Store::drop` and `Instance::run_concurrent` to ensure the store thread-local is set when dropping futures closing over `&Accessor`s. - In order to ensure that resources containing `{Stream,Future}|{Reader,Writer}` instances are disposed of properly, I've added `LinkerInstance::resource_concurrent` and have updated `wasmtime-wit-bindgen` to use it. This gives resource drop functions access to a `StoreContextMut` via an `Accessor`, allowing the stream and future handles to be disposed of. - In order to make this work, I had to change `Accessor::instance` from a `Instance` to an `Option<Instance>`, which is awkward but temporary since we're planning to remove `Accessor::instance` entirely once we've moved concurrent state from `ComponentInstance` to `Store`. That problem of disposal is definitely the most awkward part of all this. In simple cases, it's easy enough to ensure that read and write handles are disposed of properly, but both `wasmtime-wasi` and `wasmtime-wasi-http` have some pretty complicated functions where handles are passed between tasks and/or stored inside resources, so it can be tricky to ensure proper disposal on all code paths. I'm open to ideas for improving this, but I suspect we'll need new Rust language features (e.g. linear types) to make it truly ergonomic, robust, and efficient. While testing the above, I discovered an issue with `Instance::poll_until` such that it would prematurely give up and return a "deadlock" trap error, believing that there was no further work to do, even though the future passed to it was ready to resolve the next time it was polled. I've fixed this by polling it one last time and only trapping if it returns pending. Note that I've moved a few associated functions from `ConcurrentState` to `Instance` (e.g. `guest_drop_writable` and others) since they now need access to the store; they're unchanged otherwise. Apologies for the diff noise. Finally, I've tweaked how `wasmtime serve` to poll the guest for content before handing the response to Hyper, which helps performance by ensuring the first content chunk can be sent with the same TCP packet as the beginning of the response. Signed-off-by: Joel Dice <joel.dice@fermyon.com> fix wasi p3 build and test failures Signed-off-by: Joel Dice <joel.dice@fermyon.com> use `ManuallyDrop` instead of `Option` in `Dropper` This allows us to drop its `value` field in-place, i.e. without moving it, thereby upholding the `Pin` guarantee. Signed-off-by: Joel Dice <joel.dice@fermyon.com> address review comments - Remove `DropWithStoreAndValue` and friends; go back to taking a `fn() -> T` parameter in `Instance::future` instead - Make `DropWithStore::drop[_with]` take `&mut self` instead of `self` - Make `WithAccessor` and `DropWithStore` private - Instead, I've added public `Guarded{Stream,Future}{Reader,Writer}` types for RAII - and also `{Stream,Future}{Reader,Writer}::close[_with]` methods - Use RAII in `FutureReader::read` and `FutureWriter::write` to ensure handles are dropped if the `Future` is dropped Signed-off-by: Joel Dice <joel.dice@fermyon.com> * lower host stream/future writes in background task This avoids unsoundness due to guest realloc calls while there are host embedder frames on the stack. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * fix `tcp.rs` regressions Signed-off-by: Joel Dice <joel.dice@fermyon.com> --------- Signed-off-by: Joel Dice <joel.dice@fermyon.com>
* Account for concurrent resource destructors This fixes a minor merge conflict between bytecodealliance#11325 and bytecodealliance#11328 which isn't currently exercised by in-repo WASI bindings but will be soon once wasip3-prototyping is finished merging. * Update expanded test expectations
…11351) * Don't support fallible drop in futures_and_streams This commit is a refinement of bytecodealliance#11325 to use `.unwrap()` internally instead of ignoring errors from dropping futures and streams. Fallible drop isn't supported in Rust and these shouldn't panic assuming the host is properly matching handles to stores. * Fix build of wasmtime-wasi * Don't empty the table during store destruction Leave it around while futures/fibers are being manually dropped so any destructors associated there get access to the table (as required by streams/futures/etc). * Remove unused import
This is a follow-up to bytecodealliance#11325 with a number of cosmetic changes about the shape of the API and structure of the internals: * `{Stream,Future}{Reader,Writer}::guard` is now an alternative constructor to `Guard*::new` (import fewer types). * Internally `WithAccessor` and `DropWithStore` are removed in favor of direct `Drop for Guard*` impls. * An `Option` is used to replace `ManuallyDrop` and `unsafe` code. * `{Stream,Future}{Reader,Writer}::close{,_with}` now take `&mut self` instead of `self` to be more composable with `&mut self` arguments during `Drop` for other structures (e.g. build-your-own drop-with-store). * The type parameters on `Guard*` are simplified to just `T`, the future or stream payload, and `A: AsAccessor`. This helps cut down on the complexity of signatures. * `Guard*` types now have `into_{stream,future}` as an alternative to `.into()` which doesn't require type annotations.
This makes a several changes to how
{Stream,Future}|{Reader,Writer}work to make them more efficient and, in some ways, more ergonomic:The background tasks have been removed, allowing reads and writes to complete without task context switching. We now only allocate and use oneshot channels lazily when the other end is not yet ready; this improves real world performance benchmarks (e.g. wasi-http request handling) considerably.
Instances of
{Stream,Future}Readercan now be lifted and lowered directly; no need forHost{Stream,Future}anymore.The type parameter for
Stream{Reader,Writer}no longer refers to the buffer type -- just the payload type (i.e.StreamReader<u8>instead ofStreamReader<Vec<u8>>), meaning any buffer type may be used for a given read or write operation. This also means the compiler needs help with type inference less often when callingInstance::stream.Instances of
{Stream,Future}|{Reader,Writer}now require access to the store in order to be disposed of properly. I've added RAII wrapper structs (WithAccessor[AndValue]) to help with this, and also updatedStore::dropandInstance::run_concurrentto ensure the store thread-local is set when dropping futures closing over&Accessors.In order to ensure that resources containing
{Stream,Future}|{Reader,Writer}instances are disposed of properly, I've addedLinkerInstance::resource_concurrentand have updatedwasmtime-wit-bindgento use it. This gives resource drop functions access to aStoreContextMutvia anAccessor, allowing the stream and future handles to be disposed of.Accessor::instancefrom aInstanceto anOption<Instance>, which is awkward but temporary since we're planning to removeAccessor::instanceentirely once we've moved concurrent state fromComponentInstancetoStore.That problem of disposal is definitely the most awkward part of all this. In simple cases, it's easy enough to ensure that read and write handles are disposed of properly, but both
wasmtime-wasiandwasmtime-wasi-httphave some pretty complicated functions where handles are passed between tasks and/or stored inside resources, so it can be tricky to ensure proper disposal on all code paths. I'm open to ideas for improving this, but I suspect we'll need new Rust language features (e.g. linear types) to make it truly ergonomic, robust, and efficient.While testing the above, I discovered an issue with
Instance::poll_untilsuch that it would prematurely give up and return a "deadlock" trap error, believing that there was no further work to do, even though the future passed to it was ready to resolve the next time it was polled. I've fixed this by polling it one last time and only trapping if it returns pending.Note that I've moved a few associated functions from
ConcurrentStatetoInstance(e.g.guest_drop_writableand others) since they now need access to the store; they're unchanged otherwise. Apologies for the diff noise.Finally, I've tweaked how
wasmtime serveto poll the guest for content before handing the response to Hyper, which helps performance by ensuring the first content chunk can be sent with the same TCP packet as the beginning of the response.