From b4e74e7fb805e94ec3b0f19db2ff5df3d1ecadbb Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Thu, 17 Jul 2025 15:10:50 -0600 Subject: [PATCH 1/5] 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` instead of `StreamReader>`), 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, but they can only provide a "best-effort" guarantee since using an `Accessor` from within a `Drop::drop` implementation won't always work. - 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`, 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 --- .../tests/expanded/char_concurrent.rs | 4 +- .../tests/expanded/conventions_concurrent.rs | 24 +- .../tests/expanded/dead-code_concurrent.rs | 2 +- .../expanded/direct-import_concurrent.rs | 2 +- .../tests/expanded/flags_concurrent.rs | 14 +- .../tests/expanded/floats_concurrent.rs | 8 +- .../tests/expanded/host-world_concurrent.rs | 2 +- .../tests/expanded/integers_concurrent.rs | 36 +- .../tests/expanded/lists_concurrent.rs | 58 +- .../expanded/many-arguments_concurrent.rs | 4 +- .../tests/expanded/multiversion_concurrent.rs | 4 +- .../tests/expanded/records_concurrent.rs | 22 +- .../tests/expanded/rename_concurrent.rs | 2 +- .../expanded/resources-export_concurrent.rs | 38 +- .../expanded/resources-import_concurrent.rs | 198 +- .../tests/expanded/share-types_concurrent.rs | 2 +- .../expanded/simple-functions_concurrent.rs | 12 +- .../tests/expanded/simple-lists_concurrent.rs | 8 +- .../tests/expanded/simple-wasi_concurrent.rs | 4 +- .../expanded/small-anonymous_concurrent.rs | 2 +- .../tests/expanded/smoke_concurrent.rs | 2 +- .../tests/expanded/strings_concurrent.rs | 6 +- .../expanded/unstable-features_concurrent.rs | 82 +- .../expanded/unversioned-foo_concurrent.rs | 2 +- .../tests/expanded/use-paths_concurrent.rs | 8 +- .../tests/expanded/variants_concurrent.rs | 40 +- .../src/resource_stream.rs | 28 +- .../tests/scenario/streams.rs | 264 ++- .../tests/scenario/transmit.rs | 115 +- crates/wasi-http/src/p3/bindings.rs | 2 + crates/wasi-http/src/p3/body.rs | 32 +- crates/wasi-http/src/p3/host/handler.rs | 38 +- crates/wasi-http/src/p3/host/mod.rs | 9 - crates/wasi-http/src/p3/host/types.rs | 168 +- crates/wasi-http/src/p3/response.rs | 10 +- crates/wasi/src/p3/cli/host.rs | 22 +- crates/wasi/src/p3/filesystem/host.rs | 299 +-- crates/wasi/src/p3/mod.rs | 8 +- crates/wasi/src/p3/sockets/host/types/tcp.rs | 75 +- .../src/runtime/component/concurrent.rs | 102 +- .../concurrent/futures_and_streams.rs | 2016 ++++++++--------- .../src/runtime/component/concurrent/tls.rs | 8 + .../runtime/component/concurrent_disabled.rs | 8 +- .../wasmtime/src/runtime/component/linker.rs | 53 + crates/wasmtime/src/runtime/component/mod.rs | 6 +- .../wasmtime/src/runtime/component/values.rs | 10 +- .../src/runtime/vm/component/libcalls.rs | 6 +- crates/wit-bindgen/src/lib.rs | 158 +- crates/wit-bindgen/src/rust.rs | 4 +- src/commands/serve.rs | 52 +- 50 files changed, 2102 insertions(+), 1977 deletions(-) diff --git a/crates/component-macro/tests/expanded/char_concurrent.rs b/crates/component-macro/tests/expanded/char_concurrent.rs index 90a8575df5..e65536df88 100644 --- a/crates/component-macro/tests/expanded/char_concurrent.rs +++ b/crates/component-macro/tests/expanded/char_concurrent.rs @@ -206,7 +206,7 @@ pub mod foo { "take-char", move |caller: &wasmtime::component::Accessor, (arg0,): (char,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::take_char(accessor, arg0) .await; Ok(r) @@ -217,7 +217,7 @@ pub mod foo { "return-char", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::return_char(accessor).await; Ok((r,)) }) diff --git a/crates/component-macro/tests/expanded/conventions_concurrent.rs b/crates/component-macro/tests/expanded/conventions_concurrent.rs index 5974503978..d28cac5e71 100644 --- a/crates/component-macro/tests/expanded/conventions_concurrent.rs +++ b/crates/component-macro/tests/expanded/conventions_concurrent.rs @@ -294,7 +294,7 @@ pub mod foo { "kebab-case", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::kebab_case(accessor).await; Ok(r) }) @@ -307,7 +307,7 @@ pub mod foo { (arg0,): (LudicrousSpeed,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::foo(accessor, arg0).await; Ok(r) }) @@ -317,7 +317,7 @@ pub mod foo { "function-with-dashes", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::function_with_dashes(accessor) .await; Ok(r) @@ -328,7 +328,7 @@ pub mod foo { "function-with-no-weird-characters", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::function_with_no_weird_characters( accessor, ) @@ -341,7 +341,7 @@ pub mod foo { "apple", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::apple(accessor).await; Ok(r) }) @@ -351,7 +351,7 @@ pub mod foo { "apple-pear", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::apple_pear(accessor).await; Ok(r) }) @@ -361,7 +361,7 @@ pub mod foo { "apple-pear-grape", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::apple_pear_grape(accessor) .await; Ok(r) @@ -372,7 +372,7 @@ pub mod foo { "a0", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a0(accessor).await; Ok(r) }) @@ -382,7 +382,7 @@ pub mod foo { "is-XML", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::is_xml(accessor).await; Ok(r) }) @@ -392,7 +392,7 @@ pub mod foo { "explicit", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::explicit(accessor).await; Ok(r) }) @@ -402,7 +402,7 @@ pub mod foo { "explicit-kebab", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::explicit_kebab(accessor) .await; Ok(r) @@ -413,7 +413,7 @@ pub mod foo { "bool", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::bool(accessor).await; Ok(r) }) diff --git a/crates/component-macro/tests/expanded/dead-code_concurrent.rs b/crates/component-macro/tests/expanded/dead-code_concurrent.rs index 1c525d87a0..caf5db15d8 100644 --- a/crates/component-macro/tests/expanded/dead-code_concurrent.rs +++ b/crates/component-macro/tests/expanded/dead-code_concurrent.rs @@ -213,7 +213,7 @@ pub mod a { "f", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::f(accessor).await; Ok((r,)) }) diff --git a/crates/component-macro/tests/expanded/direct-import_concurrent.rs b/crates/component-macro/tests/expanded/direct-import_concurrent.rs index ddf3ec7052..e5d0de6d1f 100644 --- a/crates/component-macro/tests/expanded/direct-import_concurrent.rs +++ b/crates/component-macro/tests/expanded/direct-import_concurrent.rs @@ -170,7 +170,7 @@ const _: () = { "foo", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::foo(accessor).await; Ok(r) }) diff --git a/crates/component-macro/tests/expanded/flags_concurrent.rs b/crates/component-macro/tests/expanded/flags_concurrent.rs index 22771b77b1..e92bde4e0d 100644 --- a/crates/component-macro/tests/expanded/flags_concurrent.rs +++ b/crates/component-macro/tests/expanded/flags_concurrent.rs @@ -349,7 +349,7 @@ pub mod foo { "roundtrip-flag1", move |caller: &wasmtime::component::Accessor, (arg0,): (Flag1,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::roundtrip_flag1( accessor, arg0, @@ -363,7 +363,7 @@ pub mod foo { "roundtrip-flag2", move |caller: &wasmtime::component::Accessor, (arg0,): (Flag2,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::roundtrip_flag2( accessor, arg0, @@ -377,7 +377,7 @@ pub mod foo { "roundtrip-flag4", move |caller: &wasmtime::component::Accessor, (arg0,): (Flag4,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::roundtrip_flag4( accessor, arg0, @@ -391,7 +391,7 @@ pub mod foo { "roundtrip-flag8", move |caller: &wasmtime::component::Accessor, (arg0,): (Flag8,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::roundtrip_flag8( accessor, arg0, @@ -405,7 +405,7 @@ pub mod foo { "roundtrip-flag16", move |caller: &wasmtime::component::Accessor, (arg0,): (Flag16,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::roundtrip_flag16( accessor, arg0, @@ -419,7 +419,7 @@ pub mod foo { "roundtrip-flag32", move |caller: &wasmtime::component::Accessor, (arg0,): (Flag32,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::roundtrip_flag32( accessor, arg0, @@ -433,7 +433,7 @@ pub mod foo { "roundtrip-flag64", move |caller: &wasmtime::component::Accessor, (arg0,): (Flag64,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::roundtrip_flag64( accessor, arg0, diff --git a/crates/component-macro/tests/expanded/floats_concurrent.rs b/crates/component-macro/tests/expanded/floats_concurrent.rs index 7d8cf0d06d..dd21ad33da 100644 --- a/crates/component-macro/tests/expanded/floats_concurrent.rs +++ b/crates/component-macro/tests/expanded/floats_concurrent.rs @@ -217,7 +217,7 @@ pub mod foo { "f32-param", move |caller: &wasmtime::component::Accessor, (arg0,): (f32,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::f32_param(accessor, arg0) .await; Ok(r) @@ -228,7 +228,7 @@ pub mod foo { "f64-param", move |caller: &wasmtime::component::Accessor, (arg0,): (f64,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::f64_param(accessor, arg0) .await; Ok(r) @@ -239,7 +239,7 @@ pub mod foo { "f32-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::f32_result(accessor).await; Ok((r,)) }) @@ -249,7 +249,7 @@ pub mod foo { "f64-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::f64_result(accessor).await; Ok((r,)) }) diff --git a/crates/component-macro/tests/expanded/host-world_concurrent.rs b/crates/component-macro/tests/expanded/host-world_concurrent.rs index fab15db7ef..356c47fb0c 100644 --- a/crates/component-macro/tests/expanded/host-world_concurrent.rs +++ b/crates/component-macro/tests/expanded/host-world_concurrent.rs @@ -170,7 +170,7 @@ const _: () = { "foo", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::foo(accessor).await; Ok(r) }) diff --git a/crates/component-macro/tests/expanded/integers_concurrent.rs b/crates/component-macro/tests/expanded/integers_concurrent.rs index 6f373dbaf6..d6e9e0cf77 100644 --- a/crates/component-macro/tests/expanded/integers_concurrent.rs +++ b/crates/component-macro/tests/expanded/integers_concurrent.rs @@ -301,7 +301,7 @@ pub mod foo { "a1", move |caller: &wasmtime::component::Accessor, (arg0,): (u8,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a1(accessor, arg0).await; Ok(r) }) @@ -311,7 +311,7 @@ pub mod foo { "a2", move |caller: &wasmtime::component::Accessor, (arg0,): (i8,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a2(accessor, arg0).await; Ok(r) }) @@ -321,7 +321,7 @@ pub mod foo { "a3", move |caller: &wasmtime::component::Accessor, (arg0,): (u16,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a3(accessor, arg0).await; Ok(r) }) @@ -331,7 +331,7 @@ pub mod foo { "a4", move |caller: &wasmtime::component::Accessor, (arg0,): (i16,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a4(accessor, arg0).await; Ok(r) }) @@ -341,7 +341,7 @@ pub mod foo { "a5", move |caller: &wasmtime::component::Accessor, (arg0,): (u32,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a5(accessor, arg0).await; Ok(r) }) @@ -351,7 +351,7 @@ pub mod foo { "a6", move |caller: &wasmtime::component::Accessor, (arg0,): (i32,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a6(accessor, arg0).await; Ok(r) }) @@ -361,7 +361,7 @@ pub mod foo { "a7", move |caller: &wasmtime::component::Accessor, (arg0,): (u64,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a7(accessor, arg0).await; Ok(r) }) @@ -371,7 +371,7 @@ pub mod foo { "a8", move |caller: &wasmtime::component::Accessor, (arg0,): (i64,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a8(accessor, arg0).await; Ok(r) }) @@ -393,7 +393,7 @@ pub mod foo { ): (u8, i8, u16, i16, u32, i32, u64, i64)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a9( accessor, arg0, @@ -414,7 +414,7 @@ pub mod foo { "r1", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::r1(accessor).await; Ok((r,)) }) @@ -424,7 +424,7 @@ pub mod foo { "r2", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::r2(accessor).await; Ok((r,)) }) @@ -434,7 +434,7 @@ pub mod foo { "r3", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::r3(accessor).await; Ok((r,)) }) @@ -444,7 +444,7 @@ pub mod foo { "r4", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::r4(accessor).await; Ok((r,)) }) @@ -454,7 +454,7 @@ pub mod foo { "r5", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::r5(accessor).await; Ok((r,)) }) @@ -464,7 +464,7 @@ pub mod foo { "r6", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::r6(accessor).await; Ok((r,)) }) @@ -474,7 +474,7 @@ pub mod foo { "r7", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::r7(accessor).await; Ok((r,)) }) @@ -484,7 +484,7 @@ pub mod foo { "r8", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::r8(accessor).await; Ok((r,)) }) @@ -494,7 +494,7 @@ pub mod foo { "pair-ret", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::pair_ret(accessor).await; Ok((r,)) }) diff --git a/crates/component-macro/tests/expanded/lists_concurrent.rs b/crates/component-macro/tests/expanded/lists_concurrent.rs index 8052e22323..4448cd4b0b 100644 --- a/crates/component-macro/tests/expanded/lists_concurrent.rs +++ b/crates/component-macro/tests/expanded/lists_concurrent.rs @@ -585,7 +585,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_u8_param(accessor, arg0) .await; Ok(r) @@ -599,7 +599,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_u16_param(accessor, arg0) .await; Ok(r) @@ -613,7 +613,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_u32_param(accessor, arg0) .await; Ok(r) @@ -627,7 +627,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_u64_param(accessor, arg0) .await; Ok(r) @@ -641,7 +641,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_s8_param(accessor, arg0) .await; Ok(r) @@ -655,7 +655,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_s16_param(accessor, arg0) .await; Ok(r) @@ -669,7 +669,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_s32_param(accessor, arg0) .await; Ok(r) @@ -683,7 +683,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_s64_param(accessor, arg0) .await; Ok(r) @@ -697,7 +697,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_f32_param(accessor, arg0) .await; Ok(r) @@ -711,7 +711,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_f64_param(accessor, arg0) .await; Ok(r) @@ -722,7 +722,7 @@ pub mod foo { "list-u8-ret", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_u8_ret(accessor).await; Ok((r,)) }) @@ -732,7 +732,7 @@ pub mod foo { "list-u16-ret", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_u16_ret(accessor).await; Ok((r,)) }) @@ -742,7 +742,7 @@ pub mod foo { "list-u32-ret", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_u32_ret(accessor).await; Ok((r,)) }) @@ -752,7 +752,7 @@ pub mod foo { "list-u64-ret", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_u64_ret(accessor).await; Ok((r,)) }) @@ -762,7 +762,7 @@ pub mod foo { "list-s8-ret", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_s8_ret(accessor).await; Ok((r,)) }) @@ -772,7 +772,7 @@ pub mod foo { "list-s16-ret", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_s16_ret(accessor).await; Ok((r,)) }) @@ -782,7 +782,7 @@ pub mod foo { "list-s32-ret", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_s32_ret(accessor).await; Ok((r,)) }) @@ -792,7 +792,7 @@ pub mod foo { "list-s64-ret", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_s64_ret(accessor).await; Ok((r,)) }) @@ -802,7 +802,7 @@ pub mod foo { "list-f32-ret", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_f32_ret(accessor).await; Ok((r,)) }) @@ -812,7 +812,7 @@ pub mod foo { "list-f64-ret", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_f64_ret(accessor).await; Ok((r,)) }) @@ -825,7 +825,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec<(u8, i8)>,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::tuple_list(accessor, arg0) .await; Ok((r,)) @@ -845,7 +845,7 @@ pub mod foo { )| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::string_list_arg( accessor, arg0, @@ -859,7 +859,7 @@ pub mod foo { "string-list-ret", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::string_list_ret(accessor) .await; Ok((r,)) @@ -879,7 +879,7 @@ pub mod foo { )| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::tuple_string_list( accessor, arg0, @@ -902,7 +902,7 @@ pub mod foo { )| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::string_list(accessor, arg0) .await; Ok((r,)) @@ -916,7 +916,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::record_list(accessor, arg0) .await; Ok((r,)) @@ -930,7 +930,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::record_list_reverse( accessor, arg0, @@ -947,7 +947,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::variant_list(accessor, arg0) .await; Ok((r,)) @@ -961,7 +961,7 @@ pub mod foo { (arg0,): (LoadStoreAllSizes,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::load_store_everything( accessor, arg0, diff --git a/crates/component-macro/tests/expanded/many-arguments_concurrent.rs b/crates/component-macro/tests/expanded/many-arguments_concurrent.rs index a8c2298502..ee12d1cc90 100644 --- a/crates/component-macro/tests/expanded/many-arguments_concurrent.rs +++ b/crates/component-macro/tests/expanded/many-arguments_concurrent.rs @@ -340,7 +340,7 @@ pub mod foo { )| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::many_args( accessor, arg0, @@ -372,7 +372,7 @@ pub mod foo { (arg0,): (BigStruct,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::big_argument(accessor, arg0) .await; Ok(r) diff --git a/crates/component-macro/tests/expanded/multiversion_concurrent.rs b/crates/component-macro/tests/expanded/multiversion_concurrent.rs index 32e64fd34f..2a7ea3a991 100644 --- a/crates/component-macro/tests/expanded/multiversion_concurrent.rs +++ b/crates/component-macro/tests/expanded/multiversion_concurrent.rs @@ -209,7 +209,7 @@ pub mod my { "x", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::x(accessor).await; Ok(r) }) @@ -249,7 +249,7 @@ pub mod my { "x", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::x(accessor).await; Ok(r) }) diff --git a/crates/component-macro/tests/expanded/records_concurrent.rs b/crates/component-macro/tests/expanded/records_concurrent.rs index 2a03557b83..8f00b4cd51 100644 --- a/crates/component-macro/tests/expanded/records_concurrent.rs +++ b/crates/component-macro/tests/expanded/records_concurrent.rs @@ -414,7 +414,7 @@ pub mod foo { (arg0,): ((char, u32),)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::tuple_arg(accessor, arg0) .await; Ok(r) @@ -425,7 +425,7 @@ pub mod foo { "tuple-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::tuple_result(accessor).await; Ok((r,)) }) @@ -435,7 +435,7 @@ pub mod foo { "empty-arg", move |caller: &wasmtime::component::Accessor, (arg0,): (Empty,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::empty_arg(accessor, arg0) .await; Ok(r) @@ -446,7 +446,7 @@ pub mod foo { "empty-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::empty_result(accessor).await; Ok((r,)) }) @@ -459,7 +459,7 @@ pub mod foo { (arg0,): (Scalars,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::scalar_arg(accessor, arg0) .await; Ok(r) @@ -470,7 +470,7 @@ pub mod foo { "scalar-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::scalar_result(accessor).await; Ok((r,)) }) @@ -483,7 +483,7 @@ pub mod foo { (arg0,): (ReallyFlags,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::flags_arg(accessor, arg0) .await; Ok(r) @@ -494,7 +494,7 @@ pub mod foo { "flags-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::flags_result(accessor).await; Ok((r,)) }) @@ -507,7 +507,7 @@ pub mod foo { (arg0,): (Aggregates,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::aggregate_arg(accessor, arg0) .await; Ok(r) @@ -518,7 +518,7 @@ pub mod foo { "aggregate-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::aggregate_result(accessor) .await; Ok((r,)) @@ -532,7 +532,7 @@ pub mod foo { (arg0,): (TupleTypedef2,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::typedef_inout(accessor, arg0) .await; Ok((r,)) diff --git a/crates/component-macro/tests/expanded/rename_concurrent.rs b/crates/component-macro/tests/expanded/rename_concurrent.rs index df4c9d5b35..971f1d88a3 100644 --- a/crates/component-macro/tests/expanded/rename_concurrent.rs +++ b/crates/component-macro/tests/expanded/rename_concurrent.rs @@ -220,7 +220,7 @@ pub mod foo { "foo", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::foo(accessor).await; Ok((r,)) }) diff --git a/crates/component-macro/tests/expanded/resources-export_concurrent.rs b/crates/component-macro/tests/expanded/resources-export_concurrent.rs index 9cfa5adee1..85292a832f 100644 --- a/crates/component-macro/tests/expanded/resources-export_concurrent.rs +++ b/crates/component-macro/tests/expanded/resources-export_concurrent.rs @@ -185,7 +185,7 @@ const _: () = { host_getter: fn(&mut T) -> D::Data<'_>, ) -> wasmtime::Result<()> where - D: wasmtime::component::HasData, + D: foo::foo::transitive_import::HostConcurrent + Send, for<'a> D::Data<'a>: foo::foo::transitive_import::Host + Send, T: 'static + Send, { @@ -220,21 +220,20 @@ pub mod foo { use wasmtime::component::__internal::{anyhow, Box}; pub enum Y {} #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] - pub trait HostY: Send { - async fn drop( - &mut self, + pub trait HostYConcurrent: wasmtime::component::HasData + Send { + fn drop( + accessor: &wasmtime::component::Accessor, rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()>; - } - impl<_T: HostY + ?Sized + Send> HostY for &mut _T { - async fn drop( - &mut self, - rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()> { - HostY::drop(*self, rep).await - } + ) -> impl ::core::future::Future> + Send + where + Self: Sized; } #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] + pub trait HostY: Send {} + impl<_T: HostY + ?Sized + Send> HostY for &mut _T {} + #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] + pub trait HostConcurrent: wasmtime::component::HasData + Send + HostYConcurrent {} + #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] pub trait Host: Send + HostY {} impl<_T: Host + ?Sized + Send> Host for &mut _T {} pub fn add_to_linker( @@ -242,18 +241,19 @@ pub mod foo { host_getter: fn(&mut T) -> D::Data<'_>, ) -> wasmtime::Result<()> where - D: wasmtime::component::HasData, + D: HostConcurrent, for<'a> D::Data<'a>: Host, T: 'static + Send, { let mut inst = linker.instance("foo:foo/transitive-import")?; - inst.resource_async( + inst.resource_concurrent( "y", wasmtime::component::ResourceType::host::(), - move |mut store, rep| { - wasmtime::component::__internal::Box::new(async move { - HostY::drop( - &mut host_getter(store.data_mut()), + move |caller: &wasmtime::component::Accessor, rep| { + wasmtime::component::__internal::Box::pin(async move { + let accessor = &caller.with_data(host_getter); + HostYConcurrent::drop( + accessor, wasmtime::component::Resource::new_own(rep), ) .await diff --git a/crates/component-macro/tests/expanded/resources-import_concurrent.rs b/crates/component-macro/tests/expanded/resources-import_concurrent.rs index 89196d9280..c791182150 100644 --- a/crates/component-macro/tests/expanded/resources-import_concurrent.rs +++ b/crates/component-macro/tests/expanded/resources-import_concurrent.rs @@ -19,22 +19,16 @@ pub trait HostWorldResourceConcurrent: wasmtime::component::HasData + Send { ) -> impl ::core::future::Future + Send where Self: Sized; -} -#[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] -pub trait HostWorldResource: Send { - async fn drop( - &mut self, - rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()>; -} -impl<_T: HostWorldResource + ?Sized + Send> HostWorldResource for &mut _T { - async fn drop( - &mut self, + fn drop( + accessor: &wasmtime::component::Accessor, rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()> { - HostWorldResource::drop(*self, rep).await - } + ) -> impl ::core::future::Future> + Send + where + Self: Sized; } +#[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] +pub trait HostWorldResource: Send {} +impl<_T: HostWorldResource + ?Sized + Send> HostWorldResource for &mut _T {} /// Auto-generated bindings for a pre-instantiated version of a /// component which implements the world `the-world`. /// @@ -254,13 +248,14 @@ const _: () = { { let mut linker = linker.root(); linker - .resource_async( + .resource_concurrent( "world-resource", wasmtime::component::ResourceType::host::(), - move |mut store, rep| { - wasmtime::component::__internal::Box::new(async move { - HostWorldResource::drop( - &mut host_getter(store.data_mut()), + move |caller: &wasmtime::component::Accessor, rep| { + wasmtime::component::__internal::Box::pin(async move { + let accessor = &caller.with_data(host_getter); + HostWorldResourceConcurrent::drop( + accessor, wasmtime::component::Resource::new_own(rep), ) .await @@ -272,7 +267,7 @@ const _: () = { "some-world-func", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::some_world_func( accessor, ) @@ -286,7 +281,7 @@ const _: () = { "[constructor]world-resource", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::new(accessor) .await; Ok((r,)) @@ -301,7 +296,7 @@ const _: () = { (arg0,): (wasmtime::component::Resource,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::foo( accessor, arg0, @@ -316,7 +311,7 @@ const _: () = { "[static]world-resource.static-foo", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::static_foo( accessor, ) @@ -333,8 +328,10 @@ const _: () = { ) -> wasmtime::Result<()> where D: foo::foo::resources::HostConcurrent - + foo::foo::long_use_chain4::HostConcurrent + TheWorldImportsConcurrent - + Send, + + foo::foo::long_use_chain1::HostConcurrent + + foo::foo::long_use_chain4::HostConcurrent + + foo::foo::transitive_interface_with_resource::HostConcurrent + + TheWorldImportsConcurrent + Send, for<'a> D::Data< 'a, >: foo::foo::resources::Host + foo::foo::long_use_chain1::Host @@ -407,22 +404,16 @@ pub mod foo { ) -> impl ::core::future::Future + Send where Self: Sized; - } - #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] - pub trait HostBar: Send { - async fn drop( - &mut self, - rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()>; - } - impl<_T: HostBar + ?Sized + Send> HostBar for &mut _T { - async fn drop( - &mut self, + fn drop( + accessor: &wasmtime::component::Accessor, rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()> { - HostBar::drop(*self, rep).await - } + ) -> impl ::core::future::Future> + Send + where + Self: Sized; } + #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] + pub trait HostBar: Send {} + impl<_T: HostBar + ?Sized + Send> HostBar for &mut _T {} #[derive(wasmtime::component::ComponentType)] #[derive(wasmtime::component::Lift)] #[derive(wasmtime::component::Lower)] @@ -618,13 +609,14 @@ pub mod foo { T: 'static + Send, { let mut inst = linker.instance("foo:foo/resources")?; - inst.resource_async( + inst.resource_concurrent( "bar", wasmtime::component::ResourceType::host::(), - move |mut store, rep| { - wasmtime::component::__internal::Box::new(async move { - HostBar::drop( - &mut host_getter(store.data_mut()), + move |caller: &wasmtime::component::Accessor, rep| { + wasmtime::component::__internal::Box::pin(async move { + let accessor = &caller.with_data(host_getter); + HostBarConcurrent::drop( + accessor, wasmtime::component::Resource::new_own(rep), ) .await @@ -635,7 +627,7 @@ pub mod foo { "[constructor]bar", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::new(accessor).await; Ok((r,)) }) @@ -645,7 +637,7 @@ pub mod foo { "[static]bar.static-a", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::static_a(accessor).await; Ok((r,)) }) @@ -658,7 +650,7 @@ pub mod foo { (arg0,): (wasmtime::component::Resource,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::method_a(accessor, arg0) .await; Ok((r,)) @@ -672,7 +664,7 @@ pub mod foo { (arg0,): (wasmtime::component::Resource,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::bar_own_arg(accessor, arg0) .await; Ok(r) @@ -686,7 +678,7 @@ pub mod foo { (arg0,): (wasmtime::component::Resource,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::bar_borrow_arg(accessor, arg0) .await; Ok(r) @@ -697,7 +689,7 @@ pub mod foo { "bar-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::bar_result(accessor).await; Ok((r,)) }) @@ -710,7 +702,7 @@ pub mod foo { (arg0,): ((wasmtime::component::Resource, u32),)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::tuple_own_arg(accessor, arg0) .await; Ok(r) @@ -724,7 +716,7 @@ pub mod foo { (arg0,): ((wasmtime::component::Resource, u32),)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::tuple_borrow_arg( accessor, arg0, @@ -738,7 +730,7 @@ pub mod foo { "tuple-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::tuple_result(accessor).await; Ok((r,)) }) @@ -751,7 +743,7 @@ pub mod foo { (arg0,): (Option>,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::option_own_arg(accessor, arg0) .await; Ok(r) @@ -765,7 +757,7 @@ pub mod foo { (arg0,): (Option>,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::option_borrow_arg( accessor, arg0, @@ -779,7 +771,7 @@ pub mod foo { "option-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::option_result(accessor).await; Ok((r,)) }) @@ -792,7 +784,7 @@ pub mod foo { (arg0,): (Result, ()>,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::result_own_arg(accessor, arg0) .await; Ok(r) @@ -806,7 +798,7 @@ pub mod foo { (arg0,): (Result, ()>,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::result_borrow_arg( accessor, arg0, @@ -820,7 +812,7 @@ pub mod foo { "result-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::result_result(accessor).await; Ok((r,)) }) @@ -839,7 +831,7 @@ pub mod foo { )| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_own_arg(accessor, arg0) .await; Ok(r) @@ -859,7 +851,7 @@ pub mod foo { )| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_borrow_arg( accessor, arg0, @@ -873,7 +865,7 @@ pub mod foo { "list-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::list_result(accessor).await; Ok((r,)) }) @@ -886,7 +878,7 @@ pub mod foo { (arg0,): (NestedOwn,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::record_own_arg(accessor, arg0) .await; Ok(r) @@ -900,7 +892,7 @@ pub mod foo { (arg0,): (NestedBorrow,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::record_borrow_arg( accessor, arg0, @@ -914,7 +906,7 @@ pub mod foo { "record-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::record_result(accessor).await; Ok((r,)) }) @@ -927,7 +919,7 @@ pub mod foo { (arg0,): (SomeHandle,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::func_with_handle_typedef( accessor, arg0, @@ -946,21 +938,20 @@ pub mod foo { use wasmtime::component::__internal::{anyhow, Box}; pub enum A {} #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] - pub trait HostA: Send { - async fn drop( - &mut self, - rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()>; - } - impl<_T: HostA + ?Sized + Send> HostA for &mut _T { - async fn drop( - &mut self, + pub trait HostAConcurrent: wasmtime::component::HasData + Send { + fn drop( + accessor: &wasmtime::component::Accessor, rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()> { - HostA::drop(*self, rep).await - } + ) -> impl ::core::future::Future> + Send + where + Self: Sized; } #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] + pub trait HostA: Send {} + impl<_T: HostA + ?Sized + Send> HostA for &mut _T {} + #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] + pub trait HostConcurrent: wasmtime::component::HasData + Send + HostAConcurrent {} + #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] pub trait Host: Send + HostA {} impl<_T: Host + ?Sized + Send> Host for &mut _T {} pub fn add_to_linker( @@ -968,18 +959,19 @@ pub mod foo { host_getter: fn(&mut T) -> D::Data<'_>, ) -> wasmtime::Result<()> where - D: wasmtime::component::HasData, + D: HostConcurrent, for<'a> D::Data<'a>: Host, T: 'static + Send, { let mut inst = linker.instance("foo:foo/long-use-chain1")?; - inst.resource_async( + inst.resource_concurrent( "a", wasmtime::component::ResourceType::host::(), - move |mut store, rep| { - wasmtime::component::__internal::Box::new(async move { - HostA::drop( - &mut host_getter(store.data_mut()), + move |caller: &wasmtime::component::Accessor, rep| { + wasmtime::component::__internal::Box::pin(async move { + let accessor = &caller.with_data(host_getter); + HostAConcurrent::drop( + accessor, wasmtime::component::Resource::new_own(rep), ) .await @@ -1063,7 +1055,7 @@ pub mod foo { "foo", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::foo(accessor).await; Ok((r,)) }) @@ -1078,21 +1070,20 @@ pub mod foo { use wasmtime::component::__internal::{anyhow, Box}; pub enum Foo {} #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] - pub trait HostFoo: Send { - async fn drop( - &mut self, - rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()>; - } - impl<_T: HostFoo + ?Sized + Send> HostFoo for &mut _T { - async fn drop( - &mut self, + pub trait HostFooConcurrent: wasmtime::component::HasData + Send { + fn drop( + accessor: &wasmtime::component::Accessor, rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()> { - HostFoo::drop(*self, rep).await - } + ) -> impl ::core::future::Future> + Send + where + Self: Sized; } #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] + pub trait HostFoo: Send {} + impl<_T: HostFoo + ?Sized + Send> HostFoo for &mut _T {} + #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] + pub trait HostConcurrent: wasmtime::component::HasData + Send + HostFooConcurrent {} + #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] pub trait Host: Send + HostFoo {} impl<_T: Host + ?Sized + Send> Host for &mut _T {} pub fn add_to_linker( @@ -1100,19 +1091,20 @@ pub mod foo { host_getter: fn(&mut T) -> D::Data<'_>, ) -> wasmtime::Result<()> where - D: wasmtime::component::HasData, + D: HostConcurrent, for<'a> D::Data<'a>: Host, T: 'static + Send, { let mut inst = linker .instance("foo:foo/transitive-interface-with-resource")?; - inst.resource_async( + inst.resource_concurrent( "foo", wasmtime::component::ResourceType::host::(), - move |mut store, rep| { - wasmtime::component::__internal::Box::new(async move { - HostFoo::drop( - &mut host_getter(store.data_mut()), + move |caller: &wasmtime::component::Accessor, rep| { + wasmtime::component::__internal::Box::pin(async move { + let accessor = &caller.with_data(host_getter); + HostFooConcurrent::drop( + accessor, wasmtime::component::Resource::new_own(rep), ) .await diff --git a/crates/component-macro/tests/expanded/share-types_concurrent.rs b/crates/component-macro/tests/expanded/share-types_concurrent.rs index 1e11aea96f..81d3efbf81 100644 --- a/crates/component-macro/tests/expanded/share-types_concurrent.rs +++ b/crates/component-macro/tests/expanded/share-types_concurrent.rs @@ -270,7 +270,7 @@ pub mod http_fetch { "fetch-request", move |caller: &wasmtime::component::Accessor, (arg0,): (Request,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::fetch_request(accessor, arg0).await; Ok((r,)) }) diff --git a/crates/component-macro/tests/expanded/simple-functions_concurrent.rs b/crates/component-macro/tests/expanded/simple-functions_concurrent.rs index 2e3148c805..c71d698724 100644 --- a/crates/component-macro/tests/expanded/simple-functions_concurrent.rs +++ b/crates/component-macro/tests/expanded/simple-functions_concurrent.rs @@ -231,7 +231,7 @@ pub mod foo { "f1", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::f1(accessor).await; Ok(r) }) @@ -241,7 +241,7 @@ pub mod foo { "f2", move |caller: &wasmtime::component::Accessor, (arg0,): (u32,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::f2(accessor, arg0).await; Ok(r) }) @@ -254,7 +254,7 @@ pub mod foo { (arg0, arg1): (u32, u32)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::f3(accessor, arg0, arg1) .await; Ok(r) @@ -265,7 +265,7 @@ pub mod foo { "f4", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::f4(accessor).await; Ok((r,)) }) @@ -275,7 +275,7 @@ pub mod foo { "f5", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::f5(accessor).await; Ok((r,)) }) @@ -288,7 +288,7 @@ pub mod foo { (arg0, arg1, arg2): (u32, u32, u32)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::f6(accessor, arg0, arg1, arg2) .await; Ok((r,)) diff --git a/crates/component-macro/tests/expanded/simple-lists_concurrent.rs b/crates/component-macro/tests/expanded/simple-lists_concurrent.rs index af56c7ccc4..68c211b852 100644 --- a/crates/component-macro/tests/expanded/simple-lists_concurrent.rs +++ b/crates/component-macro/tests/expanded/simple-lists_concurrent.rs @@ -235,7 +235,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::Vec,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::simple_list1(accessor, arg0) .await; Ok(r) @@ -246,7 +246,7 @@ pub mod foo { "simple-list2", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::simple_list2(accessor).await; Ok((r,)) }) @@ -265,7 +265,7 @@ pub mod foo { )| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::simple_list3( accessor, arg0, @@ -289,7 +289,7 @@ pub mod foo { )| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::simple_list4(accessor, arg0) .await; Ok((r,)) diff --git a/crates/component-macro/tests/expanded/simple-wasi_concurrent.rs b/crates/component-macro/tests/expanded/simple-wasi_concurrent.rs index 6895fbf8b0..c1453128d0 100644 --- a/crates/component-macro/tests/expanded/simple-wasi_concurrent.rs +++ b/crates/component-macro/tests/expanded/simple-wasi_concurrent.rs @@ -260,7 +260,7 @@ pub mod foo { "create-directory-at", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::create_directory_at(accessor) .await; Ok((r,)) @@ -271,7 +271,7 @@ pub mod foo { "stat", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::stat(accessor).await; Ok((r,)) }) diff --git a/crates/component-macro/tests/expanded/small-anonymous_concurrent.rs b/crates/component-macro/tests/expanded/small-anonymous_concurrent.rs index ca76cfae75..df8bb972e5 100644 --- a/crates/component-macro/tests/expanded/small-anonymous_concurrent.rs +++ b/crates/component-macro/tests/expanded/small-anonymous_concurrent.rs @@ -248,7 +248,7 @@ pub mod foo { "option-test", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::option_test(accessor).await; Ok((r,)) }) diff --git a/crates/component-macro/tests/expanded/smoke_concurrent.rs b/crates/component-macro/tests/expanded/smoke_concurrent.rs index dae1c5bd0a..b56ab99c01 100644 --- a/crates/component-macro/tests/expanded/smoke_concurrent.rs +++ b/crates/component-macro/tests/expanded/smoke_concurrent.rs @@ -187,7 +187,7 @@ pub mod imports { "y", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::y(accessor).await; Ok(r) }) diff --git a/crates/component-macro/tests/expanded/strings_concurrent.rs b/crates/component-macro/tests/expanded/strings_concurrent.rs index b6422dceeb..62b5df7f43 100644 --- a/crates/component-macro/tests/expanded/strings_concurrent.rs +++ b/crates/component-macro/tests/expanded/strings_concurrent.rs @@ -220,7 +220,7 @@ pub mod foo { (arg0,): (wasmtime::component::__internal::String,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a(accessor, arg0).await; Ok(r) }) @@ -230,7 +230,7 @@ pub mod foo { "b", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::b(accessor).await; Ok((r,)) }) @@ -249,7 +249,7 @@ pub mod foo { )| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::c(accessor, arg0, arg1).await; Ok((r,)) }) diff --git a/crates/component-macro/tests/expanded/unstable-features_concurrent.rs b/crates/component-macro/tests/expanded/unstable-features_concurrent.rs index b607006c4a..f9f1f00a58 100644 --- a/crates/component-macro/tests/expanded/unstable-features_concurrent.rs +++ b/crates/component-macro/tests/expanded/unstable-features_concurrent.rs @@ -87,22 +87,16 @@ pub trait HostBazConcurrent: wasmtime::component::HasData + Send { ) -> impl ::core::future::Future + Send where Self: Sized; -} -#[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] -pub trait HostBaz: Send { - async fn drop( - &mut self, - rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()>; -} -impl<_T: HostBaz + ?Sized + Send> HostBaz for &mut _T { - async fn drop( - &mut self, + fn drop( + accessor: &wasmtime::component::Accessor, rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()> { - HostBaz::drop(*self, rep).await - } + ) -> impl ::core::future::Future> + Send + where + Self: Sized; } +#[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] +pub trait HostBaz: Send {} +impl<_T: HostBaz + ?Sized + Send> HostBaz for &mut _T {} /// Auto-generated bindings for a pre-instantiated version of a /// component which implements the world `the-world`. /// @@ -274,13 +268,14 @@ const _: () = { if options.experimental_world { if options.experimental_world_resource { linker - .resource_async( + .resource_concurrent( "baz", wasmtime::component::ResourceType::host::(), - move |mut store, rep| { - wasmtime::component::__internal::Box::new(async move { - HostBaz::drop( - &mut host_getter(store.data_mut()), + move |caller: &wasmtime::component::Accessor, rep| { + wasmtime::component::__internal::Box::pin(async move { + let accessor = &caller.with_data(host_getter); + HostBazConcurrent::drop( + accessor, wasmtime::component::Resource::new_own(rep), ) .await @@ -294,9 +289,7 @@ const _: () = { "foo", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { - caller.with_data(host_getter) - }; + let accessor = &caller.with_data(host_getter); let r = ::foo(accessor) .await; Ok(r) @@ -313,9 +306,7 @@ const _: () = { (arg0,): (wasmtime::component::Resource,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { - caller.with_data(host_getter) - }; + let accessor = &caller.with_data(host_getter); let r = ::foo(accessor, arg0).await; Ok(r) }) @@ -403,23 +394,17 @@ pub mod foo { ) -> impl ::core::future::Future + Send where Self: Sized; - } - #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] - pub trait HostBar: Send { - async fn drop( - &mut self, - rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()>; - } - impl<_T: HostBar + ?Sized + Send> HostBar for &mut _T { - async fn drop( - &mut self, + fn drop( + accessor: &wasmtime::component::Accessor, rep: wasmtime::component::Resource, - ) -> wasmtime::Result<()> { - HostBar::drop(*self, rep).await - } + ) -> impl ::core::future::Future> + Send + where + Self: Sized; } #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] + pub trait HostBar: Send {} + impl<_T: HostBar + ?Sized + Send> HostBar for &mut _T {} + #[wasmtime::component::__internal::trait_variant_make(::core::marker::Send)] pub trait HostConcurrent: wasmtime::component::HasData + Send + HostBarConcurrent { fn foo( accessor: &wasmtime::component::Accessor, @@ -443,13 +428,14 @@ pub mod foo { if options.experimental_interface { let mut inst = linker.instance("foo:foo/the-interface")?; if options.experimental_interface_resource { - inst.resource_async( + inst.resource_concurrent( "bar", wasmtime::component::ResourceType::host::(), - move |mut store, rep| { - wasmtime::component::__internal::Box::new(async move { - HostBar::drop( - &mut host_getter(store.data_mut()), + move |caller: &wasmtime::component::Accessor, rep| { + wasmtime::component::__internal::Box::pin(async move { + let accessor = &caller.with_data(host_getter); + HostBarConcurrent::drop( + accessor, wasmtime::component::Resource::new_own(rep), ) .await @@ -462,9 +448,7 @@ pub mod foo { "foo", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { - caller.with_data(host_getter) - }; + let accessor = &caller.with_data(host_getter); let r = ::foo(accessor).await; Ok(r) }) @@ -479,9 +463,7 @@ pub mod foo { (arg0,): (wasmtime::component::Resource,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { - caller.with_data(host_getter) - }; + let accessor = &caller.with_data(host_getter); let r = ::foo(accessor, arg0).await; Ok(r) }) diff --git a/crates/component-macro/tests/expanded/unversioned-foo_concurrent.rs b/crates/component-macro/tests/expanded/unversioned-foo_concurrent.rs index 84b5ace884..92876736d4 100644 --- a/crates/component-macro/tests/expanded/unversioned-foo_concurrent.rs +++ b/crates/component-macro/tests/expanded/unversioned-foo_concurrent.rs @@ -217,7 +217,7 @@ pub mod foo { "g", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::g(accessor).await; Ok((r,)) }) diff --git a/crates/component-macro/tests/expanded/use-paths_concurrent.rs b/crates/component-macro/tests/expanded/use-paths_concurrent.rs index e171a1332e..867cfcc6ec 100644 --- a/crates/component-macro/tests/expanded/use-paths_concurrent.rs +++ b/crates/component-macro/tests/expanded/use-paths_concurrent.rs @@ -211,7 +211,7 @@ pub mod foo { "a", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a(accessor).await; Ok((r,)) }) @@ -254,7 +254,7 @@ pub mod foo { "a", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a(accessor).await; Ok((r,)) }) @@ -297,7 +297,7 @@ pub mod foo { "a", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::a(accessor).await; Ok((r,)) }) @@ -342,7 +342,7 @@ pub mod d { "b", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::b(accessor).await; Ok((r,)) }) diff --git a/crates/component-macro/tests/expanded/variants_concurrent.rs b/crates/component-macro/tests/expanded/variants_concurrent.rs index cedaa4db94..8c1f3bfe92 100644 --- a/crates/component-macro/tests/expanded/variants_concurrent.rs +++ b/crates/component-macro/tests/expanded/variants_concurrent.rs @@ -619,7 +619,7 @@ pub mod foo { "e1-arg", move |caller: &wasmtime::component::Accessor, (arg0,): (E1,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::e1_arg(accessor, arg0).await; Ok(r) }) @@ -629,7 +629,7 @@ pub mod foo { "e1-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::e1_result(accessor).await; Ok((r,)) }) @@ -639,7 +639,7 @@ pub mod foo { "v1-arg", move |caller: &wasmtime::component::Accessor, (arg0,): (V1,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::v1_arg(accessor, arg0).await; Ok(r) }) @@ -649,7 +649,7 @@ pub mod foo { "v1-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::v1_result(accessor).await; Ok((r,)) }) @@ -659,7 +659,7 @@ pub mod foo { "bool-arg", move |caller: &wasmtime::component::Accessor, (arg0,): (bool,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::bool_arg(accessor, arg0) .await; Ok(r) @@ -670,7 +670,7 @@ pub mod foo { "bool-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::bool_result(accessor).await; Ok((r,)) }) @@ -697,7 +697,7 @@ pub mod foo { )| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::option_arg( accessor, arg0, @@ -716,7 +716,7 @@ pub mod foo { "option-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::option_result(accessor).await; Ok((r,)) }) @@ -736,7 +736,7 @@ pub mod foo { ): (Casts1, Casts2, Casts3, Casts4, Casts5, Casts6)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::casts( accessor, arg0, @@ -775,7 +775,7 @@ pub mod foo { )| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::result_arg( accessor, arg0, @@ -794,7 +794,7 @@ pub mod foo { "result-result", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::result_result(accessor).await; Ok((r,)) }) @@ -804,7 +804,7 @@ pub mod foo { "return-result-sugar", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::return_result_sugar(accessor) .await; Ok((r,)) @@ -815,7 +815,7 @@ pub mod foo { "return-result-sugar2", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::return_result_sugar2(accessor) .await; Ok((r,)) @@ -826,7 +826,7 @@ pub mod foo { "return-result-sugar3", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::return_result_sugar3(accessor) .await; Ok((r,)) @@ -837,7 +837,7 @@ pub mod foo { "return-result-sugar4", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::return_result_sugar4(accessor) .await; Ok((r,)) @@ -848,7 +848,7 @@ pub mod foo { "return-option-sugar", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::return_option_sugar(accessor) .await; Ok((r,)) @@ -859,7 +859,7 @@ pub mod foo { "return-option-sugar2", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::return_option_sugar2(accessor) .await; Ok((r,)) @@ -870,7 +870,7 @@ pub mod foo { "result-simple", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::result_simple(accessor).await; Ok((r,)) }) @@ -883,7 +883,7 @@ pub mod foo { (arg0,): (IsClone,)| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::is_clone_arg(accessor, arg0) .await; Ok(r) @@ -894,7 +894,7 @@ pub mod foo { "is-clone-return", move |caller: &wasmtime::component::Accessor, (): ()| { wasmtime::component::__internal::Box::pin(async move { - let accessor = &mut unsafe { caller.with_data(host_getter) }; + let accessor = &caller.with_data(host_getter); let r = ::is_clone_return(accessor) .await; Ok((r,)) diff --git a/crates/misc/component-async-tests/src/resource_stream.rs b/crates/misc/component-async-tests/src/resource_stream.rs index 63ca916c42..84633db691 100644 --- a/crates/misc/component-async-tests/src/resource_stream.rs +++ b/crates/misc/component-async-tests/src/resource_stream.rs @@ -1,5 +1,7 @@ use anyhow::Result; -use wasmtime::component::{Accessor, AccessorTask, HostStream, Resource, StreamWriter}; +use wasmtime::component::{ + Accessor, AccessorTask, Resource, StreamReader, StreamWriter, WithAccessor, +}; use wasmtime_wasi::p2::IoView; use super::Ctx; @@ -14,7 +16,7 @@ pub mod bindings { async: true, with: { "local:local/resource-stream/x": super::ResourceStreamX, - } + }, }); } @@ -27,29 +29,31 @@ impl bindings::local::local::resource_stream::HostXConcurrent for Ctx { Ok(()) }) } -} -impl bindings::local::local::resource_stream::HostX for Ctx { - async fn drop(&mut self, x: Resource) -> Result<()> { - IoView::table(self).delete(x)?; - Ok(()) + async fn drop(accessor: &Accessor, x: Resource) -> Result<()> { + accessor.with(move |mut view| { + view.get().table().delete(x)?; + Ok(()) + }) } } +impl bindings::local::local::resource_stream::HostX for Ctx {} + impl bindings::local::local::resource_stream::HostConcurrent for Ctx { async fn foo( accessor: &Accessor, count: u32, - ) -> wasmtime::Result>> { + ) -> wasmtime::Result>> { struct Task { - tx: StreamWriter>>, + tx: StreamWriter>, count: u32, } impl AccessorTask> for Task { async fn run(self, accessor: &Accessor) -> Result<()> { - let mut tx = self.tx; + let mut tx = WithAccessor::new(accessor, self.tx); for _ in 0..self.count { let item = accessor.with(|mut view| view.get().table().push(ResourceStreamX))?; @@ -61,10 +65,10 @@ impl bindings::local::local::resource_stream::HostConcurrent for Ctx { let (tx, rx) = accessor.with(|mut view| { let instance = view.instance(); - instance.stream::<_, _, Option<_>>(&mut view) + instance.stream(&mut view) })?; accessor.spawn(Task { tx, count }); - Ok(rx.into()) + Ok(rx) } } diff --git a/crates/misc/component-async-tests/tests/scenario/streams.rs b/crates/misc/component-async-tests/tests/scenario/streams.rs index e77daf683a..f19c4de600 100644 --- a/crates/misc/component-async-tests/tests/scenario/streams.rs +++ b/crates/misc/component-async-tests/tests/scenario/streams.rs @@ -14,13 +14,15 @@ use { }, wasmtime::{ Engine, Store, - component::{Linker, ResourceTable, StreamReader, StreamWriter, VecBuffer}, + component::{Linker, ResourceTable, StreamReader, StreamWriter, VecBuffer, WithAccessor}, }, wasmtime_wasi::p2::WasiCtxBuilder, }; #[tokio::test] pub async fn async_watch_streams() -> Result<()> { + use wasmtime::component::{DropWithStore, DropWithStoreAndValue}; + let engine = Engine::new(&config())?; let mut store = Store::new( @@ -46,84 +48,90 @@ pub async fn async_watch_streams() -> Result<()> { let instance = linker.instantiate_async(&mut store, &component).await?; // Test watching and then dropping the read end of a stream. - let (mut tx, rx) = instance.stream::, Option<_>>(&mut store)?; + let (mut tx, rx) = instance.stream::(&mut store)?; instance .run_concurrent(&mut store, async |store| { - futures::join!(tx.watch_reader(store), async { - drop(rx); - }); + futures::join!(tx.watch_reader(store), async { rx.drop_with(store) }).1 }) - .await?; + .await??; // Test dropping and then watching the read end of a stream. - let (mut tx, rx) = instance.stream::, Option<_>>(&mut store)?; - drop(rx); + let (mut tx, rx) = instance.stream::(&mut store)?; instance - .run_concurrent(&mut store, async |store| tx.watch_reader(store).await) - .await?; + .run_concurrent(&mut store, async |store| { + rx.drop_with(store)?; + tx.watch_reader(store).await; + anyhow::Ok(()) + }) + .await??; // Test watching and then dropping the write end of a stream. - let (tx, mut rx) = instance.stream::, Option<_>>(&mut store)?; + let (tx, mut rx) = instance.stream::(&mut store)?; instance .run_concurrent(&mut store, async |store| { - futures::join!(rx.watch_writer(store), async { - drop(tx); - }); + futures::join!(rx.watch_writer(store), async { tx.drop_with(store) }).1 }) - .await?; + .await??; // Test dropping and then watching the write end of a stream. - let (tx, mut rx) = instance.stream::, Option<_>>(&mut store)?; - drop(tx); + let (tx, mut rx) = instance.stream::(&mut store)?; instance - .run_concurrent(&mut store, async |store| rx.watch_writer(store).await) - .await?; + .run_concurrent(&mut store, async |store| { + tx.drop_with(store)?; + rx.watch_writer(store).await; + anyhow::Ok(()) + }) + .await??; // Test watching and then dropping the read end of a future. - let (mut tx, rx) = instance.future::(|| 42, &mut store)?; + let (mut tx, rx) = instance.future::(&mut store)?; instance .run_concurrent(&mut store, async |store| { - futures::join!(tx.watch_reader(store), async { - drop(rx); - }); + futures::join!(tx.watch_reader(store), async { rx.drop_with(store) }).1 }) - .await?; + .await??; // Test dropping and then watching the read end of a future. - let (mut tx, rx) = instance.future::(|| 42, &mut store)?; - drop(rx); + let (mut tx, rx) = instance.future::(&mut store)?; instance - .run_concurrent(&mut store, async |store| tx.watch_reader(store).await) - .await?; + .run_concurrent(&mut store, async |store| { + rx.drop_with(store)?; + tx.watch_reader(store).await; + anyhow::Ok(()) + }) + .await??; // Test watching and then dropping the write end of a future. - let (tx, mut rx) = instance.future::(|| 42, &mut store)?; + let (tx, mut rx) = instance.future::(&mut store)?; instance .run_concurrent(&mut store, async |store| { - futures::join!(rx.watch_writer(store), async { - drop(tx); - }); + futures::join!(rx.watch_writer(store), async { tx.drop_with(store, 42) }).1 }) - .await?; + .await??; // Test dropping and then watching the write end of a future. - let (tx, mut rx) = instance.future::(|| 42, &mut store)?; - drop(tx); + let (tx, mut rx) = instance.future::(&mut store)?; instance - .run_concurrent(&mut store, async |store| rx.watch_writer(store).await) - .await?; + .run_concurrent(&mut store, async |store| { + tx.drop_with(store, 42)?; + rx.watch_writer(store).await; + anyhow::Ok(()) + }) + .await??; - enum Event { - Write(Option>>), - Read(Option>>, Option), + enum Event<'a> { + Write(Option, Ctx>>), + Read(Option, Ctx>>, Option), } // Test watching, then writing to, then dropping, then writing again to the // read end of a stream. + let (tx, rx) = instance.stream(&mut store)?; instance - .run_concurrent(&mut store, async |store| -> wasmtime::Result<_> { + .run_concurrent(&mut store, async move |store| -> wasmtime::Result<_> { + let mut tx = WithAccessor::new(store, tx); + let mut rx = WithAccessor::new(store, rx); let mut futures = FuturesUnordered::new(); - let (mut tx, mut rx) = store.with(|s| instance.stream(s))?; assert!( pin!(tx.watch_reader(store)) .poll(&mut Context::from_waker(&Waker::noop())) @@ -133,7 +141,7 @@ pub async fn async_watch_streams() -> Result<()> { async move { tx.write_all(store, Some(42)).await; let w = if tx.is_closed() { None } else { Some(tx) }; - Event::Write(w) + anyhow::Ok(Event::Write(w)) } .boxed(), ); @@ -141,13 +149,13 @@ pub async fn async_watch_streams() -> Result<()> { async move { let b = rx.read(store, None).await; let r = if rx.is_closed() { None } else { Some(rx) }; - Event::Read(r, b) + Ok(Event::Read(r, b)) } .boxed(), ); let mut rx = None; let mut tx = None; - while let Some(event) = futures.next().await { + while let Some(event) = futures.try_next().await? { match event { Event::Write(None) => unreachable!(), Event::Write(Some(new_tx)) => tx = Some(new_tx), @@ -206,10 +214,10 @@ pub async fn test_closed_streams(watch: bool) -> Result<()> { let instance = linker.instantiate_async(&mut store, &component).await?; - enum StreamEvent { - FirstWrite(Option>>), - FirstRead(Option>>, Vec), - SecondWrite(Option>>), + enum StreamEvent<'a> { + FirstWrite(Option, Ctx>>), + FirstRead(Option, Ctx>>, Vec), + SecondWrite(Option, Ctx>>), GuestCompleted, } @@ -225,83 +233,95 @@ pub async fn test_closed_streams(watch: bool) -> Result<()> { let value = 42_u8; // First, test stream host->host - instance - .run_concurrent(&mut store, async |store| -> wasmtime::Result<_> { - let (mut tx, mut rx) = store.with(|mut s| instance.stream(&mut s))?; + { + let (tx, rx) = instance.stream(&mut store)?; + let values = values.clone(); - let mut futures = FuturesUnordered::new(); - futures.push({ - let values = values.clone(); - async move { - tx.write_all(store, values.into()).await; - StreamEvent::FirstWrite(if tx.is_closed() { None } else { Some(tx) }) - } - .boxed() - }); - futures.push( - async move { - let b = rx.read(store, Vec::with_capacity(3)).await; - let r = if rx.is_closed() { None } else { Some(rx) }; - StreamEvent::FirstRead(r, b) - } - .boxed(), - ); + instance + .run_concurrent(&mut store, async move |store| -> wasmtime::Result<_> { + let mut tx = WithAccessor::new(store, tx); + let mut rx = WithAccessor::new(store, rx); - let mut count = 0; - while let Some(event) = futures.next().await { - count += 1; - match event { - StreamEvent::FirstWrite(Some(mut tx)) => { - if watch { - futures.push( - async move { - tx.watch_reader(store).await; - StreamEvent::SecondWrite(None) - } - .boxed(), - ); + let mut futures = FuturesUnordered::new(); + futures.push({ + let values = values.clone(); + async move { + tx.write_all(store, VecBuffer::from(values)).await; + anyhow::Ok(StreamEvent::FirstWrite(if tx.is_closed() { + None } else { - futures.push({ - let values = values.clone(); - async move { - tx.write_all(store, values.into()).await; - StreamEvent::SecondWrite(if tx.is_closed() { - None - } else { - Some(tx) - }) - } - .boxed() - }); - } + Some(tx) + })) } - StreamEvent::FirstWrite(None) => { - panic!("first write should have been accepted") - } - StreamEvent::FirstRead(Some(_), results) => { - assert_eq!(values, results); + .boxed() + }); + futures.push( + async move { + let b = rx.read(store, Vec::with_capacity(3)).await; + let r = if rx.is_closed() { None } else { Some(rx) }; + Ok(StreamEvent::FirstRead(r, b)) } - StreamEvent::FirstRead(None, _) => unreachable!(), - StreamEvent::SecondWrite(None) => {} - StreamEvent::SecondWrite(Some(_)) => { - panic!("second write should _not_ have been accepted") + .boxed(), + ); + + let mut count = 0; + while let Some(event) = futures.try_next().await? { + count += 1; + match event { + StreamEvent::FirstWrite(Some(mut tx)) => { + if watch { + futures.push( + async move { + tx.watch_reader(store).await; + Ok(StreamEvent::SecondWrite(None)) + } + .boxed(), + ); + } else { + futures.push({ + let values = values.clone(); + async move { + tx.write_all(store, VecBuffer::from(values)).await; + Ok(StreamEvent::SecondWrite(if tx.is_closed() { + None + } else { + Some(tx) + })) + } + .boxed() + }); + } + } + StreamEvent::FirstWrite(None) => { + panic!("first write should have been accepted") + } + StreamEvent::FirstRead(Some(_), results) => { + assert_eq!(values, results); + } + StreamEvent::FirstRead(None, _) => unreachable!(), + StreamEvent::SecondWrite(None) => {} + StreamEvent::SecondWrite(Some(_)) => { + panic!("second write should _not_ have been accepted") + } + StreamEvent::GuestCompleted => unreachable!(), } - StreamEvent::GuestCompleted => unreachable!(), } - } - assert_eq!(count, 3); - Ok(()) - }) - .await??; + assert_eq!(count, 3); + Ok(()) + }) + .await??; + } // Next, test futures host->host { - let (tx, rx) = instance.future(|| unreachable!(), &mut store)?; - let (mut tx_ignored, rx_ignored) = instance.future(|| 42u8, &mut store)?; + let (tx, rx) = instance.future(&mut store)?; + let (mut tx_ignored, rx_ignored) = instance.future(&mut store)?; instance - .run_concurrent(&mut store, async |store| { + .run_concurrent(&mut store, async move |store| { + let rx_ignored = WithAccessor::new(store, rx_ignored); + let mut futures = FuturesUnordered::new(); futures.push(tx.write(store, value).map(FutureEvent::Write).boxed()); futures.push(rx.read(store).map(FutureEvent::Read).boxed()); @@ -348,24 +368,28 @@ pub async fn test_closed_streams(watch: bool) -> Result<()> { // Next, test stream host->guest { - let (mut tx, rx) = instance.stream::<_, _, Vec<_>>(&mut store)?; + let (tx, rx) = instance.stream(&mut store)?; let closed_streams = closed_streams::bindings::ClosedStreams::new(&mut store, &instance)?; + let values = values.clone(); + instance .run_concurrent(&mut store, async move |accessor| { + let mut tx = WithAccessor::new(accessor, tx); + let mut futures = FuturesUnordered::new(); futures.push( closed_streams .local_local_closed() - .call_read_stream(accessor, rx.into(), values.clone()) + .call_read_stream(accessor, rx, values.clone()) .map(|v| v.map(|()| StreamEvent::GuestCompleted)) .boxed(), ); futures.push({ let values = values.clone(); async move { - tx.write_all(accessor, values.into()).await; + tx.write_all(accessor, VecBuffer::from(values)).await; let w = if tx.is_closed() { None } else { Some(tx) }; Ok(StreamEvent::FirstWrite(w)) } @@ -389,7 +413,7 @@ pub async fn test_closed_streams(watch: bool) -> Result<()> { futures.push({ let values = values.clone(); async move { - tx.write_all(accessor, values.into()).await; + tx.write_all(accessor, VecBuffer::from(values)).await; let w = if tx.is_closed() { None } else { Some(tx) }; Ok(StreamEvent::SecondWrite(w)) } @@ -418,8 +442,8 @@ pub async fn test_closed_streams(watch: bool) -> Result<()> { // Next, test futures host->guest { - let (tx, rx) = instance.future(|| unreachable!(), &mut store)?; - let (mut tx_ignored, rx_ignored) = instance.future(|| 0, &mut store)?; + let (tx, rx) = instance.future(&mut store)?; + let (mut tx_ignored, rx_ignored) = instance.future(&mut store)?; let closed_streams = closed_streams::bindings::ClosedStreams::new(&mut store, &instance)?; @@ -429,7 +453,7 @@ pub async fn test_closed_streams(watch: bool) -> Result<()> { futures.push( closed_streams .local_local_closed() - .call_read_future(accessor, rx.into(), value, rx_ignored.into()) + .call_read_future(accessor, rx, value, rx_ignored) .map(|v| v.map(|()| FutureEvent::GuestCompleted)) .boxed(), ); diff --git a/crates/misc/component-async-tests/tests/scenario/transmit.rs b/crates/misc/component-async-tests/tests/scenario/transmit.rs index de1757fb6a..18cdcc02db 100644 --- a/crates/misc/component-async-tests/tests/scenario/transmit.rs +++ b/crates/misc/component-async-tests/tests/scenario/transmit.rs @@ -12,8 +12,8 @@ use futures::{ stream::{FuturesUnordered, TryStreamExt}, }; use wasmtime::component::{ - Accessor, Component, HasSelf, HostFuture, HostStream, Instance, Linker, ResourceTable, - StreamReader, StreamWriter, Val, + Accessor, Component, FutureReader, HasSelf, Instance, Linker, ResourceTable, StreamReader, + StreamWriter, Val, WithAccessor, }; use wasmtime::{AsContextMut, Engine, Store}; use wasmtime_wasi::p2::WasiCtxBuilder; @@ -179,17 +179,21 @@ pub trait TransmitTest { ) -> impl Future> + Send + 'a; fn into_params( - control: HostStream, - caller_stream: HostStream, - caller_future1: HostFuture, - caller_future2: HostFuture, + control: StreamReader, + caller_stream: StreamReader, + caller_future1: FutureReader, + caller_future2: FutureReader, ) -> Self::Params; fn from_result( store: impl AsContextMut, instance: Instance, result: Self::Result, - ) -> Result<(HostStream, HostFuture, HostFuture)>; + ) -> Result<( + StreamReader, + FutureReader, + FutureReader, + )>; } struct StaticTransmitTest; @@ -197,12 +201,16 @@ struct StaticTransmitTest; impl TransmitTest for StaticTransmitTest { type Instance = transmit::bindings::TransmitCallee; type Params = ( - HostStream, - HostStream, - HostFuture, - HostFuture, + StreamReader, + StreamReader, + FutureReader, + FutureReader, + ); + type Result = ( + StreamReader, + FutureReader, + FutureReader, ); - type Result = (HostStream, HostFuture, HostFuture); async fn instantiate( mut store: impl AsContextMut, @@ -225,10 +233,10 @@ impl TransmitTest for StaticTransmitTest { } fn into_params( - control: HostStream, - caller_stream: HostStream, - caller_future1: HostFuture, - caller_future2: HostFuture, + control: StreamReader, + caller_stream: StreamReader, + caller_future1: FutureReader, + caller_future2: FutureReader, ) -> Self::Params { (control, caller_stream, caller_future1, caller_future2) } @@ -237,7 +245,11 @@ impl TransmitTest for StaticTransmitTest { _: impl AsContextMut, _: Instance, result: Self::Result, - ) -> Result<(HostStream, HostFuture, HostFuture)> { + ) -> Result<( + StreamReader, + FutureReader, + FutureReader, + )> { Ok(result) } } @@ -289,10 +301,10 @@ impl TransmitTest for DynamicTransmitTest { } fn into_params( - control: HostStream, - caller_stream: HostStream, - caller_future1: HostFuture, - caller_future2: HostFuture, + control: StreamReader, + caller_stream: StreamReader, + caller_future1: FutureReader, + caller_future2: FutureReader, ) -> Self::Params { vec![ control.into_val(), @@ -306,13 +318,17 @@ impl TransmitTest for DynamicTransmitTest { mut store: impl AsContextMut, instance: Instance, result: Self::Result, - ) -> Result<(HostStream, HostFuture, HostFuture)> { + ) -> Result<( + StreamReader, + FutureReader, + FutureReader, + )> { let Val::Tuple(fields) = result else { unreachable!() }; - let stream = HostStream::from_val(store.as_context_mut(), instance, &fields[0])?; - let future1 = HostFuture::from_val(store.as_context_mut(), instance, &fields[1])?; - let future2 = HostFuture::from_val(store.as_context_mut(), instance, &fields[2])?; + let stream = StreamReader::from_val(store.as_context_mut(), instance, &fields[0])?; + let future1 = FutureReader::from_val(store.as_context_mut(), instance, &fields[1])?; + let future2 = FutureReader::from_val(store.as_context_mut(), instance, &fields[2])?; Ok((stream, future1, future2)) } } @@ -347,29 +363,35 @@ async fn test_transmit_with(component: &str) -> Re let (test, instance) = Test::instantiate(&mut store, &component, &linker).await?; - enum Event { + enum Event<'a, Test: TransmitTest> { Result(Test::Result), - ControlWriteA(Option>>), - ControlWriteB(Option>>), - ControlWriteC(Option>>), + ControlWriteA(Option, Ctx>>), + ControlWriteB(Option, Ctx>>), + ControlWriteC(Option, Ctx>>), ControlWriteD, WriteA, WriteB(bool), - ReadC(Option>>, Option), + ReadC( + Option, Ctx>>, + Option, + ), ReadD(Option), - ReadNone(Option>>), + ReadNone(Option, Ctx>>), } - let (mut control_tx, control_rx) = instance.stream::<_, _, Option<_>>(&mut store)?; - let (mut caller_stream_tx, caller_stream_rx) = - instance.stream::<_, _, Option<_>>(&mut store)?; - let (caller_future1_tx, caller_future1_rx) = instance.future(|| unreachable!(), &mut store)?; - let (_caller_future2_tx, caller_future2_rx) = instance.future(|| unreachable!(), &mut store)?; + let (control_tx, control_rx) = instance.stream(&mut store)?; + let (caller_stream_tx, caller_stream_rx) = instance.stream(&mut store)?; + let (caller_future1_tx, caller_future1_rx) = instance.future(&mut store)?; + let (_caller_future2_tx, caller_future2_rx) = instance.future(&mut store)?; instance .run_concurrent(&mut store, async move |accessor| { + let mut control_tx = WithAccessor::new(accessor, control_tx); + let control_rx = WithAccessor::new(accessor, control_rx); + let mut caller_stream_tx = WithAccessor::new(accessor, caller_stream_tx); + let mut futures = FuturesUnordered::< - Pin>> + Send>>, + Pin>> + Send>>, >::new(); let mut caller_future1_tx = Some(caller_future1_tx); let mut callee_stream_rx = None; @@ -406,10 +428,10 @@ async fn test_transmit_with(component: &str) -> Re accessor, &test, Test::into_params( - control_rx.into(), - caller_stream_rx.into(), - caller_future1_rx.into(), - caller_future2_rx.into(), + control_rx.into_inner(), + caller_stream_rx, + caller_future1_rx, + caller_future2_rx, ), ) .map(|v| v.map(Event::Result)) @@ -419,12 +441,10 @@ async fn test_transmit_with(component: &str) -> Re while let Some(event) = futures.try_next().await? { match event { Event::Result(result) => { - accessor.with(|mut store| { - let results = Test::from_result(&mut store, instance, result)?; - callee_stream_rx = Some(results.0.into_reader(&mut store)); - callee_future1_rx = Some(results.1.into_reader(&mut store)); - anyhow::Ok(()) - })?; + let (stream_rx, future_rx, _) = accessor + .with(|mut store| Test::from_result(&mut store, instance, result))?; + callee_stream_rx = Some(WithAccessor::new(accessor, stream_rx)); + callee_future1_rx = Some(WithAccessor::new(accessor, future_rx)); } Event::ControlWriteA(tx) => { futures.push( @@ -491,6 +511,7 @@ async fn test_transmit_with(component: &str) -> Re callee_future1_rx .take() .unwrap() + .into_inner() .read(accessor) .map(Event::ReadD) .map(Ok) diff --git a/crates/wasi-http/src/p3/bindings.rs b/crates/wasi-http/src/p3/bindings.rs index ff477eba2c..90b3f93986 100644 --- a/crates/wasi-http/src/p3/bindings.rs +++ b/crates/wasi-http/src/p3/bindings.rs @@ -15,7 +15,9 @@ mod generated { "wasi:http/types@0.3.0-draft#[method]request.body", "wasi:http/types@0.3.0-draft#[method]response.body", "wasi:http/types@0.3.0-draft#[static]request.new", + "wasi:http/types@0.3.0-draft#[drop]request", "wasi:http/types@0.3.0-draft#[static]response.new", + "wasi:http/types@0.3.0-draft#[drop]response", ], }, with: { diff --git a/crates/wasi-http/src/p3/body.rs b/crates/wasi-http/src/p3/body.rs index 33b87a34b3..4eef210268 100644 --- a/crates/wasi-http/src/p3/body.rs +++ b/crates/wasi-http/src/p3/body.rs @@ -1,7 +1,7 @@ use crate::p3::ResourceView; use crate::p3::bindings::http::types::ErrorCode; use anyhow::Context as _; -use bytes::{Buf, Bytes, BytesMut}; +use bytes::{Buf, Bytes}; use core::mem; use core::pin::Pin; use core::task::{Context, Poll, ready}; @@ -12,7 +12,11 @@ use http_body_util::combinators::BoxBody; use pin_project_lite::pin_project; use tokio::sync::mpsc; use tokio::sync::oneshot; -use wasmtime::component::{Accessor, FutureReader, FutureWriter, HasData, Resource, StreamReader}; +use wasmtime::AsContextMut; +use wasmtime::component::{ + Accessor, DropWithStore, DropWithStoreAndValue, FutureReader, FutureWriter, HasData, Resource, + StreamReader, +}; use wasmtime_wasi::p3::WithChildren; pub(crate) fn empty_body() -> impl http_body::Body> { @@ -54,7 +58,7 @@ pub enum Body { /// Body constructed by the guest Guest { /// The body stream - contents: MaybeTombstone>, + contents: MaybeTombstone>, /// Future, on which guest will write result and optional trailers trailers: MaybeTombstone>, /// Buffered frame, if any @@ -113,6 +117,28 @@ impl Body { } } +impl DropWithStore for Body { + fn drop(self, mut store: impl AsContextMut) -> wasmtime::Result<()> { + if let Body::Guest { + contents, + trailers, + tx, + .. + } = self + { + let mut store = store.as_context_mut(); + if let MaybeTombstone::Some(contents) = contents { + contents.drop(&mut store)?; + } + if let MaybeTombstone::Some(trailers) = trailers { + trailers.drop(&mut store)?; + } + tx.drop(store, Ok(()))?; + } + anyhow::Ok(()) + } +} + /// Represents `Content-Length` limit and state #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct ContentLength { diff --git a/crates/wasi-http/src/p3/host/handler.rs b/crates/wasi-http/src/p3/host/handler.rs index 2c3dbf4d18..0e80a337c1 100644 --- a/crates/wasi-http/src/p3/host/handler.rs +++ b/crates/wasi-http/src/p3/host/handler.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use tokio::sync::mpsc; use tokio::sync::oneshot; use tracing::debug; -use wasmtime::component::{Accessor, Resource}; +use wasmtime::component::{Accessor, Resource, WithAccessor, WithAccessorAndValue}; use wasmtime_wasi::p3::{AbortOnDropHandle, ResourceView as _, SpawnExt}; impl handler::HostConcurrent for WasiHttp @@ -39,6 +39,17 @@ where .. } = store.with(|mut view| delete_request(view.get().table(), request))?; + let Some(body) = Arc::into_inner(body) else { + return Ok(Err(ErrorCode::InternalError(Some( + "body is borrowed".into(), + )))); + }; + let Ok(body) = body.into_inner() else { + bail!("lock poisoned"); + }; + + let body = WithAccessor::new(store, body); + let mut client = store.with(|mut view| view.get().http().client.clone()); let options = options @@ -79,15 +90,6 @@ where } }; - let Some(body) = Arc::into_inner(body) else { - return Ok(Err(ErrorCode::InternalError(Some( - "body is borrowed".into(), - )))); - }; - let Ok(body) = body.into_inner() else { - bail!("lock poisoned"); - }; - let mut request = http::Request::builder(); *request.headers_mut().unwrap() = headers; let request = match request.method(method).uri(uri).body(()) { @@ -95,7 +97,7 @@ where Err(err) => return Ok(Err(ErrorCode::InternalError(Some(err.to_string())))), }; let (request, ()) = request.into_parts(); - let response = match body { + let response = match body.into_inner() { Body::Guest { contents: MaybeTombstone::None, buffer: Some(BodyFrame::Trailers(Ok(None))) | None, @@ -221,12 +223,14 @@ where } } Body::Guest { - contents: MaybeTombstone::Some(mut contents), + contents: MaybeTombstone::Some(contents), trailers: MaybeTombstone::Some(trailers), buffer, tx, content_length, } => { + let contents = WithAccessor::new(store, contents); + let tx = WithAccessorAndValue::new(store, tx, Ok(())); let (trailers_tx, trailers_rx) = oneshot::channel(); let (body_tx, body_rx) = mpsc::channel(1); let task = AbortOnDropHandle(store.spawn_fn_box(|store| { @@ -247,9 +251,15 @@ where let request = http::Request::from_parts(request, body); let (response, io) = match client.send_request(request, options).await? { Ok(pair) => pair, - Err(err) => return Ok(Err(err)), + Err(err) => { + return Ok(Err(err)); + } }; + let contents = contents.into_inner(); + let tx = tx.into_inner(); store.spawn_fn_box(move |store| Box::pin(async move { + let mut contents = WithAccessor::new(store, contents); + let tx = WithAccessorAndValue::new(store, tx, Ok(())); let (io_res, body_res) = futures::join! { io, async { @@ -269,7 +279,7 @@ where // itself goes away due to cancellation elsewhere, so // swallow this error. let _ = body_res; - tx.write(store,io_res.map_err(Into::into)).await; + tx.into_inner().write(store,io_res.map_err(Into::into)).await; Ok(()) })); match response.await { diff --git a/crates/wasi-http/src/p3/host/mod.rs b/crates/wasi-http/src/p3/host/mod.rs index 9be58fdf16..eb94cceaba 100644 --- a/crates/wasi-http/src/p3/host/mod.rs +++ b/crates/wasi-http/src/p3/host/mod.rs @@ -115,12 +115,3 @@ fn get_response_mut<'a>( fn push_response(table: &mut ResourceTable, res: Response) -> wasmtime::Result> { table.push(res).context("failed to push response to table") } - -fn delete_response( - table: &mut ResourceTable, - res: Resource, -) -> wasmtime::Result { - table - .delete(res) - .context("failed to delete response from table") -} diff --git a/crates/wasi-http/src/p3/host/types.rs b/crates/wasi-http/src/p3/host/types.rs index a33a3d5387..e012a7b394 100644 --- a/crates/wasi-http/src/p3/host/types.rs +++ b/crates/wasi-http/src/p3/host/types.rs @@ -4,16 +4,16 @@ use crate::p3::bindings::http::types::{ RequestOptionsError, Scheme, StatusCode, Trailers, }; use crate::p3::host::{ - delete_fields, delete_response, get_fields, get_fields_inner, get_fields_inner_mut, - get_request, get_request_mut, get_response, get_response_mut, push_fields, push_fields_child, - push_request, push_response, + delete_fields, get_fields, get_fields_inner, get_fields_inner_mut, get_request, + get_request_mut, get_response, get_response_mut, push_fields, push_fields_child, push_request, + push_response, }; use crate::p3::{ Body, BodyContext, BodyFrame, ContentLength, DEFAULT_BUFFER_CAPACITY, MaybeTombstone, Request, RequestOptions, Response, WasiHttp, WasiHttpImpl, WasiHttpView, }; use anyhow::{Context as _, bail}; -use bytes::{Bytes, BytesMut}; +use bytes::BytesMut; use core::future::Future; use core::future::poll_fn; use core::mem; @@ -27,7 +27,8 @@ use http_body::Body as _; use std::io::Cursor; use std::sync::Arc; use wasmtime::component::{ - Accessor, AccessorTask, FutureWriter, HostFuture, HostStream, Resource, StreamWriter, + Accessor, AccessorTask, DropWithStore, FutureReader, FutureWriter, Resource, StreamReader, + StreamWriter, WithAccessor, WithAccessorAndValue, }; use wasmtime_wasi::ResourceTable; use wasmtime_wasi::p3::bindings::clocks::monotonic_clock::Duration; @@ -107,12 +108,12 @@ fn get_content_length(headers: &http::HeaderMap) -> wasmtime::Result Ok(Some(v)) } -type TrailerFuture = HostFuture>, ErrorCode>>; +type TrailerFuture = FutureReader>, ErrorCode>>; struct BodyTask { cx: BodyContext, body: Arc>, - contents_tx: StreamWriter>, + contents_tx: StreamWriter, trailers_tx: FutureWriter>, ErrorCode>>, } @@ -120,13 +121,15 @@ impl AccessorTask, wasmtime::Result<()>> for BodyTask where U: WasiHttpView + 'static, { - async fn run(mut self, store: &Accessor>) -> wasmtime::Result<()> { + async fn run(self, store: &Accessor>) -> wasmtime::Result<()> { let body = { let Ok(mut body) = self.body.lock() else { bail!("lock poisoned"); }; mem::replace(&mut *body, Body::Consumed) }; + let mut contents_tx = WithAccessor::new(store, self.contents_tx); + let mut trailers_tx = WithAccessorAndValue::new(store, self.trailers_tx, Ok(None)); match body { Body::Guest { contents: MaybeTombstone::None, @@ -135,13 +138,14 @@ where content_length: Some(ContentLength { limit, sent }), .. } if limit != sent => { - drop(self.contents_tx); + drop(contents_tx); join!( async { tx.write(store, Err(self.cx.as_body_size_error(sent))).await; }, async { - self.trailers_tx + trailers_tx + .into_inner() .write(store, Err(self.cx.as_body_size_error(sent))) .await; } @@ -155,9 +159,9 @@ where tx, content_length: None, } => { - drop(self.contents_tx); + drop(contents_tx); let res = { - let mut watch_reader = pin!(self.trailers_tx.watch_reader(store)); + let mut watch_reader = pin!(trailers_tx.watch_reader(store)); let mut trailers_rx = pin!(trailers_rx.read(store)); poll_fn(|cx| match watch_reader.as_mut().poll(cx) { Poll::Ready(()) => return Poll::Ready(None), @@ -180,8 +184,8 @@ where }; return Ok(()); }; - if !self - .trailers_tx + if !trailers_tx + .into_inner() .write(store, clone_trailer_result(&res)) .await { @@ -207,9 +211,9 @@ where tx, content_length: None, } => { - drop(self.contents_tx); - if !self - .trailers_tx + drop(contents_tx); + if !trailers_tx + .into_inner() .write(store, clone_trailer_result(&res)) .await { @@ -229,13 +233,14 @@ where Ok(()) } Body::Guest { - contents: MaybeTombstone::Some(mut contents_rx), + contents: MaybeTombstone::Some(contents_rx), trailers: MaybeTombstone::Some(trailers_rx), buffer, tx, mut content_length, } => { - let mut contents_tx = self.contents_tx; + let mut contents_rx = WithAccessor::new(store, contents_rx); + let trailers_rx = WithAccessor::new(store, trailers_rx); match buffer { Some(BodyFrame::Data(buffer)) => { let buffer = contents_tx.write_all(store, Cursor::new(buffer)).await; @@ -246,8 +251,8 @@ where let pos = buffer.position().try_into()?; let buffer = buffer.into_inner().split_off(pos); *body = Body::Guest { - contents: MaybeTombstone::Some(contents_rx), - trailers: MaybeTombstone::Some(trailers_rx), + contents: MaybeTombstone::Some(contents_rx.into_inner()), + trailers: MaybeTombstone::Some(trailers_rx.into_inner()), buffer: Some(BodyFrame::Data(buffer)), tx, content_length, @@ -279,7 +284,7 @@ where // reads in Wasmtime to fully support this to avoid // needing `Taken` at all. contents: MaybeTombstone::Tombstone, - trailers: MaybeTombstone::Some(trailers_rx), + trailers: MaybeTombstone::Some(trailers_rx.into_inner()), buffer: None, tx, content_length, @@ -299,7 +304,8 @@ where .await; }, async { - self.trailers_tx + trailers_tx + .into_inner() .write(store, Err(self.cx.as_body_size_error(sent))) .await; } @@ -329,7 +335,8 @@ where tx.write(store, Err(self.cx.as_body_size_error(n))).await; }, async { - self.trailers_tx + trailers_tx + .into_inner() .write(store, Err(self.cx.as_body_size_error(n))) .await; } @@ -347,8 +354,8 @@ where let pos = buffer.position().try_into()?; let buffer = buffer.into_inner().split_off(pos); *body = Body::Guest { - contents: MaybeTombstone::Some(contents_rx), - trailers: MaybeTombstone::Some(trailers_rx), + contents: MaybeTombstone::Some(contents_rx.into_inner()), + trailers: MaybeTombstone::Some(trailers_rx.into_inner()), buffer: Some(BodyFrame::Data(buffer)), tx, content_length, @@ -359,8 +366,8 @@ where } let res = { - let mut watch_reader = pin!(self.trailers_tx.watch_reader(store)); - let mut trailers_rx = pin!(trailers_rx.read(store)); + let mut watch_reader = pin!(trailers_tx.watch_reader(store)); + let mut trailers_rx = pin!(trailers_rx.into_inner().read(store)); poll_fn(|cx| match watch_reader.as_mut().poll(cx) { Poll::Ready(()) => return Poll::Ready(None), Poll::Pending => trailers_rx.as_mut().poll(cx).map(Some), @@ -382,8 +389,8 @@ where }; return Ok(()); }; - if !self - .trailers_tx + if !trailers_tx + .into_inner() .write(store, clone_trailer_result(&res)) .await { @@ -407,7 +414,6 @@ where stream: Some(mut stream), buffer, } => { - let mut contents_tx = self.contents_tx; match buffer { Some(BodyFrame::Data(buffer)) => { let buffer = contents_tx.write_all(store, Cursor::new(buffer)).await; @@ -450,7 +456,7 @@ where } Some(None) => { drop(contents_tx); - if !self.trailers_tx.write(store, Ok(None)).await { + if !trailers_tx.into_inner().write(store, Ok(None)).await { let Ok(mut body) = self.body.lock() else { bail!("lock poisoned"); }; @@ -484,8 +490,8 @@ where let trailers = store.with(|mut view| { push_fields(view.get().table(), WithChildren::new(trailers)) })?; - if !self - .trailers_tx + if !trailers_tx + .into_inner() .write(store, Ok(Some(Resource::new_own(trailers.rep())))) .await { @@ -501,8 +507,8 @@ where } Err(Err(..)) => { drop(contents_tx); - if !self - .trailers_tx + if !trailers_tx + .into_inner() .write(store, Err(ErrorCode::HttpProtocolError)) .await { @@ -522,7 +528,11 @@ where } Some(Some(Err(err))) => { drop(contents_tx); - if !self.trailers_tx.write(store, Err(err.clone())).await { + if !trailers_tx + .into_inner() + .write(store, Err(err.clone())) + .await + { let Ok(mut body) = self.body.lock() else { bail!("lock poisoned"); }; @@ -540,9 +550,9 @@ where stream: None, buffer: Some(BodyFrame::Trailers(res)), } => { - drop(self.contents_tx); - if !self - .trailers_tx + drop(contents_tx); + if !trailers_tx + .into_inner() .write(store, clone_trailer_result(&res)) .await { @@ -762,20 +772,19 @@ where async fn new( store: &Accessor, headers: Resource>, - contents: Option>, + contents: Option>, trailers: TrailerFuture, options: Option>>, - ) -> wasmtime::Result<(Resource, HostFuture>)> { + ) -> wasmtime::Result<(Resource, FutureReader>)> { store.with(|mut view| { let instance = view.instance(); let (res_tx, res_rx) = instance - .future(|| Ok(()), &mut view) + .future(&mut view) .context("failed to create future")?; let contents = match contents { - Some(contents) => MaybeTombstone::Some(contents.into_reader(&mut view)), + Some(contents) => MaybeTombstone::Some(contents), None => MaybeTombstone::None, }; - let trailers = trailers.into_reader(&mut view); let mut binding = view.get(); let table = binding.table(); let headers = delete_fields(table, headers)?; @@ -798,21 +807,21 @@ where table, Request::new(http::Method::GET, None, None, None, headers, body, options), )?; - Ok((req, res_rx.into())) + Ok((req, res_rx)) }) } async fn body( store: &Accessor, req: Resource, - ) -> wasmtime::Result, TrailerFuture), ()>> { + ) -> wasmtime::Result, TrailerFuture), ()>> { store.with(|mut view| { let instance = view.instance(); let (contents_tx, contents_rx) = instance - .stream::<_, _, Vec<_>>(&mut view) + .stream(&mut view) .context("failed to create stream")?; let (trailers_tx, trailers_rx) = instance - .future(|| Ok(None), &mut view) + .future(&mut view) .context("failed to create future")?; let mut binding = view.get(); let Request { body, .. } = get_request_mut(binding.table(), &req)?; @@ -837,7 +846,21 @@ where let mut binding = view.get(); let req = get_request_mut(binding.table(), &req)?; req.task = Some(AbortOnDropHandle(task)); - Ok(Ok((contents_rx.into(), trailers_rx.into()))) + Ok(Ok((contents_rx, trailers_rx))) + }) + } + + async fn drop( + store: &Accessor, + req: Resource, + ) -> wasmtime::Result<()> { + store.with(|mut store| { + let request = store + .get() + .table() + .delete(req) + .context("failed to delete request from table")?; + mem::replace(request.body.lock().unwrap().deref_mut(), Body::Consumed).drop(store) }) } } @@ -958,13 +981,6 @@ where let Request { headers, .. } = get_request(table, &req)?; push_fields_child(table, headers.child(), &req) } - - fn drop(&mut self, req: Resource) -> wasmtime::Result<()> { - self.table() - .delete(req) - .context("failed to delete request from table")?; - Ok(()) - } } impl HostRequestOptions for WasiHttpImpl @@ -1088,19 +1104,18 @@ where async fn new( store: &Accessor, headers: Resource>, - contents: Option>, + contents: Option>, trailers: TrailerFuture, - ) -> wasmtime::Result<(Resource, HostFuture>)> { + ) -> wasmtime::Result<(Resource, FutureReader>)> { store.with(|mut view| { let instance = view.instance(); let (res_tx, res_rx) = instance - .future(|| Ok(()), &mut view) + .future(&mut view) .context("failed to create future")?; let contents = match contents { - Some(contents) => MaybeTombstone::Some(contents.into_reader(&mut view)), + Some(contents) => MaybeTombstone::Some(contents), None => MaybeTombstone::None, }; - let trailers = trailers.into_reader(&mut view); let mut binding = view.get(); let table = binding.table(); let headers = delete_fields(table, headers)?; @@ -1114,21 +1129,21 @@ where content_length: content_length.map(ContentLength::new), }; let res = push_response(table, Response::new(http::StatusCode::OK, headers, body))?; - Ok((res, res_rx.into())) + Ok((res, res_rx)) }) } async fn body( store: &Accessor, res: Resource, - ) -> wasmtime::Result, TrailerFuture), ()>> { + ) -> wasmtime::Result, TrailerFuture), ()>> { store.with(|mut view| { let instance = view.instance(); let (contents_tx, contents_rx) = instance - .stream::<_, _, Vec<_>>(&mut view) + .stream(&mut view) .context("failed to create stream")?; let (trailers_tx, trailers_rx) = instance - .future(|| Ok(None), &mut view) + .future(&mut view) .context("failed to create future")?; let mut binding = view.get(); let Response { body, .. } = get_response_mut(binding.table(), &res)?; @@ -1153,7 +1168,21 @@ where let mut binding = view.get(); let res = get_response_mut(binding.table(), &res)?; res.body_task = Some(AbortOnDropHandle(task)); - Ok(Ok((contents_rx.into(), trailers_rx.into()))) + Ok(Ok((contents_rx, trailers_rx))) + }) + } + + async fn drop( + store: &Accessor, + req: Resource, + ) -> wasmtime::Result<()> { + store.with(|mut store| { + let request = store + .get() + .table() + .delete(req) + .context("failed to delete request from table")?; + mem::replace(request.body.lock().unwrap().deref_mut(), Body::Consumed).drop(store) }) } } @@ -1187,9 +1216,4 @@ where let Response { headers, .. } = get_response(table, &res)?; push_fields_child(table, headers.child(), &res) } - - fn drop(&mut self, res: Resource) -> wasmtime::Result<()> { - delete_response(self.table(), res)?; - Ok(()) - } } diff --git a/crates/wasi-http/src/p3/response.rs b/crates/wasi-http/src/p3/response.rs index 93933bf83f..b306ba7e87 100644 --- a/crates/wasi-http/src/p3/response.rs +++ b/crates/wasi-http/src/p3/response.rs @@ -10,7 +10,9 @@ use http::{HeaderMap, StatusCode}; use http_body_util::combinators::BoxBody; use http_body_util::{BodyExt, BodyStream, StreamBody}; use tokio::sync::{mpsc, oneshot}; -use wasmtime::component::{Accessor, FutureReader, FutureWriter, Resource, StreamReader}; +use wasmtime::component::{ + Accessor, FutureReader, FutureWriter, Resource, StreamReader, WithAccessor, +}; use wasmtime::{AsContextMut, StoreContextMut}; use wasmtime_wasi::p3::{AbortOnDropHandle, ResourceView, WithChildren}; @@ -236,7 +238,7 @@ impl Response { /// This is primarily used with its [`ResponseIo::run`] method to finish guest /// I/O for the body, if necessary. pub struct ResponseIo { - body: Option<(StreamReader, mpsc::Sender)>, + body: Option<(StreamReader, mpsc::Sender)>, trailers: Option<( FutureReader, oneshot::Sender>>, @@ -274,7 +276,8 @@ impl ResponseIo { where T: ResourceView + 'static, { - if let Some((mut contents, contents_tx)) = self.body.take() { + if let Some((contents, contents_tx)) = self.body.take() { + let mut contents = WithAccessor::new(store, contents); let mut rx_buffer = BytesMut::with_capacity(DEFAULT_BUFFER_CAPACITY); while !contents.is_closed() { rx_buffer = contents.read(store, rx_buffer).await; @@ -286,7 +289,6 @@ impl ResponseIo { rx_buffer.reserve(DEFAULT_BUFFER_CAPACITY); } } - drop(contents_tx); } if let Some((trailers, trailers_tx)) = self.trailers.take() { diff --git a/crates/wasi/src/p3/cli/host.rs b/crates/wasi/src/p3/cli/host.rs index 4163dcbcc6..1e22790fe0 100644 --- a/crates/wasi/src/p3/cli/host.rs +++ b/crates/wasi/src/p3/cli/host.rs @@ -8,13 +8,11 @@ use anyhow::{Context as _, anyhow}; use bytes::BytesMut; use std::io::Cursor; use tokio::io::{AsyncRead, AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _}; -use wasmtime::component::{ - Accessor, AccessorTask, HostStream, Resource, StreamReader, StreamWriter, -}; +use wasmtime::component::{Accessor, AccessorTask, Resource, StreamReader, StreamWriter}; struct InputTask { input: T, - tx: StreamWriter>, + tx: StreamWriter, } impl AccessorTask, wasmtime::Result<()>> for InputTask @@ -46,7 +44,7 @@ where struct OutputTask { output: T, - data: StreamReader, + data: StreamReader, } impl AccessorTask, wasmtime::Result<()>> for OutputTask @@ -158,15 +156,17 @@ impl stdin::HostConcurrent for WasiCli where T: WasiCliView + 'static, { - async fn get_stdin(store: &Accessor) -> wasmtime::Result> { + async fn get_stdin( + store: &Accessor, + ) -> wasmtime::Result> { store.with(|mut view| { let instance = view.instance(); let (tx, rx) = instance - .stream::<_, _, Vec<_>>(&mut view) + .stream(&mut view) .context("failed to create stream")?; let stdin = view.get().cli().stdin.reader(); view.spawn(InputTask { input: stdin, tx }); - Ok(rx.into()) + Ok(rx) }) } } @@ -179,11 +179,10 @@ where { async fn set_stdout( store: &Accessor, - data: HostStream, + data: StreamReader, ) -> wasmtime::Result<()> { store.with(|mut view| { let stdout = view.get().cli().stdout.writer(); - let data = data.into_reader(&mut view); view.spawn(OutputTask { output: stdout, data, @@ -201,11 +200,10 @@ where { async fn set_stderr( store: &Accessor, - data: HostStream, + data: StreamReader, ) -> wasmtime::Result<()> { store.with(|mut view| { let stderr = view.get().cli().stderr.writer(); - let data = data.into_reader(&mut view); view.spawn(OutputTask { output: stderr, data, diff --git a/crates/wasi/src/p3/filesystem/host.rs b/crates/wasi/src/p3/filesystem/host.rs index 4d7c3a26ab..58e3eef5e7 100644 --- a/crates/wasi/src/p3/filesystem/host.rs +++ b/crates/wasi/src/p3/filesystem/host.rs @@ -4,7 +4,8 @@ use anyhow::{Context as _, anyhow}; use system_interface::fs::FileIoExt as _; use tokio::sync::mpsc; use wasmtime::component::{ - Accessor, AccessorTask, HostFuture, HostStream, Lower, Resource, ResourceTable, + Accessor, AccessorTask, FutureReader, Lower, Resource, ResourceTable, StreamReader, + WithAccessor, WithAccessorAndValue, }; use crate::p3::bindings::filesystem::types::{ @@ -57,18 +58,26 @@ where store: &Accessor, fd: Resource, mut offset: Filesize, - ) -> wasmtime::Result<(HostStream, HostFuture>)> { - store.with(|mut view| { + ) -> wasmtime::Result<(StreamReader, FutureReader>)> { + let ((data_tx, data_rx), (res_tx, res_rx)) = store.with(|mut view| { let instance = view.instance(); - let (data_tx, data_rx) = instance - .stream::<_, _, Vec<_>>(&mut view) + let data = instance + .stream(&mut view) .context("failed to create stream")?; - let (res_tx, res_rx) = instance - .future(|| unreachable!(), &mut view) + let res = instance + .future(&mut view) .context("failed to create future")?; + anyhow::Ok((data, res)) + })?; + let data_tx = WithAccessor::new(store, data_tx); + let data_rx = WithAccessor::new(store, data_rx); + let res_tx = WithAccessorAndValue::new(store, res_tx, Ok(())); + let res_rx = WithAccessor::new(store, res_rx); + + let result = store.with(|mut view| { let mut binding = view.get(); let fd = get_descriptor(binding.table(), &fd)?; - match fd.file() { + anyhow::Ok(match fd.file() { Ok(f) => { let (task_tx, task_rx) = mpsc::channel(1); let f = f.clone(); @@ -117,39 +126,46 @@ where .push(AbortOnDropHandle(task)) .context("failed to push task to table")? }; - view.spawn(ReadTask { - io: IoTask { - data: data_tx, - result: res_tx, - rx: task_rx, - }, - id, - tasks, - }); - } - Err(err) => { - drop(data_tx); - view.spawn_fn_box(move |store| { - Box::pin(async move { - res_tx.write(store, Err(err)).await; - Ok(()) - }) - }); + Ok((task_rx, id, tasks)) } + Err(err) => Err(err), + }) + })?; + + match result { + Ok((task_rx, id, tasks)) => { + store.spawn(ReadTask { + io: IoTask { + data: data_tx.into_inner(), + result: res_tx.into_inner(), + rx: task_rx, + }, + id, + tasks, + }); } - Ok((data_rx.into(), res_rx.into())) - }) + Err(err) => { + let res_tx = res_tx.into_inner(); + store.spawn_fn_box(move |store| { + Box::pin(async move { + res_tx.write(store, Err(err)).await; + Ok(()) + }) + }); + } + } + + Ok((data_rx.into_inner(), res_rx.into_inner())) } async fn write_via_stream( store: &Accessor, fd: Resource, - data: HostStream, + data: StreamReader, mut offset: Filesize, ) -> wasmtime::Result> { let (fd, mut data) = store.with(|mut view| -> wasmtime::Result<_> { let fd = get_descriptor(view.get().table(), &fd)?.clone(); - let data = data.into_reader::>(&mut view); Ok((fd, data)) })?; let f = match fd.file() { @@ -189,11 +205,10 @@ where async fn append_via_stream( store: &Accessor, fd: Resource, - data: HostStream, + data: StreamReader, ) -> wasmtime::Result> { let (fd, mut data) = store.with(|mut view| { let fd = get_descriptor(view.get().table(), &fd)?.clone(); - let data = data.into_reader::>(&mut view); anyhow::Ok((fd, data)) })?; let f = match fd.file() { @@ -303,125 +318,143 @@ where store: &Accessor, fd: Resource, ) -> wasmtime::Result<( - HostStream, - HostFuture>, + StreamReader, + FutureReader>, )> { - store.with(|mut view| { + let ((data_tx, data_rx), (res_tx, res_rx)) = store.with(|mut view| { let instance = view.instance(); - let (data_tx, data_rx) = instance - .stream::<_, _, Vec<_>>(&mut view) + let data = instance + .stream(&mut view) .context("failed to create stream")?; - let (res_tx, res_rx) = instance - .future(|| unreachable!(), &mut view) + let res = instance + .future(&mut view) .context("failed to create future")?; + anyhow::Ok((data, res)) + })?; + let data_tx = WithAccessor::new(store, data_tx); + let data_rx = WithAccessor::new(store, data_rx); + let res_tx = WithAccessorAndValue::new(store, res_tx, Ok(())); + let res_rx = WithAccessor::new(store, res_rx); + + let result = store.with(|mut view| { let mut binding = view.get(); let fd = get_descriptor(binding.table(), &fd)?; - match fd.dir().and_then(|d| { - if !d.perms.contains(DirPerms::READ) { - Err(ErrorCode::NotPermitted) - } else { - Ok(d) - } - }) { - Ok(d) => { - let d = d.clone(); - let tasks = Arc::clone(&d.tasks); - let (task_tx, task_rx) = mpsc::channel(1); - let task = view.spawn_fn(|_| async move { - match d.run_blocking(cap_std::fs::Dir::entries).await { - Ok(mut entries) => { - while let Ok(tx) = task_tx.reserve().await { - match d - .run_blocking(|_| match entries.next()? { - Ok(entry) => { - let meta = match entry.metadata() { - Ok(meta) => meta, - Err(err) => return Some(Err(err.into())), - }; - let Ok(name) = entry.file_name().into_string() - else { - return Some(Err( - ErrorCode::IllegalByteSequence, - )); - }; - Some(Ok(( - Some(DirectoryEntry { - type_: meta.file_type().into(), - name, - }), - entries, - ))) - } - Err(err) => { - // On windows, filter out files like `C:\DumpStack.log.tmp` which we - // can't get full metadata for. - #[cfg(windows)] - { - use windows_sys::Win32::Foundation::{ - ERROR_ACCESS_DENIED, - ERROR_SHARING_VIOLATION, + anyhow::Ok( + match fd.dir().and_then(|d| { + if !d.perms.contains(DirPerms::READ) { + Err(ErrorCode::NotPermitted) + } else { + Ok(d) + } + }) { + Ok(d) => { + let d = d.clone(); + let tasks = Arc::clone(&d.tasks); + let (task_tx, task_rx) = mpsc::channel(1); + let task = view.spawn_fn(|_| async move { + match d.run_blocking(cap_std::fs::Dir::entries).await { + Ok(mut entries) => { + while let Ok(tx) = task_tx.reserve().await { + match d + .run_blocking(|_| match entries.next()? { + Ok(entry) => { + let meta = match entry.metadata() { + Ok(meta) => meta, + Err(err) => return Some(Err(err.into())), + }; + let Ok(name) = entry.file_name().into_string() + else { + return Some(Err( + ErrorCode::IllegalByteSequence, + )); }; - if err.raw_os_error() - == Some(ERROR_SHARING_VIOLATION as i32) - || err.raw_os_error() - == Some(ERROR_ACCESS_DENIED as i32) + Some(Ok(( + Some(DirectoryEntry { + type_: meta.file_type().into(), + name, + }), + entries, + ))) + } + Err(err) => { + // On windows, filter out files like `C:\DumpStack.log.tmp` which we + // can't get full metadata for. + #[cfg(windows)] { - return Some(Ok((None, entries))); + use windows_sys::Win32::Foundation::{ + ERROR_ACCESS_DENIED, + ERROR_SHARING_VIOLATION, + }; + if err.raw_os_error() + == Some(ERROR_SHARING_VIOLATION as i32) + || err.raw_os_error() + == Some(ERROR_ACCESS_DENIED as i32) + { + return Some(Ok((None, entries))); + } } + Some(Err(err.into())) } - Some(Err(err.into())) + }) + .await + { + None => break, + Some(Ok((entry, tail))) => { + if let Some(entry) = entry { + tx.send(Ok(vec![entry])); + } + entries = tail; } - }) - .await - { - None => break, - Some(Ok((entry, tail))) => { - if let Some(entry) = entry { - tx.send(Ok(vec![entry])); + Some(Err(err)) => { + tx.send(Err(err)); + break; } - entries = tail; - } - Some(Err(err)) => { - tx.send(Err(err)); - break; } } } + Err(err) => { + _ = task_tx.send(Err(err.into())).await; + } } - Err(err) => { - _ = task_tx.send(Err(err.into())).await; - } - } - Ok(()) - }); - let id = { - let mut tasks = tasks.lock().map_err(|_| anyhow!("lock poisoned"))?; - tasks - .push(AbortOnDropHandle(task)) - .context("failed to push task to table")? - }; - view.spawn(ReadTask { - io: IoTask { - data: data_tx, - result: res_tx, - rx: task_rx, - }, - id, - tasks, - }); - } - Err(err) => { - drop(data_tx); - view.spawn_fn_box(move |store| { - Box::pin(async move { - res_tx.write(store, Err(err)).await; Ok(()) - }) - }); - } + }); + let id = { + let mut tasks = tasks.lock().map_err(|_| anyhow!("lock poisoned"))?; + tasks + .push(AbortOnDropHandle(task)) + .context("failed to push task to table")? + }; + Ok((task_rx, id, tasks)) + } + Err(err) => Err(err), + }, + ) + })?; + + match result { + Ok((task_rx, id, tasks)) => { + store.spawn(ReadTask { + io: IoTask { + data: data_tx.into_inner(), + result: res_tx.into_inner(), + rx: task_rx, + }, + id, + tasks, + }); + } + Err(err) => { + let res_tx = res_tx.into_inner(); + store.spawn_fn_box(move |store| { + Box::pin(async move { + res_tx.write(store, Err(err)).await; + Ok(()) + }) + }); } - Ok((data_rx.into(), res_rx.into())) - }) + } + + Ok((data_rx.into_inner(), res_rx.into_inner())) } async fn sync( diff --git a/crates/wasi/src/p3/mod.rs b/crates/wasi/src/p3/mod.rs index 1f27126fef..15a1286ea6 100644 --- a/crates/wasi/src/p3/mod.rs +++ b/crates/wasi/src/p3/mod.rs @@ -10,7 +10,7 @@ use std::sync::Arc; use tokio::sync::mpsc; use wasmtime::component::{ AbortHandle, Access, Accessor, AccessorTask, FutureWriter, HasData, Linker, Lower, - ResourceTable, StreamWriter, VecBuffer, + ResourceTable, StreamWriter, VecBuffer, WithAccessor, }; pub mod bindings; @@ -231,7 +231,7 @@ impl SpawnExt for &Accessor { } pub struct IoTask { - pub data: StreamWriter>, + pub data: StreamWriter, pub result: FutureWriter>, pub rx: mpsc::Receiver, E>>, } @@ -243,7 +243,7 @@ where E: Lower + Send + Sync + 'static, { async fn run(mut self, store: &Accessor) -> wasmtime::Result<()> { - let mut tx = self.data; + let mut tx = WithAccessor::new(store, self.data); let res = loop { match self.rx.recv().await { None => { @@ -251,7 +251,7 @@ where break Ok(()); } Some(Ok(buf)) => { - tx.write_all(store, buf.into()).await; + tx.write_all(store, VecBuffer::from(buf)).await; if tx.is_closed() { break Ok(()); } diff --git a/crates/wasi/src/p3/sockets/host/types/tcp.rs b/crates/wasi/src/p3/sockets/host/types/tcp.rs index 6faa81b396..7d868d1b98 100644 --- a/crates/wasi/src/p3/sockets/host/types/tcp.rs +++ b/crates/wasi/src/p3/sockets/host/types/tcp.rs @@ -9,7 +9,8 @@ use io_lifetimes::AsSocketlike as _; use rustix::io::Errno; use tokio::sync::mpsc; use wasmtime::component::{ - Accessor, AccessorTask, HostFuture, HostStream, Resource, ResourceTable, StreamWriter, + Accessor, AccessorTask, FutureReader, Resource, ResourceTable, StreamReader, StreamWriter, + WithAccessor, WithAccessorAndValue, }; use crate::p3::bindings::sockets::types::{ @@ -54,7 +55,7 @@ fn get_socket_mut<'a>( struct ListenTask { family: SocketAddressFamily, - tx: StreamWriter>>, + tx: StreamWriter>, rx: mpsc::Receiver>, // The socket options below are not automatically inherited from the listener @@ -269,7 +270,7 @@ where async fn listen( store: &Accessor, socket: Resource, - ) -> wasmtime::Result>, ErrorCode>> { + ) -> wasmtime::Result>, ErrorCode>> { match store.with(|mut view| { if !view.get().sockets().allowed_network_uses.tcp { return Ok(Err(ErrorCode::AccessDenied)); @@ -287,7 +288,7 @@ where }; let instance = view.instance(); let (tx, rx) = instance - .stream::<_, _, Vec<_>>(&mut view) + .stream(&mut view) .context("failed to create stream")?; let &TcpSocket { listen_backlog_size, @@ -363,7 +364,7 @@ where }) { Ok(Ok((rx, task))) => { store.spawn(task); - Ok(Ok(rx.into())) + Ok(Ok(rx)) } Ok(Err(err)) => Ok(Err(err)), Err(err) => Err(err), @@ -373,10 +374,9 @@ where async fn send( store: &Accessor, socket: Resource, - data: HostStream, + data: StreamReader, ) -> wasmtime::Result> { let (stream, mut data) = match store.with(|mut view| -> wasmtime::Result<_> { - let data = data.into_reader::>(&mut view); let mut binding = view.get(); let sock = get_socket(binding.table(), &socket)?; if let TcpState::Connected { stream, .. } = &sock.tcp_state { @@ -419,18 +419,26 @@ where async fn receive( store: &Accessor, socket: Resource, - ) -> wasmtime::Result<(HostStream, HostFuture>)> { - store.with(|mut view| { + ) -> wasmtime::Result<(StreamReader, FutureReader>)> { + let ((data_tx, data_rx), (res_tx, res_rx)) = store.with(|mut view| { let instance = view.instance(); - let (data_tx, data_rx) = instance - .stream::<_, _, Vec<_>>(&mut view) + let data = instance + .stream(&mut view) .context("failed to create stream")?; - let (res_tx, res_rx) = instance - .future(|| unreachable!(), &mut view) + let res = instance + .future(&mut view) .context("failed to create future")?; + anyhow::Ok((data, res)) + })?; + let data_tx = WithAccessor::new(store, data_tx); + let data_rx = WithAccessor::new(store, data_rx); + let res_tx = WithAccessorAndValue::new(store, res_tx, Ok(())); + let res_rx = WithAccessor::new(store, res_rx); + + let result = store.with(|mut view| { let mut binding = view.get(); let sock = get_socket(binding.table(), &socket)?; - match &sock.tcp_state { + anyhow::Ok(match &sock.tcp_state { TcpState::Connected { stream, rx_task: None, @@ -463,11 +471,6 @@ where .shutdown(Shutdown::Read); Ok(()) }); - view.spawn(IoTask { - data: data_tx, - result: res_tx, - rx: task_rx, - }); let mut binding = view.get(); let TcpSocket { tcp_state: TcpState::Connected { rx_task, .. }, @@ -477,18 +480,32 @@ where bail!("corrupted socket state"); }; *rx_task = Some(AbortOnDropHandle(task)); + Ok(task_rx) } - _ => { - view.spawn_fn_box(move |store| { - Box::pin(async move { - res_tx.write(store, Err(ErrorCode::InvalidState)).await; - Ok(()) - }) - }); - } + _ => Err(ErrorCode::InvalidState), + }) + })?; + + match result { + Ok(task_rx) => { + store.spawn(IoTask { + data: data_tx.into_inner(), + result: res_tx.into_inner(), + rx: task_rx, + }); } - Ok((data_rx.into(), res_rx.into())) - }) + Err(err) => { + let res_tx = res_tx.into_inner(); + store.spawn_fn_box(move |store| { + Box::pin(async move { + res_tx.write(store, Err(err)).await; + Ok(()) + }) + }); + } + } + + Ok((data_rx.into_inner(), res_rx.into_inner())) } } diff --git a/crates/wasmtime/src/runtime/component/concurrent.rs b/crates/wasmtime/src/runtime/component/concurrent.rs index f0174d59d0..1610741868 100644 --- a/crates/wasmtime/src/runtime/component/concurrent.rs +++ b/crates/wasmtime/src/runtime/component/concurrent.rs @@ -89,8 +89,8 @@ use wasmtime_environ::component::{ pub use abort::AbortHandle; pub use futures_and_streams::{ - ErrorContext, FutureReader, FutureWriter, HostFuture, HostStream, ReadBuffer, StreamReader, - StreamWriter, VecBuffer, Watch, WriteBuffer, + DropWithStore, DropWithStoreAndValue, ErrorContext, FutureReader, FutureWriter, ReadBuffer, + StreamReader, StreamWriter, VecBuffer, WithAccessor, WithAccessorAndValue, WriteBuffer, }; pub(crate) use futures_and_streams::{ ResourcePair, lower_error_context_to_index, lower_future_to_index, lower_stream_to_index, @@ -101,7 +101,7 @@ mod error_contexts; mod futures_and_streams; mod states; mod table; -mod tls; +pub(crate) mod tls; /// Constant defined in the Component Model spec to indicate that the async /// intrinsic (e.g. `future.write`) has not yet completed. @@ -229,7 +229,7 @@ where where T: 'static, { - self.accessor.instance.spawn_with_accessor( + self.accessor.instance.unwrap().spawn_with_accessor( self.store.as_context_mut(), self.accessor.clone_for_spawn(), task, @@ -329,7 +329,7 @@ where { token: StoreToken, get_data: fn(&mut T) -> D::Data<'_>, - instance: Instance, + instance: Option, } /// A helper trait to take any type of accessor-with-data in functions. @@ -416,7 +416,7 @@ impl Accessor { /// /// - `instance`: used to access the `Instance` to which this `Accessor` /// (and the future which closes over it) belongs - fn new(token: StoreToken, instance: Instance) -> Self { + pub(crate) fn new(token: StoreToken, instance: Option) -> Self { Self { token, get_data: |x| x, @@ -455,6 +455,17 @@ where }) } + /// Like `with`, except skips running `fun` if the thread-local state is not + /// set. + fn maybe_with(&self, fun: impl FnOnce(Access<'_, T, D>) -> R) -> Option { + tls::maybe_get(|vmstore| { + fun(Access { + store: self.token.as_context_mut(vmstore), + accessor: self, + }) + }) + } + /// Changes this accessor to access `D2` instead of the current type /// parameter `D`. /// @@ -507,7 +518,7 @@ where where T: 'static, { - let instance = self.instance; + let instance = self.instance.unwrap(); let accessor = self.clone_for_spawn(); self.with(|mut access| { instance.spawn_with_accessor(access.as_context_mut(), accessor, task) @@ -516,7 +527,7 @@ where /// Retrieve the component instance of the caller. pub fn instance(&self) -> Instance { - self.instance + self.instance.unwrap() } fn clone_for_spawn(&self) -> Self { @@ -1289,7 +1300,7 @@ impl Instance { let token = StoreToken::new(store.as_context_mut()); self.poll_until(store.as_context_mut(), async move { - let accessor = Accessor::new(token, self); + let accessor = Accessor::new(token, Some(self)); fun(&accessor).await }) .await @@ -1310,7 +1321,7 @@ impl Instance { task: impl AccessorTask, Result<()>>, ) -> AbortHandle { let mut store = store.as_context_mut(); - let accessor = Accessor::new(StoreToken::new(store.as_context_mut()), self); + let accessor = Accessor::new(StoreToken::new(store.as_context_mut()), Some(self)); self.spawn_with_accessor(store, accessor, task) } @@ -1405,34 +1416,49 @@ impl Instance { // outer loop in case there is another one ready to // complete. Poll::Ready(true) => Poll::Ready(Ok(Either::Right(Vec::new()))), - // In this case, there are no more pending futures - // in `ConcurrentState::futures`, there are no - // remaining work items, _and_ the future we were - // passed as an argument still hasn't completed, - // meaning we're stuck, so we return an error. The - // underlying assumption is that `future` depends on - // this component instance making such progress, and - // thus there's no point in continuing to poll it - // given we've run out of work to do. - // - // Note that we'd also reach this point if the host - // embedder passed e.g. a `std::future::Pending` to - // `Instance::run_concurrent`, in which case we'd - // return a "deadlock" error even when any and all - // tasks have completed normally. However, that's - // not how `Instance::run_concurrent` is intended - // (and documented) to be used, so it seems - // reasonable to lump that case in with "real" - // deadlocks. - // - // TODO: Once we've added host APIs for cancelling - // in-progress tasks, we can return some other, - // non-error value here, treating it as "normal" and - // giving the host embedder a chance to intervene by - // cancelling one or more tasks and/or starting new - // tasks capable of waking the existing ones. Poll::Ready(false) => { - Poll::Ready(Err(anyhow!(crate::Trap::AsyncDeadlock))) + // Poll the future we were passed one last time + // in case one of `ConcurrentState::futures` had + // the side effect of unblocking it. + if let Poll::Ready(value) = + self.set_tls(store.0, || future.as_mut().poll(cx)) + { + Poll::Ready(Ok(Either::Left(value))) + } else { + // In this case, there are no more pending + // futures in `ConcurrentState::futures`, + // there are no remaining work items, _and_ + // the future we were passed as an argument + // still hasn't completed, meaning we're + // stuck, so we return an error. The + // underlying assumption is that `future` + // depends on this component instance making + // such progress, and thus there's no point + // in continuing to poll it given we've run + // out of work to do. + // + // Note that we'd also reach this point if + // the host embedder passed e.g. a + // `std::future::Pending` to + // `Instance::run_concurrent`, in which case + // we'd return a "deadlock" error even when + // any and all tasks have completed + // normally. However, that's not how + // `Instance::run_concurrent` is intended + // (and documented) to be used, so it seems + // reasonable to lump that case in with + // "real" deadlocks. + // + // TODO: Once we've added host APIs for + // cancelling in-progress tasks, we can + // return some other, non-error value here, + // treating it as "normal" and giving the + // host embedder a chance to intervene by + // cancelling one or more tasks and/or + // starting new tasks capable of waking the + // existing ones. + Poll::Ready(Err(anyhow!(crate::Trap::AsyncDeadlock))) + } } // There is at least one pending future in // `ConcurrentState::futures` and we have nothing @@ -2442,7 +2468,7 @@ impl Instance { { let token = StoreToken::new(store); async move { - let mut accessor = Accessor::new(token, self); + let mut accessor = Accessor::new(token, Some(self)); closure(&mut accessor).await } } 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 0f1b5dd43d..b7836ed9cf 100644 --- a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs +++ b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs @@ -1,32 +1,31 @@ use super::table::{TableDebug, TableId}; use super::{ - Event, GlobalErrorContextRefCount, HostTaskOutput, LocalErrorContextRefCount, StateTable, - Waitable, WaitableCommon, WaitableState, + Event, GlobalErrorContextRefCount, LocalErrorContextRefCount, StateTable, Waitable, + WaitableCommon, WaitableState, }; -use crate::component::concurrent::{ConcurrentState, tls}; +use crate::component::concurrent::ConcurrentState; use crate::component::func::{self, LiftContext, LowerContext, Options}; use crate::component::matching::InstanceType; use crate::component::values::{ErrorContextAny, FutureAny, StreamAny}; -use crate::component::{AsAccessor, Instance, Lower, Val, WasmList, WasmStr}; +use crate::component::{ + Accessor, AsAccessor, HasData, HasSelf, Instance, Lower, Val, WasmList, WasmStr, +}; use crate::store::{StoreOpaque, StoreToken}; use crate::vm::{VMFuncRef, VMMemoryDefinition, VMStore}; use crate::{AsContextMut, StoreContextMut, ValRaw}; use anyhow::{Context, Result, anyhow, bail}; use buffers::Extender; use buffers::UntypedWriteBuffer; -use futures::channel::{mpsc, oneshot}; -use futures::future::{self, FutureExt}; -use futures::stream::StreamExt; +use futures::channel::oneshot; use std::boxed::Box; use std::fmt; -use std::future::Future; use std::iter; use std::marker::PhantomData; use std::mem::{self, MaybeUninit}; +use std::ops::{Deref, DerefMut}; use std::ptr::NonNull; use std::string::{String, ToString}; -use std::sync::{Arc, Mutex}; -use std::task::{Poll, Waker}; +use std::sync::Arc; use std::vec::Vec; use wasmtime_environ::component::{ CanonicalAbiInfo, ComponentTypes, InterfaceType, RuntimeComponentInstanceIndex, StringEncoding, @@ -178,156 +177,190 @@ fn waitable_state(ty: TableIndex, state: StreamFutureState) -> WaitableState { } } -/// Return a closure which matches a host write operation to a read (or drop) -/// operation. -/// -/// This may be used when the host initiates a write but there is no read -/// pending at the other end, in which case we construct a -/// `WriteState::HostReady` using the closure created here and leave it in -/// `TransmitState::write` for the reader to find and call when it's ready. +/// Complete a write initiated by a host-owned future or stream by matching it +/// with the specified `Reader`. fn accept_reader, U: 'static>( - store: StoreContextMut, + mut store: StoreContextMut, + instance: Instance, + reader: Reader, mut buffer: B, - tx: oneshot::Sender>, kind: TransmitKind, -) -> impl FnOnce(&mut dyn VMStore, Instance, Reader) -> Result -+ Send -+ Sync -+ 'static -+ use { - let token = StoreToken::new(store); - move |store, instance, reader| { - let code = match reader { - Reader::Guest { - options, - ty, - address, - count, - } => { - let mut store = token.as_context_mut(store); - let types = instance.id().get(store.0).component().types().clone(); - let count = buffer.remaining().len().min(count); - - let lower = - &mut LowerContext::new(store.as_context_mut(), options, &types, instance); - if address % usize::try_from(T::ALIGN32)? != 0 { - bail!("read pointer not aligned"); - } - lower - .as_slice_mut() - .get_mut(address..) - .and_then(|b| b.get_mut(..T::SIZE32 * count)) - .ok_or_else(|| anyhow::anyhow!("read pointer out of bounds of memory"))?; - - if let Some(ty) = payload(ty, &types) { - T::linear_store_list_to_memory( - lower, - ty, - address, - &buffer.remaining()[..count], - )?; - } - - buffer.skip(count); - _ = tx.send(HostResult { +) -> Result<(HostResult, ReturnCode)> { + Ok(match reader { + Reader::Guest { + options, + ty, + address, + count, + } => { + let types = instance.id().get(store.0).component().types().clone(); + let count = buffer.remaining().len().min(count); + + let lower = &mut LowerContext::new(store.as_context_mut(), options, &types, instance); + if address % usize::try_from(T::ALIGN32)? != 0 { + bail!("read pointer not aligned"); + } + lower + .as_slice_mut() + .get_mut(address..) + .and_then(|b| b.get_mut(..T::SIZE32 * count)) + .ok_or_else(|| anyhow::anyhow!("read pointer out of bounds of memory"))?; + + if let Some(ty) = payload(ty, &types) { + T::linear_store_list_to_memory(lower, ty, address, &buffer.remaining()[..count])?; + } + + buffer.skip(count); + ( + HostResult { buffer, dropped: false, - }); - ReturnCode::completed(kind, count.try_into().unwrap()) - } - Reader::Host { accept } => { - let count = buffer.remaining().len(); - let mut untyped = UntypedWriteBuffer::new(&mut buffer); - let count = accept(&mut untyped, count); - _ = tx.send(HostResult { + }, + ReturnCode::completed(kind, count.try_into().unwrap()), + ) + } + Reader::Host { accept } => { + let count = buffer.remaining().len(); + let mut untyped = UntypedWriteBuffer::new(&mut buffer); + let count = accept(&mut untyped, count); + ( + HostResult { buffer, dropped: false, - }); - ReturnCode::completed(kind, count.try_into().unwrap()) - } - Reader::End => { - _ = tx.send(HostResult { - buffer, - dropped: true, - }); - ReturnCode::Dropped(0) - } - }; - - Ok(code) - } + }, + ReturnCode::completed(kind, count.try_into().unwrap()), + ) + } + Reader::End => ( + HostResult { + buffer, + dropped: true, + }, + ReturnCode::Dropped(0), + ), + }) } -/// Return a closure which matches a host read operation to a write (or drop) -/// operation. -/// -/// This may be used when the host initiates a read but there is no write -/// pending at the other end, in which case we construct a -/// `ReadState::HostReady` using the closure created here and leave it in -/// `TransmitState::read` for the writer to find and call when it's ready. +/// Complete a read initiated by a host-owned future or stream by matching it with the +/// specified `Writer`. fn accept_writer, U>( + writer: Writer, mut buffer: B, - tx: oneshot::Sender>, kind: TransmitKind, -) -> impl FnOnce(Writer) -> Result + Send + Sync + 'static { - move |writer| { - let count = match writer { - Writer::Guest { - lift, - ty, - address, - count, - } => { - let count = count.min(buffer.remaining_capacity()); - if T::IS_RUST_UNIT_TYPE { - // SAFETY: `T::IS_RUST_UNIT_TYPE` is only true for `()`, a - // zero-sized type, so `MaybeUninit::uninit().assume_init()` - // is a valid way to populate the zero-sized buffer. - buffer.extend( - iter::repeat_with(|| unsafe { MaybeUninit::uninit().assume_init() }) - .take(count), - ) - } else { - let ty = ty.unwrap(); - if address % usize::try_from(T::ALIGN32)? != 0 { - bail!("write pointer not aligned"); - } - lift.memory() - .get(address..) - .and_then(|b| b.get(..T::SIZE32 * count)) - .ok_or_else(|| anyhow::anyhow!("write pointer out of bounds of memory"))?; - - let list = &WasmList::new(address, count, lift, ty)?; - T::linear_lift_into_from_memory(lift, list, &mut Extender(&mut buffer))? +) -> Result<(HostResult, ReturnCode)> { + Ok(match writer { + Writer::Guest { + lift, + ty, + address, + count, + } => { + let count = count.min(buffer.remaining_capacity()); + if T::IS_RUST_UNIT_TYPE { + // SAFETY: `T::IS_RUST_UNIT_TYPE` is only true for `()`, a + // zero-sized type, so `MaybeUninit::uninit().assume_init()` + // is a valid way to populate the zero-sized buffer. + buffer.extend( + iter::repeat_with(|| unsafe { MaybeUninit::uninit().assume_init() }) + .take(count), + ) + } else { + let ty = ty.unwrap(); + if address % usize::try_from(T::ALIGN32)? != 0 { + bail!("write pointer not aligned"); } - _ = tx.send(HostResult { - buffer, - dropped: false, - }); - ReturnCode::completed(kind, count.try_into().unwrap()) + lift.memory() + .get(address..) + .and_then(|b| b.get(..T::SIZE32 * count)) + .ok_or_else(|| anyhow::anyhow!("write pointer out of bounds of memory"))?; + + let list = &WasmList::new(address, count, lift, ty)?; + T::linear_lift_into_from_memory(lift, list, &mut Extender(&mut buffer))? } - Writer::Host { - buffer: input, - count, - } => { - let count = count.min(buffer.remaining_capacity()); - buffer.move_from(input.get_mut::(), count); - _ = tx.send(HostResult { + ( + HostResult { buffer, dropped: false, - }); - ReturnCode::completed(kind, count.try_into().unwrap()) - } - Writer::End => { - _ = tx.send(HostResult { + }, + ReturnCode::completed(kind, count.try_into().unwrap()), + ) + } + Writer::Host { + buffer: input, + count, + } => { + let count = count.min(buffer.remaining_capacity()); + buffer.move_from(input.get_mut::(), count); + ( + HostResult { buffer, - dropped: true, - }); - ReturnCode::Dropped(0) - } - }; + dropped: false, + }, + ReturnCode::completed(kind, count.try_into().unwrap()), + ) + } + Writer::End => ( + HostResult { + buffer, + dropped: true, + }, + ReturnCode::Dropped(0), + ), + }) +} + +/// Return a `Future` which will resolve once the reader end corresponding to +/// the specified writer end of a future or stream is dropped. +async fn watch_reader(accessor: impl AsAccessor, instance: Instance, id: TableId) { + let result = accessor.as_accessor().with(|mut access| { + let concurrent_state = instance.concurrent_state_mut(access.as_context_mut().0); + let state_id = concurrent_state.get(id)?.state; + let state = concurrent_state.get_mut(state_id)?; + anyhow::Ok(if matches!(&state.read, ReadState::Dropped) { + None + } else { + let (tx, rx) = oneshot::channel(); + state.reader_watcher = Some(tx); + Some(rx) + }) + }); + + if let Ok(Some(rx)) = result { + _ = rx.await + } +} + +/// Return a `Future` which will resolve once the writer end corresponding to +/// the specified reader end of a future or stream is dropped. +async fn watch_writer(accessor: impl AsAccessor, instance: Instance, id: TableId) { + let result = accessor.as_accessor().with(|mut access| { + let concurrent_state = instance.concurrent_state_mut(access.as_context_mut().0); + let state_id = concurrent_state.get(id)?.state; + let state = concurrent_state.get_mut(state_id)?; + anyhow::Ok( + if matches!( + &state.write, + WriteState::Dropped + | WriteState::GuestReady { + post_write: PostWrite::Drop, + .. + } + | WriteState::HostReady { + post_write: PostWrite::Drop, + .. + } + ) { + None + } else { + let (tx, rx) = oneshot::channel(); + state.writer_watcher = Some(tx); + Some(rx) + }, + ) + }); - Ok(count) + if let Ok(Some(rx)) = result { + _ = rx.await } } @@ -368,136 +401,229 @@ pub(super) struct FlatAbi { pub(super) align: u32, } -/// Represents a pending event on a host-owned write end of a stream or future. -/// -/// See `ComponentInstance::start_write_event_loop` for details. -enum WriteEvent { - /// Write the items in the specified buffer to the stream or future, and - /// return the result via the specified `Sender`. - Write { - buffer: B, - tx: oneshot::Sender>, - }, - /// Drop the write end of the stream or future. - Drop(Option B + Send + Sync>>), - /// Watch the read (i.e. opposite) end of this stream or future, dropping - /// the specified sender when it is dropped. - Watch { tx: oneshot::Sender<()> }, +/// Trait representing objects (such as streams, futures, or structs containing +/// them) which require access to the store in order to be disposed of properly. +pub trait DropWithStore: Sized { + /// Dispose of `Self` using the specified store. + fn drop(self, store: impl AsContextMut) -> Result<()>; + + /// Attempt to dispose of `Self` using the specified accessor. + /// + /// Note that this will return `None` without disposing of `Self` if called + /// from outside the context where the specified `Accessor` is viable + /// (i.e. from outside a `Future::poll` call for the `Future` closing over + /// the `Accessor`). See the [`Accessor`] docs for further details. + fn maybe_drop_with(self, accessor: impl AsAccessor) -> Option> { + // Note that we use `Accessor::maybe_with` here instead of + // `Accessor::with`. This is because we might have been called when the + // thread-local store is not set, in which case we'd rather leak + // (i.e. not call `Self::drop) than panic. The most likely reason the + // thread-local store is unset is that the store itself is being + // dropped, in which case leaking objects inside the store is just fine + // because the whole thing is about to go away. + accessor + .as_accessor() + .maybe_with(move |store| self.drop(store)) + } + + /// Attempt to dispose of `Self` using the specified accessor. + /// + /// This will panic if called from outside the context where the specified + /// `Accessor` is viable. See `maybe_drop_with` for details. + fn drop_with(self, accessor: impl AsAccessor) -> Result<()> { + self.maybe_drop_with(accessor).unwrap() + } } -/// Represents a pending event on a host-owned read end of a stream or future. -/// -/// See `ComponentInstance::start_read_event_loop` for details. -enum ReadEvent { - /// Read as many items as the specified buffer will hold from the stream or - /// future, and return the result via the specified `Sender`. - Read { - buffer: B, - tx: oneshot::Sender>, - }, - /// Drop the read end of the stream or future. - Drop, - /// Watch the write (i.e. opposite) end of this stream or future, dropping - /// the specified sender when it is dropped. - Watch { tx: oneshot::Sender<()> }, +/// Trait representing objects (such as the writable end of a future or a struct +/// containing one) which require access to the store _and_ a value to write in +/// order to be disposed of properly. +pub trait DropWithStoreAndValue: Sized { + /// Dispose of `self` using the specified store, writing the specified value. + fn drop(self, store: impl AsContextMut, value: T) -> Result<()>; + + /// Attempt to dispose of `self` using the specified accessor and value. + /// + /// Note that this will return `None` without disposing of `self` if called + /// from outside the context where the specified `Accessor` is viable + /// (i.e. from outside a `Future::poll` call for the `Future` closing over + /// the `Accessor`). See the [`Accessor`] docs for further details. + fn maybe_drop_with(self, accessor: impl AsAccessor, value: T) -> Option> { + // See comment in `DropWithStore::maybe_drop_with` about why we use + // `Accessor::maybe_with` here. + accessor + .as_accessor() + .maybe_with(move |store| self.drop(store, value)) + } + + /// Attempt to dispose of `Self` using the specified accessor. + /// + /// This will panic if called from outside the context where the specified + /// `Accessor` is viable. See `maybe_drop_with` for details. + fn drop_with(self, accessor: impl AsAccessor, value: T) -> Result<()> { + self.maybe_drop_with(accessor, value).unwrap() + } } -/// Send the specified value to the specified `Sender`. +/// RAII wrapper for `DropWithStore` implementations. +/// +/// This may be used to automatically dispose of the wrapped object when it goes +/// out of scope. /// -/// This will panic if there is no room in the channel's buffer, so it should -/// only be used in a context where there is at least one empty spot in the -/// buffer. It will silently ignore any other error (e.g. if the `Receiver` has -/// been dropped). -fn send(tx: &mut mpsc::Sender, value: T) { - if let Err(e) = tx.try_send(value) { - if e.is_full() { - unreachable!(); +/// Note that this will call `DropWithStore::maybe_drop_with` when dropped, +/// which may silently skip disposal if called from outside the `Future::poll` +/// call where the `Accessor` is enabled. +pub struct WithAccessor<'a, T: DropWithStore, U: 'static, D: HasData = HasSelf> { + accessor: &'a Accessor, + inner: Option, +} + +impl<'a, T: DropWithStore, U, D: HasData> WithAccessor<'a, T, U, D> { + /// Create a new instance wrapping the specified `inner` object. + pub fn new(accessor: &'a Accessor, inner: T) -> Self { + Self { + accessor, + inner: Some(inner), } } + + /// Deconstruct `self`, returning the inner object. + pub fn into_inner(mut self) -> T { + // TODO: Could consider using `unwrap_unchecked` here for performance. + self.inner.take().unwrap() + } } -/// Wrapper struct which may be converted to the inner value as needed. -/// -/// This object is normally paired with a `Future` which represents a state -/// change on the inner value, resolving when that state change happens _or_ -/// when the `Watch` is converted back into the inner value -- whichever happens -/// first. -pub struct Watch { - inner: T, - waker: Arc>, +impl<'a, T: DropWithStore, U, D: HasData> Deref for WithAccessor<'a, T, U, D> { + type Target = T; + + fn deref(&self) -> &T { + // TODO: Could consider using `unwrap_unchecked` here for performance. + self.inner.as_ref().unwrap() + } } -enum WatchState { - Idle, - Waiting(Waker), - Done, +impl<'a, T: DropWithStore, U, D: HasData> DerefMut for WithAccessor<'a, T, U, D> { + fn deref_mut(&mut self) -> &mut T { + // TODO: Could consider using `unwrap_unchecked` here for performance. + self.inner.as_mut().unwrap() + } } -impl Watch { - /// Convert this object into its inner value. - /// - /// Calling this function will cause the associated `Future` to resolve - /// immediately if it hasn't already. - pub fn into_inner(self) -> T { - let state = mem::replace(&mut *self.waker.lock().unwrap(), WatchState::Done); - if let WatchState::Waiting(waker) = state { - waker.wake(); +impl<'a, T: DropWithStore, U, D: HasData> Drop for WithAccessor<'a, T, U, D> { + fn drop(&mut self) { + if let Some(inner) = self.inner.take() { + _ = inner.maybe_drop_with(self.accessor); } - self.inner } } -/// Wrap the specified `oneshot::Receiver` in a future which resolves when -/// either that `Receiver` resolves or `Watch::into_inner` has been called on -/// the returned `Watch`. -fn watch( - instance: Instance, - mut rx: oneshot::Receiver<()>, - inner: T, -) -> (impl Future + Send + 'static, Watch) { - let waker = Arc::new(Mutex::new(WatchState::Idle)); - ( - super::checked( - instance, - future::poll_fn({ - let waker = waker.clone(); +/// RAII wrapper for `DropWithStoreAndValue` implementations. +/// +/// This may be used to automatically dispose of the wrapped object when it goes +/// out of scope, passing the specified value. +/// +/// Note that this will call `DropWithStoreAndValue::maybe_drop_with` when +/// dropped, which may silently skip disposal if called from outside the +/// `Future::poll` call where the `Accessor` is enabled. +pub struct WithAccessorAndValue<'a, V, T, U, D = HasSelf> +where + U: 'static, + D: HasData, + V: func::Lower + Send + Sync + 'static, + T: DropWithStoreAndValue, +{ + accessor: &'a Accessor, + inner_and_value: Option<(T, V)>, +} + +impl< + 'a, + V: func::Lower + Send + Sync + 'static, + T: DropWithStoreAndValue, + U: 'static, + D: HasData, +> WithAccessorAndValue<'a, V, T, U, D> +{ + /// Create a new instance wrapping the specified `inner` object and value. + pub fn new(accessor: &'a Accessor, inner: T, value: V) -> Self { + Self { + accessor, + inner_and_value: Some((inner, value)), + } + } - move |cx| { - if rx.poll_unpin(cx).is_ready() { - return Poll::Ready(()); - } - let mut state = waker.lock().unwrap(); - match *state { - WatchState::Done => Poll::Ready(()), - _ => { - *state = WatchState::Waiting(cx.waker().clone()); - Poll::Pending - } - } - } - }), - ), - Watch { waker, inner }, - ) + /// Deconstruct `self`, returning the inner object. + pub fn into_inner(mut self) -> T { + // TODO: Could consider using `unwrap_unchecked` here for performance. + self.inner_and_value.take().unwrap().0 + } +} + +impl< + 'a, + V: func::Lower + Send + Sync + 'static, + T: DropWithStoreAndValue, + U: 'static, + D: HasData, +> Deref for WithAccessorAndValue<'a, V, T, U, D> +{ + type Target = T; + + fn deref(&self) -> &T { + // TODO: Could consider using `unwrap_unchecked` here for performance. + &self.inner_and_value.as_ref().unwrap().0 + } +} + +impl< + 'a, + V: func::Lower + Send + Sync + 'static, + T: DropWithStoreAndValue, + U: 'static, + D: HasData, +> DerefMut for WithAccessorAndValue<'a, V, T, U, D> +{ + fn deref_mut(&mut self) -> &mut T { + // TODO: Could consider using `unwrap_unchecked` here for performance. + &mut self.inner_and_value.as_mut().unwrap().0 + } +} + +impl< + 'a, + V: func::Lower + Send + Sync + 'static, + T: DropWithStoreAndValue, + U: 'static, + D: HasData, +> Drop for WithAccessorAndValue<'a, V, T, U, D> +{ + fn drop(&mut self) { + if let Some((inner, value)) = self.inner_and_value.take() { + _ = inner.maybe_drop_with(self.accessor, value); + } + } } /// Represents the writable end of a Component Model `future`. -pub struct FutureWriter { - default: Option T>, +/// +/// Note that `FutureWriter` instances must be disposed of using either +/// `FutureWriter::write` or `DropWithStoreAndValue::drop`; otherwise the +/// in-store representation will leak and the reader end will hang indefinitely. +/// Consider using [`WithAccessorAndValue`] to ensure that disposal happens +/// automatically. +pub struct FutureWriter { + id: TableId, instance: Instance, - tx: Option>>>, + _phantom: PhantomData, } impl FutureWriter { - fn new( - default: fn() -> T, - tx: Option>>>, - instance: Instance, - ) -> Self { + fn new(id: TableId, instance: Instance) -> Self { Self { - default: Some(default), + id, instance, - tx, + _phantom: PhantomData, } } @@ -511,28 +637,22 @@ impl FutureWriter { /// /// Panics if the store that the [`Accessor`] is derived from does not own /// this future. - pub async fn write(mut self, accessor: impl AsAccessor, value: T) -> bool + pub async fn write(self, accessor: impl AsAccessor, value: T) -> bool where - T: Send + 'static, + T: func::Lower + Send + Sync + 'static, { - // FIXME: this is intended to be used in the future to directly - // manipulate state for this future within the store without having to - // go through an mpsc. - let _accessor = accessor.as_accessor(); - let (tx, rx) = oneshot::channel(); - send( - &mut self.tx.as_mut().unwrap(), - WriteEvent::Write { - buffer: Some(value), - tx, - }, - ); - self.default = None; - let v = rx.await; - drop(self); - match v { + let accessor = accessor.as_accessor(); + + let result = self + .instance + .host_write_async(accessor, self.id, Some(value), TransmitKind::Future) + .await; + + _ = accessor.with(|store| self.just_drop(store)); + + match result { Ok(HostResult { dropped, .. }) => !dropped, - Err(_) => todo!("guarantee buffer recovery if event loop errors or panics"), + Err(_) => todo!("guarantee buffer recovery if `host_write` fails"), } } @@ -545,79 +665,109 @@ impl FutureWriter { /// /// Panics if the store that the [`Accessor`] is derived from does not own /// this future. - pub async fn watch_reader(&mut self, accessor: impl AsAccessor) - where - T: Send + 'static, - { - // FIXME: this is intended to be used in the future to directly - // manipulate state for this future within the store without having to - // go through an mpsc. - let _accessor = accessor.as_accessor(); - let (tx, rx) = oneshot::channel(); - send(&mut self.tx.as_mut().unwrap(), WriteEvent::Watch { tx }); - let (future, _watch) = watch(self.instance, rx, ()); - future.await; + pub async fn watch_reader(&mut self, accessor: impl AsAccessor) { + watch_reader(accessor, self.instance, self.id).await + } + + fn just_drop(self, mut store: impl AsContextMut) -> Result<()> { + self.instance.host_drop_writer( + store.as_context_mut().0.traitobj_mut(), + self.id, + TransmitKind::Future, + ) } } -impl Drop for FutureWriter { - fn drop(&mut self) { - if let Some(mut tx) = self.tx.take() { - send( - &mut tx, - WriteEvent::Drop(self.default.take().map(|v| { - Box::new(move || Some(v())) - as Box Option + Send + Sync + 'static> - })), - ); - } +impl DropWithStoreAndValue for FutureWriter { + fn drop(self, mut store: impl AsContextMut, value: T) -> Result<()> { + let result = self.instance.host_write( + store.as_context_mut(), + self.id, + Some(value), + TransmitKind::Future, + ); + + _ = self.just_drop(store); + + result.map(drop) } } /// Represents the readable end of a Component Model `future`. /// -/// In order to actually read from or drop this `future`, first convert it to a -/// [`FutureReader`] using the `into_reader` method. -/// -/// Note that if a value of this type is dropped without either being converted -/// to a `FutureReader` or passed to the guest, any writes on the write end may -/// block forever. -pub struct HostFuture { +/// Note that `FutureReader` instances must be disposed of using either +/// `FutureReader::read` or `DropWithStore::drop`; otherwise the in-store +/// representation will leak and the writer end will hang indefinitely. +/// Consider using [`WithAccessor`] to ensure that disposal happens +/// automatically. +pub struct FutureReader { instance: Instance, - rep: u32, + id: TableId, _phantom: PhantomData, } -impl HostFuture { - /// Create a new `HostFuture`. - fn new(rep: u32, instance: Instance) -> Self { +impl FutureReader { + fn new(id: TableId, instance: Instance) -> Self { Self { instance, - rep, + id, _phantom: PhantomData, } } - /// Convert this object into a [`FutureReader`]. - pub fn into_reader(self, mut store: impl AsContextMut) -> FutureReader + /// Read the value from this `future`. + /// + /// The returned `Future` will yield `Err` if the guest has trapped + /// before it could produce a result. + /// + /// The [`Accessor`] provided can be acquired from [`Instance::run_concurrent`] or + /// from within a host function for example. + /// + /// # Panics + /// + /// Panics if the store that the [`Accessor`] is derived from does not own + /// this future. + pub async fn read(self, accessor: impl AsAccessor) -> Option where - T: func::Lower + func::Lift + Send + Sync + 'static, + T: func::Lift + Send + 'static, { - FutureReader { - instance: self.instance, - rep: self.rep, - tx: Some(self.instance.start_read_event_loop( - store.as_context_mut(), - self.rep, - TransmitKind::Future, - )), + let accessor = accessor.as_accessor(); + + let result = self + .instance + .host_read_async(accessor, self.id, None, TransmitKind::Future) + .await; + + _ = accessor.with(|store| self.drop(store)); + + if let Ok(HostResult { + mut buffer, + dropped: false, + }) = result + { + buffer.take() + } else { + None } } + /// Wait for the write end of this `future` to be dropped. + /// + /// The [`Accessor`] provided can be acquired from + /// [`Instance::run_concurrent`] or from within a host function for example. + /// + /// # Panics + /// + /// Panics if the store that the [`Accessor`] is derived from does not own + /// this future. + pub async fn watch_writer(&mut self, accessor: impl AsAccessor) { + watch_writer(accessor, self.instance, self.id).await; + } + /// Convert this `FutureReader` into a [`Val`]. // See TODO comment for `FutureAny`; this is prone to handle leakage. pub fn into_val(self) -> Val { - Val::Future(FutureAny(self.rep)) + Val::Future(FutureAny(self.id.rep())) } /// Attempt to convert the specified [`Val`] to a `FutureReader`. @@ -630,10 +780,9 @@ impl HostFuture { bail!("expected `future`; got `{}`", value.desc()); }; let store = store.as_context_mut(); - instance - .concurrent_state_mut(store.0) - .get(TableId::::new(*rep))?; // Just make sure it's present - Ok(Self::new(*rep, instance)) + let id = TableId::::new(*rep); + instance.concurrent_state_mut(store.0).get(id)?; // Just make sure it's present + Ok(Self::new(id, instance)) } /// Transfer ownership of the read end of a future from a guest to the host. @@ -655,26 +804,36 @@ impl HostFuture { StreamFutureState::Busy => bail!("cannot transfer busy future"), } + let id = TableId::::new(rep); let concurrent_state = cx.instance_mut().concurrent_state_mut(); - let state = concurrent_state - .get(TableId::::new(rep))? - .state; + let state = concurrent_state.get(id)?.state; if concurrent_state.get(state)?.done { bail!("cannot lift future after previous read succeeded"); } - Ok(Self::new(rep, cx.instance_handle())) + Ok(Self::new(id, cx.instance_handle())) } _ => func::bad_type_info(), } } } -impl fmt::Debug for HostFuture { +impl DropWithStore for FutureReader { + fn drop(self, mut store: impl AsContextMut) -> Result<()> { + self.instance.host_drop_reader( + store.as_context_mut().0.traitobj_mut(), + self.id, + TransmitKind::Future, + ) + } +} + +impl fmt::Debug for FutureReader { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("HostFuture") - .field("rep", &self.rep) + f.debug_struct("FutureReader") + .field("id", &self.id) + .field("instance", &self.instance) .finish() } } @@ -706,7 +865,7 @@ pub(crate) fn lower_future_to_index( // SAFETY: This relies on the `ComponentType` implementation for `u32` being // safe and correct since we lift and lower future handles as `u32`s. -unsafe impl func::ComponentType for HostFuture { +unsafe impl func::ComponentType for FutureReader { const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4; type Lower = ::Lower; @@ -720,14 +879,18 @@ unsafe impl func::ComponentType for HostFuture { } // SAFETY: See the comment on the `ComponentType` `impl` for this type. -unsafe impl func::Lower for HostFuture { +unsafe impl func::Lower for FutureReader { fn linear_lower_to_flat( &self, cx: &mut LowerContext<'_, U>, ty: InterfaceType, dst: &mut MaybeUninit, ) -> Result<()> { - lower_future_to_index(self.rep, cx, ty)?.linear_lower_to_flat(cx, InterfaceType::U32, dst) + lower_future_to_index(self.id.rep(), cx, ty)?.linear_lower_to_flat( + cx, + InterfaceType::U32, + dst, + ) } fn linear_lower_to_memory( @@ -736,7 +899,7 @@ unsafe impl func::Lower for HostFuture { ty: InterfaceType, offset: usize, ) -> Result<()> { - lower_future_to_index(self.rep, cx, ty)?.linear_lower_to_memory( + lower_future_to_index(self.id.rep(), cx, ty)?.linear_lower_to_memory( cx, InterfaceType::U32, offset, @@ -745,7 +908,7 @@ unsafe impl func::Lower for HostFuture { } // SAFETY: See the comment on the `ComponentType` `impl` for this type. -unsafe impl func::Lift for HostFuture { +unsafe impl func::Lift for FutureReader { fn linear_lift_from_flat( cx: &mut LiftContext<'_>, ty: InterfaceType, @@ -765,117 +928,26 @@ unsafe impl func::Lift for HostFuture { } } -impl From> for HostFuture { - fn from(mut value: FutureReader) -> Self { - value.tx.take(); - - Self { - instance: value.instance, - rep: value.rep, - _phantom: PhantomData, - } - } -} - -/// Represents the readable end of a Component Model `future`. -/// -/// In order to pass this end to guest code, first convert it to a -/// [`HostFuture`] using the `into` method. -pub struct FutureReader { - instance: Instance, - rep: u32, - tx: Option>>>, -} - -impl FutureReader { - fn new(rep: u32, tx: Option>>>, instance: Instance) -> Self { - Self { instance, rep, tx } - } - - /// Read the value from this `future`. - /// - /// The returned `Future` will yield `None` if the guest has trapped - /// before it could produce a result. - /// - /// The [`Accessor`] provided can be acquired from [`Instance::run_concurrent`] or - /// from within a host function for example. - /// - /// # Panics - /// - /// Panics if the store that the [`Accessor`] is derived from does not own - /// this future. - pub async fn read(mut self, accessor: impl AsAccessor) -> Option - where - T: Send + 'static, - { - // FIXME: this is intended to be used in the future to directly - // manipulate state for this future within the store without having to - // go through an mpsc. - let _accessor = accessor.as_accessor(); - let (tx, rx) = oneshot::channel(); - send( - &mut self.tx.as_mut().unwrap(), - ReadEvent::Read { buffer: None, tx }, - ); - let v = rx.await; - drop(self); - - if let Ok(HostResult { - mut buffer, - dropped: false, - }) = v - { - buffer.take() - } else { - None - } - } - - /// Wait for the write end of this `future` to be dropped. - /// - /// The [`Accessor`] provided can be acquired from - /// [`Instance::run_concurrent`] or from within a host function for example. - /// - /// # Panics - /// - /// Panics if the store that the [`Accessor`] is derived from does not own - /// this future. - pub async fn watch_writer(&mut self, accessor: impl AsAccessor) - where - T: Send + 'static, - { - // FIXME: this is intended to be used in the future to directly - // manipulate state for this future within the store without having to - // go through an mpsc. - let _accessor = accessor.as_accessor(); - let (tx, rx) = oneshot::channel(); - send(&mut self.tx.as_mut().unwrap(), ReadEvent::Watch { tx }); - let (future, _watch) = watch(self.instance, rx, ()); - future.await - } -} - -impl Drop for FutureReader { - fn drop(&mut self) { - if let Some(mut tx) = self.tx.take() { - send(&mut tx, ReadEvent::Drop); - } - } -} - /// Represents the writable end of a Component Model `stream`. -pub struct StreamWriter { +/// +/// Note that `StreamWriter` instances must be disposed of using +/// `DropWithStore::drop`; otherwise the in-store representation will leak and +/// the reader end will hang indefinitely. Consider using [`WithAccessor`] to +/// ensure that disposal happens automatically. +pub struct StreamWriter { instance: Instance, + id: TableId, closed: bool, - tx: Option>>, + _phantom: PhantomData, } -impl StreamWriter { - fn new(tx: Option>>, instance: Instance) -> Self { +impl StreamWriter { + fn new(id: TableId, instance: Instance) -> Self { Self { instance, - tx, + id, closed: false, + _phantom: PhantomData, } } @@ -902,18 +974,22 @@ impl StreamWriter { /// /// Panics if the store that the [`Accessor`] is derived from does not own /// this future. - pub async fn write(&mut self, accessor: impl AsAccessor, buffer: B) -> B + pub async fn write(&mut self, accessor: impl AsAccessor, buffer: B) -> B where - B: Send + 'static, + T: func::Lower + 'static, + B: WriteBuffer, { - // FIXME: this is intended to be used in the future to directly - // manipulate state for this future within the store without having to - // go through an mpsc. - let _accessor = accessor.as_accessor(); - let (tx, rx) = oneshot::channel(); - send(self.tx.as_mut().unwrap(), WriteEvent::Write { buffer, tx }); - let v = rx.await; - match v { + let result = self + .instance + .host_write_async( + accessor.as_accessor(), + self.id, + buffer, + TransmitKind::Stream, + ) + .await; + + match result { Ok(HostResult { buffer, dropped }) => { if self.closed { debug_assert!(dropped); @@ -921,7 +997,7 @@ impl StreamWriter { self.closed = dropped; buffer } - Err(_) => todo!("guarantee buffer recovery if event loop errors or panics"), + Err(_) => todo!("guarantee buffer recovery if `host_write` fails"), } } @@ -937,8 +1013,9 @@ impl StreamWriter { /// /// Panics if the store that the [`Accessor`] is derived from does not own /// this future. - pub async fn write_all(&mut self, accessor: impl AsAccessor, mut buffer: B) -> B + pub async fn write_all(&mut self, accessor: impl AsAccessor, mut buffer: B) -> B where + T: func::Lower + 'static, B: WriteBuffer, { let accessor = accessor.as_accessor(); @@ -954,78 +1031,108 @@ impl StreamWriter { /// /// Panics if the store that the [`Accessor`] is derived from does not own /// this future. - pub async fn watch_reader(&mut self, accessor: impl AsAccessor) - where - B: Send + 'static, - { - // FIXME: this is intended to be used in the future to directly - // manipulate state for this future within the store without having to - // go through an mpsc. - let _accessor = accessor.as_accessor(); - let (tx, rx) = oneshot::channel(); - send(&mut self.tx.as_mut().unwrap(), WriteEvent::Watch { tx }); - let (future, _watch) = watch(self.instance, rx, ()); - future.await; + pub async fn watch_reader(&mut self, accessor: impl AsAccessor) { + watch_reader(accessor, self.instance, self.id).await } } -impl Drop for StreamWriter { - fn drop(&mut self) { - if let Some(mut tx) = self.tx.take() { - send(&mut tx, WriteEvent::Drop(None)); - } +impl DropWithStore for StreamWriter { + fn drop(self, mut store: impl AsContextMut) -> Result<()> { + self.instance.host_drop_writer( + store.as_context_mut().0.traitobj_mut(), + self.id, + TransmitKind::Stream, + ) } } /// Represents the readable end of a Component Model `stream`. /// -/// In order to actually read from or drop this `stream`, first convert it to a -/// [`FutureReader`] using the `into_reader` method. -/// -/// Note that if a value of this type is dropped without either being converted -/// to a `StreamReader` or passed to the guest, any writes on the write end may -/// block forever. -pub struct HostStream { +/// Note that `StreamReader` instances must be disposed of using +/// `DropWithStore::drop`; otherwise the in-store representation will leak and +/// the writer end will hang indefinitely. Consider using [`WithAccessor`] to +/// ensure that disposal happens automatically. +pub struct StreamReader { instance: Instance, - rep: u32, + id: TableId, + closed: bool, _phantom: PhantomData, } -impl HostStream { - /// Create a new `HostStream`. - fn new(rep: u32, instance: Instance) -> Self { +impl StreamReader { + fn new(id: TableId, instance: Instance) -> Self { Self { instance, - rep, + id, + closed: false, _phantom: PhantomData, } } - /// Convert this object into a [`StreamReader`]. - pub fn into_reader(self, mut store: impl AsContextMut) -> StreamReader - where - T: func::Lower + func::Lift + Send + 'static, - B: ReadBuffer, - { - StreamReader { - instance: self.instance, - rep: self.rep, - tx: Some(self.instance.start_read_event_loop( - store.as_context_mut(), - self.rep, - TransmitKind::Stream, - )), - closed: false, - } + /// Returns whether this stream is "closed" meaning that the other end of + /// the stream has been dropped. + pub fn is_closed(&self) -> bool { + self.closed + } + + /// Read values from this `stream`. + /// + /// The returned `Future` will yield a `(Some(_), _)` if the read completed + /// (possibly with zero items if the write was empty). It will return + /// `(None, _)` if the read failed due to the closure of the write end. In + /// either case, the returned buffer will be the same one passed as a + /// parameter, with zero or more items added. + /// + /// # Panics + /// + /// Panics if the store that the [`Accessor`] is derived from does not own + /// this future. + pub async fn read(&mut self, accessor: impl AsAccessor, buffer: B) -> B + where + T: func::Lift + 'static, + B: ReadBuffer + Send + 'static, + { + let result = self + .instance + .host_read_async( + accessor.as_accessor(), + self.id, + buffer, + TransmitKind::Stream, + ) + .await; + + match result { + Ok(HostResult { buffer, dropped }) => { + if self.closed { + debug_assert!(dropped); + } + self.closed = dropped; + buffer + } + Err(_) => { + todo!("guarantee buffer recovery if `host_read` fails") + } + } + } + + /// Wait until the write end of this `stream` is dropped. + /// + /// # Panics + /// + /// Panics if the store that the [`Accessor`] is derived from does not own + /// this future. + pub async fn watch_writer(&mut self, accessor: impl AsAccessor) { + watch_writer(accessor, self.instance, self.id).await } - /// Convert this `HostStream` into a [`Val`]. + /// Convert this `StreamReader` into a [`Val`]. // See TODO comment for `StreamAny`; this is prone to handle leakage. pub fn into_val(self) -> Val { - Val::Stream(StreamAny(self.rep)) + Val::Stream(StreamAny(self.id.rep())) } - /// Attempt to convert the specified [`Val`] to a `HostStream`. + /// Attempt to convert the specified [`Val`] to a `StreamReader`. pub fn from_val( mut store: impl AsContextMut, instance: Instance, @@ -1035,10 +1142,9 @@ impl HostStream { bail!("expected `stream`; got `{}`", value.desc()); }; let store = store.as_context_mut(); - instance - .concurrent_state_mut(store.0) - .get(TableId::::new(*rep))?; // Just make sure it's present - Ok(Self::new(*rep, instance)) + let id = TableId::::new(*rep); + instance.concurrent_state_mut(store.0).get(id)?; // Just make sure it's present + Ok(Self::new(id, instance)) } /// Transfer ownership of the read end of a stream from a guest to the host. @@ -1063,17 +1169,30 @@ impl HostStream { StreamFutureState::Busy => bail!("cannot transfer busy stream"), } - Ok(Self::new(rep, cx.instance_handle())) + let id = TableId::::new(rep); + + Ok(Self::new(id, cx.instance_handle())) } _ => func::bad_type_info(), } } } -impl fmt::Debug for HostStream { +impl DropWithStore for StreamReader { + fn drop(self, mut store: impl AsContextMut) -> Result<()> { + self.instance.host_drop_reader( + store.as_context_mut().0.traitobj_mut(), + self.id, + TransmitKind::Stream, + ) + } +} + +impl fmt::Debug for StreamReader { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("HostStream") - .field("rep", &self.rep) + f.debug_struct("StreamReader") + .field("id", &self.id) + .field("instance", &self.instance) .finish() } } @@ -1105,7 +1224,7 @@ pub(crate) fn lower_stream_to_index( // SAFETY: This relies on the `ComponentType` implementation for `u32` being // safe and correct since we lift and lower stream handles as `u32`s. -unsafe impl func::ComponentType for HostStream { +unsafe impl func::ComponentType for StreamReader { const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4; type Lower = ::Lower; @@ -1119,14 +1238,18 @@ unsafe impl func::ComponentType for HostStream { } // SAFETY: See the comment on the `ComponentType` `impl` for this type. -unsafe impl func::Lower for HostStream { +unsafe impl func::Lower for StreamReader { fn linear_lower_to_flat( &self, cx: &mut LowerContext<'_, U>, ty: InterfaceType, dst: &mut MaybeUninit, ) -> Result<()> { - lower_stream_to_index(self.rep, cx, ty)?.linear_lower_to_flat(cx, InterfaceType::U32, dst) + lower_stream_to_index(self.id.rep(), cx, ty)?.linear_lower_to_flat( + cx, + InterfaceType::U32, + dst, + ) } fn linear_lower_to_memory( @@ -1135,7 +1258,7 @@ unsafe impl func::Lower for HostStream { ty: InterfaceType, offset: usize, ) -> Result<()> { - lower_stream_to_index(self.rep, cx, ty)?.linear_lower_to_memory( + lower_stream_to_index(self.id.rep(), cx, ty)?.linear_lower_to_memory( cx, InterfaceType::U32, offset, @@ -1144,7 +1267,7 @@ unsafe impl func::Lower for HostStream { } // SAFETY: See the comment on the `ComponentType` `impl` for this type. -unsafe impl func::Lift for HostStream { +unsafe impl func::Lift for StreamReader { fn linear_lift_from_flat( cx: &mut LiftContext<'_>, ty: InterfaceType, @@ -1164,111 +1287,6 @@ unsafe impl func::Lift for HostStream { } } -impl From> for HostStream { - fn from(mut value: StreamReader) -> Self { - value.tx.take(); - - Self { - instance: value.instance, - rep: value.rep, - _phantom: PhantomData, - } - } -} - -/// Represents the readable end of a Component Model `stream`. -/// -/// In order to pass this end to guest code, first convert it to a -/// [`HostStream`] using the `into` method. -pub struct StreamReader { - instance: Instance, - rep: u32, - tx: Option>>, - closed: bool, -} - -impl StreamReader { - fn new(rep: u32, tx: Option>>, instance: Instance) -> Self { - Self { - instance, - rep, - tx, - closed: false, - } - } - - /// Returns whether this stream is "closed" meaning that the other end of - /// the stream has been dropped. - pub fn is_closed(&self) -> bool { - self.closed - } - - /// Read values from this `stream`. - /// - /// The returned `Future` will yield a `(Some(_), _)` if the read completed - /// (possibly with zero items if the write was empty). It will return - /// `(None, _)` if the read failed due to the closure of the write end. In - /// either case, the returned buffer will be the same one passed as a - /// parameter, with zero or more items added. - /// - /// # Panics - /// - /// Panics if the store that the [`Accessor`] is derived from does not own - /// this future. - pub async fn read(&mut self, accessor: impl AsAccessor, buffer: B) -> B - where - B: Send + 'static, - { - // FIXME: this is intended to be used in the future to directly - // manipulate state for this future within the store without having to - // go through an mpsc. - let _accessor = accessor.as_accessor(); - let (tx, rx) = oneshot::channel(); - send(self.tx.as_mut().unwrap(), ReadEvent::Read { buffer, tx }); - let v = rx.await; - match v { - Ok(HostResult { buffer, dropped }) => { - if self.closed { - debug_assert!(dropped); - } - self.closed = dropped; - buffer - } - Err(_) => { - todo!("guarantee buffer recovery if event loop errors or panics") - } - } - } - - /// Wait until the write end of this `stream` is dropped. - /// - /// # Panics - /// - /// Panics if the store that the [`Accessor`] is derived from does not own - /// this future. - pub async fn watch_writer(&mut self, accessor: impl AsAccessor) - where - B: Send + 'static, - { - // FIXME: this is intended to be used in the future to directly - // manipulate state for this future within the store without having to - // go through an mpsc. - let _accessor = accessor.as_accessor(); - let (tx, rx) = oneshot::channel(); - send(&mut self.tx.as_mut().unwrap(), ReadEvent::Watch { tx }); - let (future, _) = watch(self.instance, rx, ()); - future.await - } -} - -impl Drop for StreamReader { - fn drop(&mut self) { - if let Some(mut tx) = self.tx.take() { - send(&mut tx, ReadEvent::Drop); - } - } -} - /// Represents a Component Model `error-context`. pub struct ErrorContext { rep: u32, @@ -1564,7 +1582,7 @@ enum Reader<'a> { }, /// The read end is owned by the host. Host { - accept: Box usize>, + accept: Box usize + 'a>, }, /// The read end has been dropped. End, @@ -1573,254 +1591,49 @@ enum Reader<'a> { impl Instance { /// Create a new Component Model `future` as pair of writable and readable ends, /// the latter of which may be passed to guest code. - /// - /// The `default` parameter will be used if the returned `FutureWriter` is - /// dropped before `FutureWriter::write` is called. Since the write end of - /// a Component Model `future` must be written to before it is dropped, and - /// since Rust does not currently provide a way to statically enforce that - /// (e.g. linear typing), we use this mechanism to ensure a value is always - /// written prior to closing. - /// - /// If there's no plausible default value, and you're sure - /// `FutureWriter::write` will be called, you can consider passing `|| - /// unreachable!()` as the `default` parameter. pub fn future( self, - default: fn() -> T, mut store: impl AsContextMut, ) -> Result<(FutureWriter, FutureReader)> { - let mut store = store.as_context_mut(); - let (write, read) = self.concurrent_state_mut(store.0).new_transmit()?; + let (write, read) = self + .concurrent_state_mut(store.as_context_mut().0) + .new_transmit()?; Ok(( - FutureWriter::new( - default, - Some(self.start_write_event_loop( - store.as_context_mut(), - write.rep(), - TransmitKind::Future, - )), - self, - ), - FutureReader::new( - read.rep(), - Some(self.start_read_event_loop( - store.as_context_mut(), - read.rep(), - TransmitKind::Future, - )), - self, - ), + FutureWriter::new(write, self), + FutureReader::new(read, self), )) } /// Create a new Component Model `stream` as pair of writable and readable ends, /// the latter of which may be passed to guest code. - pub fn stream< - T: func::Lower + func::Lift + Send + 'static, - W: WriteBuffer, - R: ReadBuffer, - >( + pub fn stream( self, mut store: impl AsContextMut, - ) -> Result<(StreamWriter, StreamReader)> { - let mut store = store.as_context_mut(); - let (write, read) = self.concurrent_state_mut(store.0).new_transmit()?; + ) -> Result<(StreamWriter, StreamReader)> { + let (write, read) = self + .concurrent_state_mut(store.as_context_mut().0) + .new_transmit()?; Ok(( - StreamWriter::new( - Some(self.start_write_event_loop( - store.as_context_mut(), - write.rep(), - TransmitKind::Stream, - )), - self, - ), - StreamReader::new( - read.rep(), - Some(self.start_read_event_loop( - store.as_context_mut(), - read.rep(), - TransmitKind::Stream, - )), - self, - ), + StreamWriter::new(write, self), + StreamReader::new(read, self), )) } - /// Spawn a background task to be polled in this instance's event loop. - /// - /// The spawned task will accept host events from the `Receiver` corresponding to - /// the returned `Sender`, handling each event it receives and then exiting - /// when the channel is dropped. - /// - /// We handle `StreamWriter` and `FutureWriter` operations this way so that - /// they can be initiated without access to the store and possibly outside - /// the instance's event loop, improving the ergonmics for host embedders. - fn start_write_event_loop< - T: func::Lower + func::Lift + Send + 'static, - B: WriteBuffer, - U, - >( - self, - mut store: StoreContextMut, - rep: u32, - kind: TransmitKind, - ) -> mpsc::Sender> { - let (tx, mut rx) = mpsc::channel(1); - let id = TableId::::new(rep); - let run_on_drop = - RunOnDrop::new(move || log::trace!("write event loop for {id:?} dropped")); - let token = StoreToken::new(store.as_context_mut()); - let task = Box::pin( - async move { - log::trace!("write event loop for {id:?} started"); - let mut my_rep = None; - while let Some(event) = rx.next().await { - if my_rep.is_none() { - my_rep = Some(self.get_state_rep(rep)?); - } - let rep = my_rep.unwrap(); - match event { - WriteEvent::Write { buffer, tx } => tls::get(|store| { - self.host_write::<_, _, U>( - token.as_context_mut(store), - rep, - buffer, - PostWrite::Continue, - tx, - kind, - ) - })?, - WriteEvent::Drop(default) => tls::get(|store| { - if let Some(default) = default { - self.host_write::<_, _, U>( - token.as_context_mut(store), - rep, - default(), - PostWrite::Continue, - oneshot::channel().0, - kind, - )?; - } - self.concurrent_state_mut(store).host_drop_writer(rep, kind) - })?, - WriteEvent::Watch { tx } => tls::get(|store| { - let state = - self.concurrent_state_mut(store) - .get_mut(TableId::::new(rep))?; - if !matches!(&state.read, ReadState::Dropped) { - state.reader_watcher = Some(tx); - } - Ok::<_, anyhow::Error>(()) - })?, - } - } - Ok(()) - } - .map(move |v| { - run_on_drop.cancel(); - log::trace!("write event loop for {id:?} finished: {v:?}"); - HostTaskOutput::Result(v) - }), - ); - self.concurrent_state_mut(store.0).push_future(task); - tx - } - - /// Same as `Self::start_write_event_loop`, but for the read end of a stream - /// or future. - fn start_read_event_loop, U>( - self, - mut store: StoreContextMut, - rep: u32, - kind: TransmitKind, - ) -> mpsc::Sender> { - let (tx, mut rx) = mpsc::channel(1); - let id = TableId::::new(rep); - let run_on_drop = RunOnDrop::new(move || log::trace!("read event loop for {id:?} dropped")); - let token = StoreToken::new(store.as_context_mut()); - let task = Box::pin( - async move { - log::trace!("read event loop for {id:?} started"); - let mut my_rep = None; - while let Some(event) = rx.next().await { - if my_rep.is_none() { - my_rep = Some(self.get_state_rep(rep)?); - } - let rep = my_rep.unwrap(); - match event { - ReadEvent::Read { buffer, tx } => tls::get(|store| { - self.host_read::<_, _, U>( - token.as_context_mut(store), - rep, - buffer, - tx, - kind, - ) - })?, - ReadEvent::Drop => { - tls::get(|store| self.host_drop_reader(store, rep, kind))? - } - ReadEvent::Watch { tx } => tls::get(|store| { - let state = - self.concurrent_state_mut(store) - .get_mut(TableId::::new(rep))?; - if !matches!( - &state.write, - WriteState::Dropped - | WriteState::GuestReady { - post_write: PostWrite::Drop, - .. - } - | WriteState::HostReady { - post_write: PostWrite::Drop, - .. - } - ) { - state.writer_watcher = Some(tx); - } - Ok::<_, anyhow::Error>(()) - })?, - } - } - Ok(()) - } - .map(move |v| { - run_on_drop.cancel(); - log::trace!("read event loop for {id:?} finished: {v:?}"); - HostTaskOutput::Result(v) - }), - ); - self.concurrent_state_mut(store.0).push_future(task); - tx - } - /// Write to the specified stream or future from the host. - /// - /// # Arguments - /// - /// * `store` - The store to which this instance belongs - /// * `transmit_rep` - The `TransmitState` rep for the stream or future - /// * `buffer` - Buffer of values that should be written - /// * `post_write` - Whether the transmit should be dropped after write, possibly with an error context - /// * `tx` - Oneshot channel to notify when operation completes (or drop on error) - /// * `kind` - whether this is a stream or a future fn host_write, U>( self, mut store: StoreContextMut, - transmit_rep: u32, + id: TableId, mut buffer: B, - mut post_write: PostWrite, - tx: oneshot::Sender>, kind: TransmitKind, - ) -> Result<()> { - let mut store = store.as_context_mut(); - let transmit_id = TableId::::new(transmit_rep); + ) -> Result, oneshot::Receiver>>> { + let transmit_id = self.concurrent_state_mut(store.0).get(id)?.state; let transmit = self .concurrent_state_mut(store.0) .get_mut(transmit_id) - .with_context(|| format!("retrieving state for transmit [{transmit_rep}]"))?; + .with_context(|| format!("retrieving state for transmit [{transmit_id:?}]"))?; log::trace!("host_write state {transmit_id:?}; {:?}", transmit.read); let new_state = if let ReadState::Dropped = &transmit.read { @@ -1829,23 +1642,31 @@ impl Instance { ReadState::Open }; - match mem::replace(&mut transmit.read, new_state) { + Ok(match mem::replace(&mut transmit.read, new_state) { ReadState::Open => { assert!(matches!(&transmit.write, WriteState::Open)); + let token = StoreToken::new(store.as_context_mut()); + let (tx, rx) = oneshot::channel(); let state = WriteState::HostReady { - accept: Box::new(accept_reader::( - store.as_context_mut(), - buffer, - tx, - kind, - )), - post_write, + accept: Box::new(move |store, instance, reader| { + let (result, code) = accept_reader::( + token.as_context_mut(store), + instance, + reader, + buffer, + kind, + )?; + _ = tx.send(result); + Ok(code) + }), + post_write: PostWrite::Continue, }; self.concurrent_state_mut(store.0) .get_mut(transmit_id)? .write = state; - post_write = PostWrite::Continue; + + Err(rx) } ReadState::GuestReady { @@ -1862,8 +1683,8 @@ impl Instance { } let read_handle = transmit.read_handle; - let code = accept_reader::(store.as_context_mut(), buffer, tx, kind)( - store.0.traitobj_mut(), + let (result, code) = accept_reader::( + store.as_context_mut(), self, Reader::Guest { options: &options, @@ -1871,6 +1692,8 @@ impl Instance { address, count, }, + buffer, + kind, )?; self.concurrent_state_mut(store.0).set_event( @@ -1886,6 +1709,8 @@ impl Instance { }, }, )?; + + Ok(result) } ReadState::HostReady { accept } => { @@ -1899,51 +1724,49 @@ impl Instance { unreachable!() }; - _ = tx.send(HostResult { + Ok(HostResult { buffer, dropped: false, - }); + }) } - ReadState::Dropped => { - _ = tx.send(HostResult { - buffer, - dropped: true, - }); - } - } + ReadState::Dropped => Ok(HostResult { + buffer, + dropped: true, + }), + }) + } - if let PostWrite::Drop = post_write { - self.concurrent_state_mut(store.0) - .host_drop_writer(transmit_rep, kind)?; + /// Async wrapper around `Self::host_write`. + async fn host_write_async>( + self, + accessor: impl AsAccessor, + id: TableId, + buffer: B, + kind: TransmitKind, + ) -> Result> { + match accessor + .as_accessor() + .with(move |mut access| self.host_write(access.as_context_mut(), id, buffer, kind))? + { + Ok(result) => Ok(result), + Err(rx) => Ok(rx.await?), } - - Ok(()) } /// Read from the specified stream or future from the host. - /// - /// # Arguments - /// - /// * `store` - The store to which this instance belongs - /// * `rep` - The `TransmitState` rep for the stream or future - /// * `buffer` - Buffer to receive values - /// * `tx` - Oneshot channel to notify when operation completes (or drop on error) - /// * `kind` - whether this is a stream or a future fn host_read, U>( self, - mut store: StoreContextMut, - rep: u32, + store: StoreContextMut, + id: TableId, mut buffer: B, - tx: oneshot::Sender>, kind: TransmitKind, - ) -> Result<()> { - let store = store.as_context_mut(); - let transmit_id = TableId::::new(rep); + ) -> Result, oneshot::Receiver>>> { + let transmit_id = self.concurrent_state_mut(store.0).get(id)?.state; let transmit = self .concurrent_state_mut(store.0) .get_mut(transmit_id) - .with_context(|| rep.to_string())?; + .with_context(|| format!("retrieving state for transmit [{transmit_id:?}]"))?; log::trace!("host_read state {transmit_id:?}; {:?}", transmit.write); let new_state = if let WriteState::Dropped = &transmit.write { @@ -1952,13 +1775,20 @@ impl Instance { WriteState::Open }; - match mem::replace(&mut transmit.write, new_state) { + Ok(match mem::replace(&mut transmit.write, new_state) { WriteState::Open => { assert!(matches!(&transmit.read, ReadState::Open)); + let (tx, rx) = oneshot::channel(); transmit.read = ReadState::HostReady { - accept: Box::new(accept_writer::(buffer, tx, kind)), + accept: Box::new(move |writer| { + let (result, code) = accept_writer::(writer, buffer, kind)?; + _ = tx.send(result); + Ok(code) + }), }; + + Err(rx) } WriteState::GuestReady { @@ -1977,12 +1807,16 @@ impl Instance { let write_handle = transmit.write_handle; let lift = &mut LiftContext::new(store.0.store_opaque_mut(), &options, self); - let code = accept_writer::(buffer, tx, kind)(Writer::Guest { - ty: payload(ty, lift.types), - lift, - address, - count, - })?; + let (result, code) = accept_writer::( + Writer::Guest { + ty: payload(ty, lift.types), + lift, + address, + count, + }, + buffer, + kind, + )?; let state = self.concurrent_state_mut(store.0); let pending = if let PostWrite::Drop = post_write { @@ -2005,6 +1839,8 @@ impl Instance { }, }, )?; + + Ok(result) } WriteState::HostReady { accept, post_write } => { @@ -2012,13 +1848,9 @@ impl Instance { store.0.traitobj_mut(), self, Reader::Host { - accept: Box::new(move |input, count| { + accept: Box::new(|input, count| { let count = count.min(buffer.remaining_capacity()); buffer.move_from(input.get_mut::(), count); - _ = tx.send(HostResult { - buffer, - dropped: false, - }); count }), }, @@ -2029,36 +1861,49 @@ impl Instance { .get_mut(transmit_id)? .write = WriteState::Dropped; } - } - WriteState::Dropped => { - _ = tx.send(HostResult { + Ok(HostResult { buffer, - dropped: true, - }); + dropped: false, + }) } - } - Ok(()) + WriteState::Dropped => Ok(HostResult { + buffer, + dropped: true, + }), + }) + } + + /// Async wrapper around `Self::host_read`. + async fn host_read_async>( + self, + accessor: impl AsAccessor, + id: TableId, + buffer: B, + kind: TransmitKind, + ) -> Result> { + match accessor + .as_accessor() + .with(move |mut access| self.host_read(access.as_context_mut(), id, buffer, kind))? + { + Ok(result) => Ok(result), + Err(rx) => Ok(rx.await?), + } } /// Drop the read end of a stream or future read from the host. - /// - /// # Arguments - /// - /// * `store` - The store to which this instance belongs - /// * `transmit_rep` - The `TransmitState` rep for the stream or future. fn host_drop_reader( self, store: &mut dyn VMStore, - transmit_rep: u32, + id: TableId, kind: TransmitKind, ) -> Result<()> { - let transmit_id = TableId::::new(transmit_rep); + let transmit_id = self.concurrent_state_mut(store).get(id)?.state; let state = self.concurrent_state_mut(store); let transmit = state .get_mut(transmit_id) - .with_context(|| format!("error closing reader {transmit_rep}"))?; + .with_context(|| format!("error closing reader {transmit_id:?}"))?; log::trace!( "host_drop_reader state {transmit_id:?}; read state {:?} write state {:?}", transmit.read, @@ -2102,23 +1947,130 @@ impl Instance { pending: Some((ty, handle)), }, }, - )?; - }; + )?; + }; + } + + WriteState::HostReady { accept, .. } => { + accept(store, self, Reader::End)?; + } + + WriteState::Open => { + state.update_event( + write_handle.rep(), + match kind { + TransmitKind::Future => Event::FutureWrite { + code: ReturnCode::Dropped(0), + pending: None, + }, + TransmitKind::Stream => Event::StreamWrite { + code: ReturnCode::Dropped(0), + pending: None, + }, + }, + )?; + } + + WriteState::Dropped => { + log::trace!("host_drop_reader delete {transmit_id:?}"); + state.delete_transmit(transmit_id)?; + } + } + Ok(()) + } + + /// Drop the write end of a stream or future read from the host. + fn host_drop_writer( + self, + store: &mut dyn VMStore, + id: TableId, + kind: TransmitKind, + ) -> Result<()> { + let transmit_id = self.concurrent_state_mut(store).get(id)?.state; + let transmit = self + .concurrent_state_mut(store) + .get_mut(transmit_id) + .with_context(|| format!("error closing writer {transmit_id:?}"))?; + log::trace!( + "host_drop_writer state {transmit_id:?}; write state {:?} read state {:?}", + transmit.read, + transmit.write + ); + + transmit.writer_watcher = None; + + // Existing queued transmits must be updated with information for the impending writer closure + match &mut transmit.write { + WriteState::GuestReady { post_write, .. } => { + *post_write = PostWrite::Drop; + } + WriteState::HostReady { post_write, .. } => { + *post_write = PostWrite::Drop; + } + v @ WriteState::Open => { + if let (TransmitKind::Future, false) = ( + kind, + transmit.done || matches!(transmit.read, ReadState::Dropped), + ) { + bail!("cannot drop future write end without first writing a value") + } + + *v = WriteState::Dropped; + } + WriteState::Dropped => unreachable!("write state is already dropped"), + } + + // If the existing read state is dropped, then there's nothing to read + // and we can keep it that way. + // + // If the read state was any other state, then we must set the new state to open + // to indicate that there *is* data to be read + let new_state = if let ReadState::Dropped = &transmit.read { + ReadState::Dropped + } else { + ReadState::Open + }; + + let read_handle = transmit.read_handle; + + // Swap in the new read state + match mem::replace(&mut transmit.read, new_state) { + // If the guest was ready to read, then we cannot drop the reader (or writer) + // we must deliver the event, and update the state associated with the handle to + // represent that a read must be performed + ReadState::GuestReady { ty, handle, .. } => { + // Ensure the final read of the guest is queued, with appropriate closure indicator + self.concurrent_state_mut(store).update_event( + read_handle.rep(), + match ty { + TableIndex::Future(ty) => Event::FutureRead { + code: ReturnCode::Dropped(0), + pending: Some((ty, handle)), + }, + TableIndex::Stream(ty) => Event::StreamRead { + code: ReturnCode::Dropped(0), + pending: Some((ty, handle)), + }, + }, + )?; } - WriteState::HostReady { accept, .. } => { - accept(store, self, Reader::End)?; + // If the host was ready to read, and the writer end is being dropped (host->host write?) + // signal to the reader that we've reached the end of the stream + ReadState::HostReady { accept } => { + accept(Writer::End)?; } - WriteState::Open => { - state.update_event( - write_handle.rep(), + // If the read state is open, then there are no registered readers of the stream/future + ReadState::Open => { + self.concurrent_state_mut(store).update_event( + read_handle.rep(), match kind { - TransmitKind::Future => Event::FutureWrite { + TransmitKind::Future => Event::FutureRead { code: ReturnCode::Dropped(0), pending: None, }, - TransmitKind::Stream => Event::StreamWrite { + TransmitKind::Stream => Event::StreamRead { code: ReturnCode::Dropped(0), pending: None, }, @@ -2126,14 +2078,69 @@ impl Instance { )?; } - WriteState::Dropped => { - log::trace!("host_drop_reader delete {transmit_rep}"); - state.delete_transmit(transmit_id)?; + // If the read state was already dropped, then we can remove the transmit state completely + // (both writer and reader have been dropped) + ReadState::Dropped => { + log::trace!("host_drop_writer delete {transmit_id:?}"); + self.concurrent_state_mut(store) + .delete_transmit(transmit_id)?; } } Ok(()) } + /// Drop the writable end of the specified stream or future from the guest. + fn guest_drop_writable( + self, + store: &mut dyn VMStore, + ty: TableIndex, + writer: u32, + ) -> Result<()> { + let (transmit_rep, state) = self + .concurrent_state_mut(store) + .state_table(ty) + .remove_by_index(writer) + .context("failed to find writer")?; + let (state, kind) = match state { + WaitableState::Stream(_, state) => (state, TransmitKind::Stream), + WaitableState::Future(_, state) => (state, TransmitKind::Future), + _ => { + bail!("invalid stream or future handle"); + } + }; + match state { + StreamFutureState::Write { .. } => {} + StreamFutureState::Read { .. } => { + bail!("passed read end to `{{stream|future}}.drop-writable`") + } + StreamFutureState::Busy => bail!("cannot drop busy stream or future"), + } + + let id = TableId::::new(transmit_rep); + log::trace!("guest_drop_writable: drop writer {id:?}"); + self.host_drop_writer(store, id, kind) + } + + /// Implements the `future.drop-writable` intrinsic. + pub(crate) fn future_drop_writable( + self, + store: &mut dyn VMStore, + ty: TypeFutureTableIndex, + writer: u32, + ) -> Result<()> { + self.guest_drop_writable(store, TableIndex::Future(ty), writer) + } + + /// Implements the `stream.drop-writable` intrinsic. + pub(crate) fn stream_drop_writable( + self, + store: &mut dyn VMStore, + ty: TypeStreamTableIndex, + writer: u32, + ) -> Result<()> { + self.guest_drop_writable(store, TableIndex::Stream(ty), writer) + } + /// Copy `count` items from `read_address` to `write_address` for the /// specified stream or future. fn copy( @@ -2761,9 +2768,8 @@ impl Instance { StreamFutureState::Busy => bail!("cannot drop busy stream or future"), } let id = TableId::::new(rep); - let rep = concurrent_state.get(id)?.state.rep(); log::trace!("guest_drop_readable: drop reader {id:?}"); - self.host_drop_reader(store, rep, kind) + self.host_drop_reader(store, id, kind) } /// Create a new error context for the given component. @@ -2916,40 +2922,6 @@ impl Instance { ) -> Result<()> { self.guest_drop_readable(store, TableIndex::Stream(ty), reader) } - - /// Retrieve the `TransmitState` rep for the specified `TransmitHandle` rep. - fn get_state_rep(&self, rep: u32) -> Result { - tls::get(|store| { - let transmit_handle = TableId::::new(rep); - Ok(self - .concurrent_state_mut(store) - .get(transmit_handle) - .with_context(|| format!("stream or future {transmit_handle:?} not found"))? - .state - .rep()) - }) - } -} - -/// Helper struct for running a closure on drop, e.g. for logging purposes. -struct RunOnDrop(Option); - -impl RunOnDrop { - fn new(fun: F) -> Self { - Self(Some(fun)) - } - - fn cancel(mut self) { - self.0 = None; - } -} - -impl Drop for RunOnDrop { - fn drop(&mut self) { - if let Some(fun) = self.0.take() { - fun(); - } - } } impl ConcurrentState { @@ -3173,113 +3145,6 @@ impl ConcurrentState { Ok(code) } - /// Drop the write end of a stream or future read from the host. - /// - /// # Arguments - /// - /// * `transmit_rep` - The `TransmitState` rep for the stream or future. - fn host_drop_writer(&mut self, transmit_rep: u32, kind: TransmitKind) -> Result<()> { - let transmit_id = TableId::::new(transmit_rep); - let transmit = self - .get_mut(transmit_id) - .with_context(|| format!("error closing writer {transmit_rep}"))?; - log::trace!( - "host_drop_writer state {transmit_id:?}; write state {:?} read state {:?}", - transmit.read, - transmit.write - ); - - transmit.writer_watcher = None; - - // Existing queued transmits must be updated with information for the impending writer closure - match &mut transmit.write { - WriteState::GuestReady { post_write, .. } => { - *post_write = PostWrite::Drop; - } - WriteState::HostReady { post_write, .. } => { - *post_write = PostWrite::Drop; - } - v @ WriteState::Open => { - if let (TransmitKind::Future, false) = ( - kind, - transmit.done || matches!(transmit.read, ReadState::Dropped), - ) { - bail!("cannot drop future write end without first writing a value") - } - - *v = WriteState::Dropped; - } - WriteState::Dropped => unreachable!("write state is already dropped"), - } - - // If the existing read state is dropped, then there's nothing to read - // and we can keep it that way. - // - // If the read state was any other state, then we must set the new state to open - // to indicate that there *is* data to be read - let new_state = if let ReadState::Dropped = &transmit.read { - ReadState::Dropped - } else { - ReadState::Open - }; - - let read_handle = transmit.read_handle; - - // Swap in the new read state - match mem::replace(&mut transmit.read, new_state) { - // If the guest was ready to read, then we cannot drop the reader (or writer) - // we must deliver the event, and update the state associated with the handle to - // represent that a read must be performed - ReadState::GuestReady { ty, handle, .. } => { - // Ensure the final read of the guest is queued, with appropriate closure indicator - self.update_event( - read_handle.rep(), - match ty { - TableIndex::Future(ty) => Event::FutureRead { - code: ReturnCode::Dropped(0), - pending: Some((ty, handle)), - }, - TableIndex::Stream(ty) => Event::StreamRead { - code: ReturnCode::Dropped(0), - pending: Some((ty, handle)), - }, - }, - )?; - } - - // If the host was ready to read, and the writer end is being dropped (host->host write?) - // signal to the reader that we've reached the end of the stream - ReadState::HostReady { accept } => { - accept(Writer::End)?; - } - - // If the read state is open, then there are no registered readers of the stream/future - ReadState::Open => { - self.update_event( - read_handle.rep(), - match kind { - TransmitKind::Future => Event::FutureRead { - code: ReturnCode::Dropped(0), - pending: None, - }, - TransmitKind::Stream => Event::StreamRead { - code: ReturnCode::Dropped(0), - pending: None, - }, - }, - )?; - } - - // If the read state was already dropped, then we can remove the transmit state completely - // (both writer and reader have been dropped) - ReadState::Dropped => { - log::trace!("host_drop_writer delete {transmit_rep}"); - self.delete_transmit(transmit_id)?; - } - } - Ok(()) - } - /// Cancel a pending write for the specified stream or future from the guest. fn guest_cancel_write( &mut self, @@ -3338,33 +3203,6 @@ impl ConcurrentState { self.host_cancel_read(rep) } - /// Drop the writable end of the specified stream or future from the guest. - fn guest_drop_writable(&mut self, ty: TableIndex, writer: u32) -> Result<()> { - let (transmit_rep, state) = self - .state_table(ty) - .remove_by_index(writer) - .context("failed to find writer")?; - let (state, kind) = match state { - WaitableState::Stream(_, state) => (state, TransmitKind::Stream), - WaitableState::Future(_, state) => (state, TransmitKind::Future), - _ => { - bail!("invalid stream or future handle"); - } - }; - match state { - StreamFutureState::Write { .. } => {} - StreamFutureState::Read { .. } => { - bail!("passed read end to `{{stream|future}}.drop-writable`") - } - StreamFutureState::Busy => bail!("cannot drop busy stream or future"), - } - - let id = TableId::::new(transmit_rep); - let transmit_rep = self.get(id)?.state.rep(); - log::trace!("guest_drop_writable: drop writer {id:?}"); - self.host_drop_writer(transmit_rep, kind) - } - /// Drop the specified error context. pub(crate) fn error_context_drop( &mut self, @@ -3485,15 +3323,6 @@ impl ConcurrentState { .map(|result| result.encode()) } - /// Implements the `future.drop-writable` intrinsic. - pub(crate) fn future_drop_writable( - &mut self, - ty: TypeFutureTableIndex, - writer: u32, - ) -> Result<()> { - self.guest_drop_writable(TableIndex::Future(ty), writer) - } - /// Implements the `stream.new` intrinsic. pub(crate) fn stream_new(&mut self, ty: TypeStreamTableIndex) -> Result { self.guest_new(TableIndex::Stream(ty)) @@ -3521,15 +3350,6 @@ impl ConcurrentState { .map(|result| result.encode()) } - /// Implements the `stream.drop-writable` intrinsic. - pub(crate) fn stream_drop_writable( - &mut self, - ty: TypeStreamTableIndex, - writer: u32, - ) -> Result<()> { - self.guest_drop_writable(TableIndex::Stream(ty), writer) - } - /// Transfer ownership of the specified future read end from one guest to /// another. pub(crate) fn future_transfer( diff --git a/crates/wasmtime/src/runtime/component/concurrent/tls.rs b/crates/wasmtime/src/runtime/component/concurrent/tls.rs index a8496a2b79..98b1674176 100644 --- a/crates/wasmtime/src/runtime/component/concurrent/tls.rs +++ b/crates/wasmtime/src/runtime/component/concurrent/tls.rs @@ -76,6 +76,14 @@ pub fn get(f: impl FnOnce(&mut dyn VMStore) -> R) -> R { }) } +/// Like `get` except skips running `f` if the thread-local state is not set. +pub fn maybe_get(f: impl FnOnce(&mut dyn VMStore) -> R) -> Option { + try_get(|val| match val { + TryGet::Some(store) => Some(f(store)), + TryGet::None | TryGet::Taken => None, + }) +} + #[cold] fn get_failed() -> ! { panic!( diff --git a/crates/wasmtime/src/runtime/component/concurrent_disabled.rs b/crates/wasmtime/src/runtime/component/concurrent_disabled.rs index 1a661dd4da..1866761807 100644 --- a/crates/wasmtime/src/runtime/component/concurrent_disabled.rs +++ b/crates/wasmtime/src/runtime/component/concurrent_disabled.rs @@ -80,12 +80,12 @@ impl ErrorContext { } } -pub struct HostStream

{ +pub struct StreamReader

{ uninhabited: Uninhabited, _phantom: PhantomData

, } -impl

HostStream

{ +impl

StreamReader

{ pub(crate) fn into_val(self) -> Val { match self.uninhabited {} } @@ -107,12 +107,12 @@ impl

HostStream

{ } } -pub struct HostFuture

{ +pub struct FutureReader

{ uninhabited: Uninhabited, _phantom: PhantomData

, } -impl

HostFuture

{ +impl

FutureReader

{ pub(crate) fn into_val(self) -> Val { match self.uninhabited {} } diff --git a/crates/wasmtime/src/runtime/component/linker.rs b/crates/wasmtime/src/runtime/component/linker.rs index 71bae43474..2f68d4b5e6 100644 --- a/crates/wasmtime/src/runtime/component/linker.rs +++ b/crates/wasmtime/src/runtime/component/linker.rs @@ -797,6 +797,59 @@ impl LinkerInstance<'_, T> { Ok(()) } + /// Identical to [`Self::resource`], except that it takes a concurrent destructor. + #[cfg(feature = "component-model-async")] + pub fn resource_concurrent(&mut self, name: &str, ty: ResourceType, dtor: F) -> Result<()> + where + T: Send + 'static, + F: Fn(&Accessor, u32) -> Pin> + Send + '_>> + + Send + + Sync + + 'static, + { + assert!( + self.engine.config().async_support, + "cannot use `resource_concurrent` without enabling async support in the config" + ); + // TODO: This isn't really concurrent -- it requires exclusive access to + // the store for the duration of the call, preventing guest code from + // running until it completes. We should make it concurrent and clean + // up the implementation to avoid using e.g. `Accessor::new` and + // `tls::set` directly. + let dtor = Arc::new(dtor); + let dtor = Arc::new(crate::func::HostFunc::wrap_inner( + &self.engine, + move |mut cx: crate::Caller<'_, T>, (param,): (u32,)| { + let dtor = dtor.clone(); + cx.as_context_mut().block_on(move |mut store| { + Box::pin(async move { + // NOTE: We currently pass `None` as the `instance` + // parameter to `Accessor::new` because we don't have ready + // access to it, meaning `dtor` will panic if it tries to + // use `Accessor::instance`. We could plumb that through + // from the `wasmtime-cranelift`-generated code, but we plan + // to remove `Accessor::instance` once all instances in a + // store share the same concurrent state, at which point we + // won't need it anyway. + let accessor = &Accessor::new( + crate::store::StoreToken::new(store.as_context_mut()), + None, + ); + let mut future = std::pin::pin!(dtor(accessor, param)); + std::future::poll_fn(|cx| { + crate::component::concurrent::tls::set(store.0.traitobj_mut(), || { + future.as_mut().poll(cx) + }) + }) + .await + }) + })? + }, + )); + self.insert(name, Definition::Resource(ty, dtor))?; + Ok(()) + } + /// Defines a nested instance within this instance. /// /// This can be used to describe arbitrarily nested levels of instances diff --git a/crates/wasmtime/src/runtime/component/mod.rs b/crates/wasmtime/src/runtime/component/mod.rs index 27f6945dd1..3af50f9376 100644 --- a/crates/wasmtime/src/runtime/component/mod.rs +++ b/crates/wasmtime/src/runtime/component/mod.rs @@ -119,9 +119,9 @@ mod values; pub use self::component::{Component, ComponentExportIndex}; #[cfg(feature = "component-model-async")] pub use self::concurrent::{ - AbortHandle, Access, Accessor, AccessorTask, AsAccessor, ErrorContext, FutureReader, - FutureWriter, HostFuture, HostStream, ReadBuffer, StreamReader, StreamWriter, - VMComponentAsyncStore, VecBuffer, Watch, WriteBuffer, + AbortHandle, Access, Accessor, AccessorTask, AsAccessor, DropWithStore, DropWithStoreAndValue, + ErrorContext, FutureReader, FutureWriter, ReadBuffer, StreamReader, StreamWriter, + VMComponentAsyncStore, VecBuffer, WithAccessor, WithAccessorAndValue, WriteBuffer, }; pub use self::func::{ ComponentNamedList, ComponentType, Func, Lift, Lower, TypedFunc, WasmList, WasmStr, diff --git a/crates/wasmtime/src/runtime/component/values.rs b/crates/wasmtime/src/runtime/component/values.rs index dbe129ca43..9436072806 100644 --- a/crates/wasmtime/src/runtime/component/values.rs +++ b/crates/wasmtime/src/runtime/component/values.rs @@ -1,6 +1,6 @@ use crate::ValRaw; use crate::component::ResourceAny; -use crate::component::concurrent::{self, ErrorContext, HostFuture, HostStream}; +use crate::component::concurrent::{self, ErrorContext, FutureReader, StreamReader}; use crate::component::func::{Lift, LiftContext, Lower, LowerContext, desc}; use crate::prelude::*; use core::mem::MaybeUninit; @@ -207,10 +207,10 @@ impl Val { Val::Flags(flags) } InterfaceType::Future(_) => { - HostFuture::<()>::linear_lift_from_flat(cx, ty, next(src))?.into_val() + FutureReader::<()>::linear_lift_from_flat(cx, ty, next(src))?.into_val() } InterfaceType::Stream(_) => { - HostStream::<()>::linear_lift_from_flat(cx, ty, next(src))?.into_val() + StreamReader::<()>::linear_lift_from_flat(cx, ty, next(src))?.into_val() } InterfaceType::ErrorContext(_) => { ErrorContext::linear_lift_from_flat(cx, ty, next(src))?.into_val() @@ -337,10 +337,10 @@ impl Val { Val::Flags(flags) } InterfaceType::Future(_) => { - HostFuture::<()>::linear_lift_from_memory(cx, ty, bytes)?.into_val() + FutureReader::<()>::linear_lift_from_memory(cx, ty, bytes)?.into_val() } InterfaceType::Stream(_) => { - HostStream::<()>::linear_lift_from_memory(cx, ty, bytes)?.into_val() + StreamReader::<()>::linear_lift_from_memory(cx, ty, bytes)?.into_val() } InterfaceType::ErrorContext(_) => { ErrorContext::linear_lift_from_memory(cx, ty, bytes)?.into_val() diff --git a/crates/wasmtime/src/runtime/vm/component/libcalls.rs b/crates/wasmtime/src/runtime/vm/component/libcalls.rs index 2dc9abef5d..5f8de8511d 100644 --- a/crates/wasmtime/src/runtime/vm/component/libcalls.rs +++ b/crates/wasmtime/src/runtime/vm/component/libcalls.rs @@ -1032,7 +1032,8 @@ fn future_drop_writable( ty: u32, writer: u32, ) -> Result<()> { - instance.concurrent_state_mut(store).future_drop_writable( + instance.future_drop_writable( + store, wasmtime_environ::component::TypeFutureTableIndex::from_u32(ty), writer, ) @@ -1148,7 +1149,8 @@ fn stream_drop_writable( ty: u32, writer: u32, ) -> Result<()> { - instance.concurrent_state_mut(store).stream_drop_writable( + instance.stream_drop_writable( + store, wasmtime_environ::component::TypeStreamTableIndex::from_u32(ty), writer, ) diff --git a/crates/wit-bindgen/src/lib.rs b/crates/wit-bindgen/src/lib.rs index 9093f1822e..ee9da684d2 100644 --- a/crates/wit-bindgen/src/lib.rs +++ b/crates/wit-bindgen/src/lib.rs @@ -1420,6 +1420,11 @@ impl Wasmtime { let key = resolve.name_world_key(key); return resolve.interfaces[id].functions.iter().any(|(name, _)| { self.opts.import_call_style(Some(&key), name) == CallStyle::Concurrent + }) || get_resources(resolve, id).any(|(_, name)| { + matches!( + self.opts.drop_call_style(Some(&key), name), + CallStyle::Concurrent + ) }); } unreachable!() @@ -1592,30 +1597,48 @@ impl Wasmtime { ) { let gate = FeatureGate::open(src, stability); let camel = name.to_upper_camel_case(); - if let CallStyle::Async | CallStyle::Concurrent = opts.drop_call_style(qualifier, name) { - uwriteln!( - src, - "{inst}.resource_async( - \"{name}\", - {wt}::component::ResourceType::host::<{camel}>(), - move |mut store, rep| {{ - {wt}::component::__internal::Box::new(async move {{ - Host{camel}::drop(&mut host_getter(store.data_mut()), {wt}::component::Resource::new_own(rep)).await - }}) - }}, - )?;" - ) - } else { - uwriteln!( - src, - "{inst}.resource( - \"{name}\", - {wt}::component::ResourceType::host::<{camel}>(), - move |mut store, rep| -> {wt}::Result<()> {{ - Host{camel}::drop(&mut host_getter(store.data_mut()), {wt}::component::Resource::new_own(rep)) - }}, - )?;" - ) + match opts.drop_call_style(qualifier, name) { + CallStyle::Concurrent => { + uwriteln!( + src, + "{inst}.resource_concurrent( + \"{name}\", + {wt}::component::ResourceType::host::<{camel}>(), + move |caller: &{wt}::component::Accessor::, rep| {{ + {wt}::component::__internal::Box::pin(async move {{ + let accessor = &caller.with_data(host_getter); + Host{camel}Concurrent::drop(accessor, {wt}::component::Resource::new_own(rep)).await + }}) + }}, + )?;" + ) + } + CallStyle::Async => { + uwriteln!( + src, + "{inst}.resource_async( + \"{name}\", + {wt}::component::ResourceType::host::<{camel}>(), + move |mut store, rep| {{ + {wt}::component::__internal::Box::new(async move {{ + Host{camel}::drop(&mut host_getter(store.data_mut()), {wt}::component::Resource::new_own(rep)).await + }}) + }}, + )?;" + ) + } + CallStyle::Sync => { + uwriteln!( + src, + "{inst}.resource( + \"{name}\", + {wt}::component::ResourceType::host::<{camel}>(), + move |mut store, rep| -> {wt}::Result<()> {{ + Host{camel}::drop(&mut host_getter(store.data_mut()), {wt}::component::Resource::new_own(rep)) + }}, + )?;" + ) + } } gate.close(src); } @@ -2511,7 +2534,7 @@ impl<'a> InterfaceGenerator<'a> { uwriteln!( self.src, "{wt}::component::__internal::Box::pin(async move {{ - let accessor = &mut unsafe {{ caller.with_data(host_getter) }}; + let accessor = &caller.with_data(host_getter); " ); } @@ -2941,7 +2964,20 @@ impl<'a> InterfaceGenerator<'a> { CallStyle::Async | CallStyle::Concurrent ); let partition = self.partition_concurrent_funcs(functions.iter().copied()); - ret.any_concurrent = !partition.concurrent.is_empty(); + ret.any_concurrent = !partition.concurrent.is_empty() + || extra_functions.iter().any(|f| { + matches!( + f, + ExtraTraitMethod::ResourceDrop { name } + if matches!( + self + .generator + .opts + .drop_call_style(self.qualifier().as_deref(), name), + CallStyle::Concurrent + ) + ) + }); let mut concurrent_supertraits = vec![format!("{wt}::component::HasData")]; let mut sync_supertraits = vec![]; @@ -2953,7 +2989,13 @@ impl<'a> InterfaceGenerator<'a> { let camel = name.to_upper_camel_case(); sync_supertraits.push(format!("Host{camel}")); let funcs = self.partition_concurrent_funcs(get_resource_functions(self.resolve, *id)); - if !funcs.concurrent.is_empty() { + let concurrent_drop = matches!( + self.generator + .opts + .drop_call_style(self.qualifier().as_deref(), name), + CallStyle::Concurrent + ); + if concurrent_drop || !funcs.concurrent.is_empty() { ret.any_concurrent = true; concurrent_supertraits.push(format!("Host{camel}Concurrent")); } @@ -2974,6 +3016,26 @@ impl<'a> InterfaceGenerator<'a> { self.generate_function_trait_sig(func, false); self.push_str(";\n"); } + + for extra in extra_functions { + match extra { + ExtraTraitMethod::ResourceDrop { name } => { + let camel = name.to_upper_camel_case(); + if let CallStyle::Concurrent = self + .generator + .opts + .drop_call_style(self.qualifier().as_deref(), name) + { + uwrite!( + self.src, + "fn drop(accessor: &{wt}::component::Accessor, rep: {wt}::component::Resource<{camel}>) -> impl ::core::future::Future> + Send where Self: Sized;" + ); + } + } + ExtraTraitMethod::ErrorConvert { .. } => {} + } + } + uwriteln!(self.src, "}}"); } @@ -2998,17 +3060,19 @@ impl<'a> InterfaceGenerator<'a> { match extra { ExtraTraitMethod::ResourceDrop { name } => { let camel = name.to_upper_camel_case(); - if let CallStyle::Async | CallStyle::Concurrent = self + let style = self .generator .opts - .drop_call_style(self.qualifier().as_deref(), name) - { - uwrite!(self.src, "async "); + .drop_call_style(self.qualifier().as_deref(), name); + if !matches!(style, CallStyle::Concurrent) { + if let CallStyle::Async = style { + uwrite!(self.src, "async "); + } + uwrite!( + self.src, + "fn drop(&mut self, rep: {wt}::component::Resource<{camel}>) -> {wt}::Result<()>;" + ); } - uwrite!( - self.src, - "fn drop(&mut self, rep: {wt}::component::Resource<{camel}>) -> {wt}::Result<()>;" - ); } ExtraTraitMethod::ErrorConvert { name, id } => { let root = self.path_to_root(); @@ -3058,23 +3122,25 @@ fn convert_{snake}(&mut self, err: {root}{custom_name}) -> {wt}::Result<{camel}> match extra { ExtraTraitMethod::ResourceDrop { name } => { let camel = name.to_upper_camel_case(); - let mut await_ = ""; - if let CallStyle::Async | CallStyle::Concurrent = self + let style = self .generator .opts - .drop_call_style(self.qualifier().as_deref(), name) - { - self.src.push_str("async "); - await_ = ".await"; - } - uwriteln!( - self.src, - " + .drop_call_style(self.qualifier().as_deref(), name); + let mut await_ = ""; + if !matches!(style, CallStyle::Concurrent) { + if let CallStyle::Async = style { + self.src.push_str("async "); + await_ = ".await"; + } + uwriteln!( + self.src, + " fn drop(&mut self, rep: {wt}::component::Resource<{camel}>) -> {wt}::Result<()> {{ {trait_name}::drop(*self, rep){await_} }} ", - ); + ); + } } ExtraTraitMethod::ErrorConvert { name, id } => { let root = self.path_to_root(); diff --git a/crates/wit-bindgen/src/rust.rs b/crates/wit-bindgen/src/rust.rs index a5fccd5de0..e2b6383379 100644 --- a/crates/wit-bindgen/src/rust.rs +++ b/crates/wit-bindgen/src/rust.rs @@ -176,12 +176,12 @@ pub trait RustGenerator<'a> { TypeDefKind::Future(ty) => { let wt = self.wasmtime_path(); let t = self.optional_ty(ty.as_ref(), TypeMode::Owned); - format!("{wt}::component::HostFuture<{t}>") + format!("{wt}::component::FutureReader<{t}>") } TypeDefKind::Stream(ty) => { let wt = self.wasmtime_path(); let t = self.optional_ty(ty.as_ref(), TypeMode::Owned); - format!("{wt}::component::HostStream<{t}>") + format!("{wt}::component::StreamReader<{t}>") } TypeDefKind::Handle(handle) => self.handle(handle), TypeDefKind::Resource => unreachable!(), diff --git a/src/commands/serve.rs b/src/commands/serve.rs index aff613f995..37326e8503 100644 --- a/src/commands/serve.rs +++ b/src/commands/serve.rs @@ -579,6 +579,12 @@ impl ServeCommand { v = listener.accept() => v?, }; let comp = component.clone(); + // Disable the Nagle algorithm, which can add 40ms of latency on + // Linux machines if guest fails to provide response body data as + // soon as Hyper needs it. The tradeoff here is that we may end up + // sending more TCP packets if the guest is producing small body + // chunks. + stream.set_nodelay(true).unwrap(); let stream = TokioIo::new(stream); let h = handler.clone(); let shutdown_guard = shutdown.clone().increment(); @@ -971,22 +977,40 @@ async fn handle_request( Ok(result.map(|body| body.map_err(|e| e.into()).boxed())) } Proxy::P3(proxy, instance) => { - let (req, body) = req.into_parts(); - let body = body.map_err(p3::http::types::ErrorCode::from_hyper_request_error); - let res = instance - .run_concurrent(&mut store, async |store| { - proxy - .handle(store, http::Request::from_parts(req, body)) - .await - }) - .await???; - let (res, io) = wasmtime_wasi_http::p3::Response::resource_into_http(&mut store, res)?; + use std::future; + use std::pin::pin; + use std::task::Poll; + + let (tx, rx) = tokio::sync::oneshot::channel(); + tokio::task::spawn(async move { instance - .run_concurrent(&mut store, async |store| { + .run_concurrent(&mut store, async move |store| { + let (req, body) = req.into_parts(); + let body = + body.map_err(p3::http::types::ErrorCode::from_hyper_request_error); + let res = proxy + .handle(store, http::Request::from_parts(req, body)) + .await??; + let (res, io) = store.with(|mut store| { + wasmtime_wasi_http::p3::Response::resource_into_http(&mut store, res) + })?; + // Poll `io` once before handing the response to Hyper. + // This ensures that, if the guest has at least one body + // chunk ready to send, it will be available to Hyper + // immediately, meaning it can be sent in the same TCP + // packet as the beginning of the response. + // // TODO: Report transmit errors - let guest_io_result = async { Ok(()) }; - io.run(store, guest_io_result).await + let mut io = pin!(io.run(store, async { Ok(()) })); + let result = future::poll_fn(|cx| Poll::Ready(io.as_mut().poll(cx))).await; + + _ = tx.send(res); + + match result { + Poll::Ready(result) => result, + Poll::Pending => io.await, + } }) .await??; @@ -995,7 +1019,7 @@ async fn handle_request( anyhow::Ok(()) }); - Ok(res.map(|body| { + Ok(rx.await?.map(|body| { body.map_err(|err| err.unwrap_or(p3::http::types::ErrorCode::InternalError(None))) .map_err(|err| err.into()) .boxed() From 15a1a3bb296cb9faf00d4ab428ddd93dc3065819 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 25 Jul 2025 10:56:38 -0600 Subject: [PATCH 2/5] remove `TcpStream::set_nodelay` call This is no longer necessary to avoid 40ms of extra response latency for `wasi:http@0.3.0-draft` components since we now poll the body stream before handing the response to Hyper. I still think the Nagle algorithm is a bad default, but I can make a separate PR to disable it. Signed-off-by: Joel Dice --- src/commands/serve.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/commands/serve.rs b/src/commands/serve.rs index 37326e8503..f3b95b9908 100644 --- a/src/commands/serve.rs +++ b/src/commands/serve.rs @@ -579,12 +579,6 @@ impl ServeCommand { v = listener.accept() => v?, }; let comp = component.clone(); - // Disable the Nagle algorithm, which can add 40ms of latency on - // Linux machines if guest fails to provide response body data as - // soon as Hyper needs it. The tradeoff here is that we may end up - // sending more TCP packets if the guest is producing small body - // chunks. - stream.set_nodelay(true).unwrap(); let stream = TokioIo::new(stream); let h = handler.clone(); let shutdown_guard = shutdown.clone().increment(); From a265abf4ea4d13543112a7822348d673832909a5 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 25 Jul 2025 13:19:00 -0600 Subject: [PATCH 3/5] always drop futures with `&Accessor` fields using `tls::set` Previously, `WithAccessor[AndValue]` made only "best-effort" guarantees about disposing of handles on `drop` -- i.e. they did nothing if dropped outside the scope of a `tls::set`. Now they will panic instead, but that should never happen since we can guarantee futures closing over `&Accessor` are always dropped within the scope of a `tls::set`. Specifically, we ensure that happens in both `Store::drop` and `Instance::run_concurrent`. Signed-off-by: Joel Dice --- .../src/runtime/component/concurrent.rs | 85 +++++++++++++------ .../concurrent/futures_and_streams.rs | 61 ++----------- .../src/runtime/component/concurrent/tls.rs | 8 -- .../wasmtime/src/runtime/component/store.rs | 7 +- crates/wasmtime/src/runtime/store.rs | 6 +- 5 files changed, 77 insertions(+), 90 deletions(-) diff --git a/crates/wasmtime/src/runtime/component/concurrent.rs b/crates/wasmtime/src/runtime/component/concurrent.rs index 1610741868..2814fc7025 100644 --- a/crates/wasmtime/src/runtime/component/concurrent.rs +++ b/crates/wasmtime/src/runtime/component/concurrent.rs @@ -455,17 +455,6 @@ where }) } - /// Like `with`, except skips running `fun` if the thread-local state is not - /// set. - fn maybe_with(&self, fun: impl FnOnce(Access<'_, T, D>) -> R) -> Option { - tls::maybe_get(|vmstore| { - fun(Access { - store: self.token.as_context_mut(vmstore), - accessor: self, - }) - }) - } - /// Changes this accessor to access `D2` instead of the current type /// parameter `D`. /// @@ -1299,11 +1288,32 @@ impl Instance { let mut store = store.as_context_mut(); let token = StoreToken::new(store.as_context_mut()); - self.poll_until(store.as_context_mut(), async move { - let accessor = Accessor::new(token, Some(self)); - fun(&accessor).await - }) - .await + struct Dropper<'a, T: 'static, V> { + store: StoreContextMut<'a, T>, + value: Option, + } + + impl<'a, T, V> Drop for Dropper<'a, T, V> { + fn drop(&mut self) { + let value = self.value.take(); + tls::set(self.store.0.traitobj_mut(), move || drop(value)); + } + } + + let accessor = &Accessor::new(token, Some(self)); + let dropper = &mut Dropper { + store, + value: Some(fun(accessor)), + }; + // SAFETY: `dropper` is a local, non-escaping variable and we do not + // move its `value` field until it is dropped. + // + // TODO: Could/should we make this safe using some combination of `pin!` + // and `pin_project!`? + let future = unsafe { Pin::new_unchecked(dropper.value.as_mut().unwrap()) }; + + self.poll_until(dropper.store.as_context_mut(), future) + .await } /// Spawn a background task to run as part of this instance's event loop. @@ -1360,10 +1370,8 @@ impl Instance { async fn poll_until( self, store: StoreContextMut<'_, T>, - future: impl Future, + mut future: Pin<&mut impl Future>, ) -> Result { - let mut future = pin!(future); - loop { // Take `ConcurrentState::futures` out of the instance so we can // poll it while also safely giving any of the futures inside access @@ -3646,7 +3654,7 @@ impl VMComponentAsyncStore for StoreInner { } /// Represents the output of a host task or background task. -enum HostTaskOutput { +pub(crate) enum HostTaskOutput { /// A plain result Result(Result<()>), /// A function to be run after the future completes (e.g. post-processing @@ -4286,7 +4294,7 @@ impl ConcurrentState { } } - /// Take ownership of any fibers owned by this object. + /// Take ownership of any fibers and futures owned by this object. /// /// This should be used when disposing of the `Store` containing this object /// in order to gracefully resolve any and all fibers using @@ -4294,33 +4302,58 @@ impl ConcurrentState { /// use-after-free bugs due to fibers which may still have access to the /// `Store`. /// + /// Additionally, the futures collected with this function should be dropped + /// within a `tls::set` call, which will ensure than any futures closing + /// over an `&Accessor` will have access to the store when dropped, allowing + /// e.g. `WithAccessor[AndValue]` instances to be disposed of without + /// panicking. + /// /// Note that this will leave the object in an inconsistent and unusable /// state, so it should only be used just prior to dropping it. - pub(crate) fn take_fibers(&mut self, vec: &mut Vec>) { + pub(crate) fn take_fibers_and_futures( + &mut self, + fibers: &mut Vec>, + futures: &mut Vec>, + ) { for entry in mem::take(&mut self.table) { if let Ok(set) = entry.downcast::() { for mode in set.waiting.into_values() { if let WaitMode::Fiber(fiber) = mode { - vec.push(fiber); + fibers.push(fiber); } } } } if let Some(fiber) = self.worker.take() { - vec.push(fiber); + fibers.push(fiber); } let mut take_items = |list| { for item in mem::take(list) { - if let WorkItem::ResumeFiber(fiber) = item { - vec.push(fiber); + match item { + WorkItem::ResumeFiber(fiber) => { + fibers.push(fiber); + } + WorkItem::PushFuture(future) => { + self.futures + .get_mut() + .unwrap() + .as_mut() + .unwrap() + .push(future.into_inner().unwrap()); + } + _ => {} } } }; take_items(&mut self.high_priority); take_items(&mut self.low_priority); + + if let Some(them) = self.futures.get_mut().unwrap().take() { + futures.push(them); + } } } 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 b7836ed9cf..44b9c50d6b 100644 --- a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs +++ b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs @@ -404,34 +404,12 @@ pub(super) struct FlatAbi { /// Trait representing objects (such as streams, futures, or structs containing /// them) which require access to the store in order to be disposed of properly. pub trait DropWithStore: Sized { - /// Dispose of `Self` using the specified store. + /// Dispose of `self` using the specified store. fn drop(self, store: impl AsContextMut) -> Result<()>; - /// Attempt to dispose of `Self` using the specified accessor. - /// - /// Note that this will return `None` without disposing of `Self` if called - /// from outside the context where the specified `Accessor` is viable - /// (i.e. from outside a `Future::poll` call for the `Future` closing over - /// the `Accessor`). See the [`Accessor`] docs for further details. - fn maybe_drop_with(self, accessor: impl AsAccessor) -> Option> { - // Note that we use `Accessor::maybe_with` here instead of - // `Accessor::with`. This is because we might have been called when the - // thread-local store is not set, in which case we'd rather leak - // (i.e. not call `Self::drop) than panic. The most likely reason the - // thread-local store is unset is that the store itself is being - // dropped, in which case leaking objects inside the store is just fine - // because the whole thing is about to go away. - accessor - .as_accessor() - .maybe_with(move |store| self.drop(store)) - } - - /// Attempt to dispose of `Self` using the specified accessor. - /// - /// This will panic if called from outside the context where the specified - /// `Accessor` is viable. See `maybe_drop_with` for details. + /// Dispose of `self` using the specified accessor. fn drop_with(self, accessor: impl AsAccessor) -> Result<()> { - self.maybe_drop_with(accessor).unwrap() + accessor.as_accessor().with(move |store| self.drop(store)) } } @@ -442,26 +420,11 @@ pub trait DropWithStoreAndValue: Sized { /// Dispose of `self` using the specified store, writing the specified value. fn drop(self, store: impl AsContextMut, value: T) -> Result<()>; - /// Attempt to dispose of `self` using the specified accessor and value. - /// - /// Note that this will return `None` without disposing of `self` if called - /// from outside the context where the specified `Accessor` is viable - /// (i.e. from outside a `Future::poll` call for the `Future` closing over - /// the `Accessor`). See the [`Accessor`] docs for further details. - fn maybe_drop_with(self, accessor: impl AsAccessor, value: T) -> Option> { - // See comment in `DropWithStore::maybe_drop_with` about why we use - // `Accessor::maybe_with` here. + /// Dispose of `self` using the specified accessor and value. + fn drop_with(self, accessor: impl AsAccessor, value: T) -> Result<()> { accessor .as_accessor() - .maybe_with(move |store| self.drop(store, value)) - } - - /// Attempt to dispose of `Self` using the specified accessor. - /// - /// This will panic if called from outside the context where the specified - /// `Accessor` is viable. See `maybe_drop_with` for details. - fn drop_with(self, accessor: impl AsAccessor, value: T) -> Result<()> { - self.maybe_drop_with(accessor, value).unwrap() + .with(move |store| self.drop(store, value)) } } @@ -469,10 +432,6 @@ pub trait DropWithStoreAndValue: Sized { /// /// This may be used to automatically dispose of the wrapped object when it goes /// out of scope. -/// -/// Note that this will call `DropWithStore::maybe_drop_with` when dropped, -/// which may silently skip disposal if called from outside the `Future::poll` -/// call where the `Accessor` is enabled. pub struct WithAccessor<'a, T: DropWithStore, U: 'static, D: HasData = HasSelf> { accessor: &'a Accessor, inner: Option, @@ -513,7 +472,7 @@ impl<'a, T: DropWithStore, U, D: HasData> DerefMut for WithAccessor<'a, T, U, D> impl<'a, T: DropWithStore, U, D: HasData> Drop for WithAccessor<'a, T, U, D> { fn drop(&mut self) { if let Some(inner) = self.inner.take() { - _ = inner.maybe_drop_with(self.accessor); + _ = inner.drop_with(self.accessor); } } } @@ -522,10 +481,6 @@ impl<'a, T: DropWithStore, U, D: HasData> Drop for WithAccessor<'a, T, U, D> { /// /// This may be used to automatically dispose of the wrapped object when it goes /// out of scope, passing the specified value. -/// -/// Note that this will call `DropWithStoreAndValue::maybe_drop_with` when -/// dropped, which may silently skip disposal if called from outside the -/// `Future::poll` call where the `Accessor` is enabled. pub struct WithAccessorAndValue<'a, V, T, U, D = HasSelf> where U: 'static, @@ -600,7 +555,7 @@ impl< { fn drop(&mut self) { if let Some((inner, value)) = self.inner_and_value.take() { - _ = inner.maybe_drop_with(self.accessor, value); + _ = inner.drop_with(self.accessor, value); } } } diff --git a/crates/wasmtime/src/runtime/component/concurrent/tls.rs b/crates/wasmtime/src/runtime/component/concurrent/tls.rs index 98b1674176..a8496a2b79 100644 --- a/crates/wasmtime/src/runtime/component/concurrent/tls.rs +++ b/crates/wasmtime/src/runtime/component/concurrent/tls.rs @@ -76,14 +76,6 @@ pub fn get(f: impl FnOnce(&mut dyn VMStore) -> R) -> R { }) } -/// Like `get` except skips running `f` if the thread-local state is not set. -pub fn maybe_get(f: impl FnOnce(&mut dyn VMStore) -> R) -> Option { - try_get(|val| match val { - TryGet::Some(store) => Some(f(store)), - TryGet::None | TryGet::Taken => None, - }) -} - #[cold] fn get_failed() -> ! { panic!( diff --git a/crates/wasmtime/src/runtime/component/store.rs b/crates/wasmtime/src/runtime/component/store.rs index 71e214d72c..edb251ed5e 100644 --- a/crates/wasmtime/src/runtime/component/store.rs +++ b/crates/wasmtime/src/runtime/component/store.rs @@ -32,8 +32,9 @@ impl ComponentStoreData { } #[cfg(feature = "component-model-async")] - pub(crate) fn drop_fibers(store: &mut StoreOpaque) { + pub(crate) fn drop_fibers_and_futures(store: &mut StoreOpaque) { let mut fibers = Vec::new(); + let mut futures = Vec::new(); for (_, instance) in store.store_data_mut().components.instances.iter_mut() { let Some(instance) = instance.as_mut() else { continue; @@ -42,12 +43,14 @@ impl ComponentStoreData { instance .get_mut() .concurrent_state_mut() - .take_fibers(&mut fibers); + .take_fibers_and_futures(&mut fibers, &mut futures); } for mut fiber in fibers { fiber.dispose(store); } + + crate::component::concurrent::tls::set(store.traitobj_mut(), move || drop(futures)); } } diff --git a/crates/wasmtime/src/runtime/store.rs b/crates/wasmtime/src/runtime/store.rs index 1ffe2bd9e4..952caaafa9 100644 --- a/crates/wasmtime/src/runtime/store.rs +++ b/crates/wasmtime/src/runtime/store.rs @@ -664,8 +664,12 @@ impl Store { // attempting to drop the instances themselves since the fibers may need // to be resumed and allowed to exit cleanly before we yank the state // out from under them. + // + // This will also drop any futures which might use a `&Accessor` fields + // in their `Drop::drop` implementations, in which case they'll need to + // be called from with in the context of a `tls::set` closure. #[cfg(feature = "component-model-async")] - ComponentStoreData::drop_fibers(&mut self.inner); + ComponentStoreData::drop_fibers_and_futures(&mut self.inner); // Ensure all fiber stacks, even cached ones, are all flushed out to the // instance allocator. From 304a7f1d4fd76813476cdfa4b22d4f2e51e4e342 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 25 Jul 2025 13:57:19 -0600 Subject: [PATCH 4/5] avoid allocating oneshot channel in `watch_{reader,writer}` Signed-off-by: Joel Dice --- .../concurrent/futures_and_streams.rs | 108 ++++++++++-------- 1 file changed, 58 insertions(+), 50 deletions(-) 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 44b9c50d6b..b06691a799 100644 --- a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs +++ b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs @@ -19,6 +19,7 @@ use buffers::UntypedWriteBuffer; use futures::channel::oneshot; use std::boxed::Box; use std::fmt; +use std::future; use std::iter; use std::marker::PhantomData; use std::mem::{self, MaybeUninit}; @@ -26,6 +27,7 @@ use std::ops::{Deref, DerefMut}; use std::ptr::NonNull; use std::string::{String, ToString}; use std::sync::Arc; +use std::task::{Poll, Waker}; use std::vec::Vec; use wasmtime_environ::component::{ CanonicalAbiInfo, ComponentTypes, InterfaceType, RuntimeComponentInstanceIndex, StringEncoding, @@ -312,56 +314,58 @@ fn accept_writer, U>( /// Return a `Future` which will resolve once the reader end corresponding to /// the specified writer end of a future or stream is dropped. async fn watch_reader(accessor: impl AsAccessor, instance: Instance, id: TableId) { - let result = accessor.as_accessor().with(|mut access| { - let concurrent_state = instance.concurrent_state_mut(access.as_context_mut().0); - let state_id = concurrent_state.get(id)?.state; - let state = concurrent_state.get_mut(state_id)?; - anyhow::Ok(if matches!(&state.read, ReadState::Dropped) { - None - } else { - let (tx, rx) = oneshot::channel(); - state.reader_watcher = Some(tx); - Some(rx) - }) - }); - - if let Ok(Some(rx)) = result { - _ = rx.await - } + future::poll_fn(|cx| { + accessor + .as_accessor() + .with(|mut access| { + let concurrent_state = instance.concurrent_state_mut(access.as_context_mut().0); + let state_id = concurrent_state.get(id)?.state; + let state = concurrent_state.get_mut(state_id)?; + anyhow::Ok(if matches!(&state.read, ReadState::Dropped) { + Poll::Ready(()) + } else { + state.reader_watcher = Some(cx.waker().clone()); + Poll::Pending + }) + }) + .unwrap_or(Poll::Ready(())) + }) + .await } /// Return a `Future` which will resolve once the writer end corresponding to /// the specified reader end of a future or stream is dropped. async fn watch_writer(accessor: impl AsAccessor, instance: Instance, id: TableId) { - let result = accessor.as_accessor().with(|mut access| { - let concurrent_state = instance.concurrent_state_mut(access.as_context_mut().0); - let state_id = concurrent_state.get(id)?.state; - let state = concurrent_state.get_mut(state_id)?; - anyhow::Ok( - if matches!( - &state.write, - WriteState::Dropped - | WriteState::GuestReady { - post_write: PostWrite::Drop, - .. - } - | WriteState::HostReady { - post_write: PostWrite::Drop, - .. - } - ) { - None - } else { - let (tx, rx) = oneshot::channel(); - state.writer_watcher = Some(tx); - Some(rx) - }, - ) - }); - - if let Ok(Some(rx)) = result { - _ = rx.await - } + future::poll_fn(|cx| { + accessor + .as_accessor() + .with(|mut access| { + let concurrent_state = instance.concurrent_state_mut(access.as_context_mut().0); + let state_id = concurrent_state.get(id)?.state; + let state = concurrent_state.get_mut(state_id)?; + anyhow::Ok( + if matches!( + &state.write, + WriteState::Dropped + | WriteState::GuestReady { + post_write: PostWrite::Drop, + .. + } + | WriteState::HostReady { + post_write: PostWrite::Drop, + .. + } + ) { + Poll::Ready(()) + } else { + state.writer_watcher = Some(cx.waker().clone()); + Poll::Pending + }, + ) + }) + .unwrap_or(Poll::Ready(())) + }) + .await } /// Represents the state of a stream or future handle from the perspective of a @@ -1405,14 +1409,14 @@ struct TransmitState { write: WriteState, /// See `ReadState` read: ReadState, - /// The `Sender`, if any, to be dropped when the write end of the stream or + /// The `Waker`, if any, to be woken when the write end of the stream or /// future is dropped. /// /// This will signal to the host-owned read end that the write end has been /// dropped. - writer_watcher: Option>, + writer_watcher: Option, /// Like `writer_watcher`, but for the reverse direction. - reader_watcher: Option>, + reader_watcher: Option, /// Whether futher values may be transmitted via this stream or future. done: bool, } @@ -1866,7 +1870,9 @@ impl Instance { ); transmit.read = ReadState::Dropped; - transmit.reader_watcher = None; + if let Some(waker) = transmit.reader_watcher.take() { + waker.wake(); + } // If the write end is already dropped, it should stay dropped, // otherwise, it should be opened. @@ -1952,7 +1958,9 @@ impl Instance { transmit.write ); - transmit.writer_watcher = None; + if let Some(waker) = transmit.writer_watcher.take() { + waker.wake(); + } // Existing queued transmits must be updated with information for the impending writer closure match &mut transmit.write { From 15c4c76165dc2cac607ccf92ac80f113da65d0ad Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 17 Jul 2025 12:04:43 -0500 Subject: [PATCH 5/5] Add more domains to tls_sample_application (#11265) Both our current domains failed in https://github.com/bytecodealliance/wasmtime/actions/runs/16349123310/job/46190913918 so add a few more. --- .../src/bin/tls_sample_application.rs | 26 ++++++++++++++----- crates/wasi-tls/tests/main.rs | 1 + 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/crates/test-programs/src/bin/tls_sample_application.rs b/crates/test-programs/src/bin/tls_sample_application.rs index 6fa7a83442..8cc813d848 100644 --- a/crates/test-programs/src/bin/tls_sample_application.rs +++ b/crates/test-programs/src/bin/tls_sample_application.rs @@ -55,12 +55,16 @@ fn test_tls_invalid_certificate(_domain: &str, ip: IpAddress) -> Result<()> { .context("tcp connect failed")?; match ClientHandshake::new(BAD_DOMAIN, tcp_input, tcp_output).blocking_finish() { - // We're expecting an error regarding the "certificate" is some form or - // another. When we add more TLS backends this naive - // check will likely need to be revisited/expanded: - Err(e) if e.to_debug_string().contains("certificate") => Ok(()), - - Err(e) => Err(e.into()), + Err(e) => { + let debug_string = e.to_debug_string(); + // We're expecting an error regarding certificates in some form or + // another. When we add more TLS backends this naive check will + // likely need to be revisited/expanded: + if debug_string.contains("certificate") || debug_string.contains("HandshakeFailure") { + return Ok(()); + } + Err(e.into()) + } Ok(_) => panic!("expecting server name mismatch"), } } @@ -68,7 +72,13 @@ fn test_tls_invalid_certificate(_domain: &str, ip: IpAddress) -> Result<()> { fn try_live_endpoints(test: impl Fn(&str, IpAddress) -> Result<()>) { // since this is testing remote endpoints to ensure system cert store works // the test uses a couple different endpoints to reduce the number of flakes - const DOMAINS: &'static [&'static str] = &["example.com", "api.github.com"]; + const DOMAINS: &'static [&'static str] = &[ + "example.com", + "api.github.com", + "docs.wasmtime.dev", + "bytecodealliance.org", + "www.rust-lang.org", + ]; let net = Network::default(); @@ -94,6 +104,8 @@ fn try_live_endpoints(test: impl Fn(&str, IpAddress) -> Result<()>) { } fn main() { + println!("sample app"); try_live_endpoints(test_tls_sample_application); + println!("invalid cert"); try_live_endpoints(test_tls_invalid_certificate); } diff --git a/crates/wasi-tls/tests/main.rs b/crates/wasi-tls/tests/main.rs index 3105cee3a5..9c8494b6ea 100644 --- a/crates/wasi-tls/tests/main.rs +++ b/crates/wasi-tls/tests/main.rs @@ -27,6 +27,7 @@ async fn run_test(path: &str) -> Result<()> { let ctx = Ctx { table: ResourceTable::new(), wasi_ctx: WasiCtxBuilder::new() + .inherit_stdout() .inherit_stderr() .inherit_network() .allow_ip_name_lookup(true)