diff --git a/crates/component-macro/tests/expanded/char_concurrent.rs b/crates/component-macro/tests/expanded/char_concurrent.rs index 90a8575df..e65536df8 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 597450397..d28cac5e7 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 1c525d87a..caf5db15d 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 ddf3ec705..e5d0de6d1 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 22771b77b..e92bde4e0 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 7d8cf0d06..dd21ad33d 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 fab15db7e..356c47fb0 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 6f373dbaf..d6e9e0cf7 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 8052e2232..4448cd4b0 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 a8c229850..ee12d1cc9 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 32e64fd34..2a7ea3a99 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 2a03557b8..8f00b4cd5 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 df4c9d5b3..971f1d88a 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 9cfa5adee..85292a832 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 89196d928..c79118215 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 1e11aea96..81d3efbf8 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 2e3148c80..c71d69872 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 af56c7ccc..68c211b85 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 6895fbf8b..c1453128d 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 ca76cfae7..df8bb972e 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 dae1c5bd0..b56ab99c0 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 b6422dcee..62b5df7f4 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 b607006c4..f9f1f00a5 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 84b5ace88..92876736d 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 e171a1332..867cfcc6e 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 cedaa4db9..8c1f3bfe9 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 63ca916c4..84633db69 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 e77daf683..f19c4de60 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 de1757fb6..18cdcc02d 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/test-programs/src/bin/tls_sample_application.rs b/crates/test-programs/src/bin/tls_sample_application.rs index 6fa7a8344..8cc813d84 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-http/src/p3/bindings.rs b/crates/wasi-http/src/p3/bindings.rs index ff477eba2..90b3f9398 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 33b87a34b..4eef21026 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 2c3dbf4d1..0e80a337c 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 9be58fdf1..eb94cceab 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 a33a3d538..e012a7b39 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 93933bf83..b306ba7e8 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-tls/tests/main.rs b/crates/wasi-tls/tests/main.rs index 3105cee3a..9c8494b6e 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) diff --git a/crates/wasi/src/p3/cli/host.rs b/crates/wasi/src/p3/cli/host.rs index 4163dcbcc..1e22790fe 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 4d7c3a26a..58e3eef5e 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 1f27126fe..15a1286ea 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 6faa81b39..7d868d1b9 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 f0174d59d..2814fc702 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, @@ -507,7 +507,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 +516,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 { @@ -1288,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, 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. @@ -1310,7 +1331,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) } @@ -1349,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 @@ -1405,34 +1424,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 +2476,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 } } @@ -3620,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 @@ -4260,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 @@ -4268,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 0f1b5dd43..b06691a79 100644 --- a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs +++ b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs @@ -1,31 +1,32 @@ 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::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::sync::Arc; use std::task::{Poll, Waker}; use std::vec::Vec; use wasmtime_environ::component::{ @@ -178,157 +179,193 @@ 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), + ), + }) +} - Ok(count) - } +/// 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) { + 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) { + 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 @@ -368,136 +405,184 @@ 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<()>; + + /// Dispose of `self` using the specified accessor. + fn drop_with(self, accessor: impl AsAccessor) -> Result<()> { + accessor.as_accessor().with(move |store| self.drop(store)) + } } -/// 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<()>; + + /// Dispose of `self` using the specified accessor and value. + fn drop_with(self, accessor: impl AsAccessor, value: T) -> Result<()> { + accessor + .as_accessor() + .with(move |store| self.drop(store, value)) + } } -/// Send the specified value to the specified `Sender`. +/// RAII wrapper for `DropWithStore` implementations. /// -/// 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!(); +/// This may be used to automatically dispose of the wrapped object when it goes +/// out of scope. +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.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. +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.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 +596,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 +624,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 +739,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 +763,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 +824,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 +838,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 +858,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 +867,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 +887,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 +933,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 +956,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 +972,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 +990,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 + /// 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::Lower + func::Lift + Send + 'static, - B: ReadBuffer, + T: func::Lift + 'static, + B: ReadBuffer + Send + 'static, { - StreamReader { - instance: self.instance, - rep: self.rep, - tx: Some(self.instance.start_read_event_loop( - store.as_context_mut(), - self.rep, + let result = self + .instance + .host_read_async( + accessor.as_accessor(), + self.id, + buffer, TransmitKind::Stream, - )), - closed: false, + ) + .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") + } } } - /// Convert this `HostStream` into a [`Val`]. + /// 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 `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 +1101,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 +1128,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 +1183,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 +1197,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 +1217,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 +1226,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 +1246,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, @@ -1432,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, } @@ -1564,7 +1541,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 +1550,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 +1601,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 +1642,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 +1651,8 @@ impl Instance { address, count, }, + buffer, + kind, )?; self.concurrent_state_mut(store.0).set_event( @@ -1886,6 +1668,8 @@ impl Instance { }, }, )?; + + Ok(result) } ReadState::HostReady { accept } => { @@ -1899,51 +1683,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 +1734,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 +1766,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 +1798,8 @@ impl Instance { }, }, )?; + + Ok(result) } WriteState::HostReady { accept, post_write } => { @@ -2012,13 +1807,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 +1820,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, @@ -2066,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. @@ -2102,23 +1908,132 @@ 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 + ); + + 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 { + 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 +2041,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 +2731,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 +2885,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 +3108,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 +3166,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 +3286,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 +3313,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_disabled.rs b/crates/wasmtime/src/runtime/component/concurrent_disabled.rs index 1a661dd4d..186676180 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 71bae4347..2f68d4b5e 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 27f6945dd..3af50f937 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/store.rs b/crates/wasmtime/src/runtime/component/store.rs index 71e214d72..edb251ed5 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/component/values.rs b/crates/wasmtime/src/runtime/component/values.rs index dbe129ca4..943607280 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/store.rs b/crates/wasmtime/src/runtime/store.rs index 1ffe2bd9e..952caaafa 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. diff --git a/crates/wasmtime/src/runtime/vm/component/libcalls.rs b/crates/wasmtime/src/runtime/vm/component/libcalls.rs index 2dc9abef5..5f8de8511 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 9093f1822..ee9da684d 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 a5fccd5de..e2b638337 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 aff613f99..f3b95b990 100644 --- a/src/commands/serve.rs +++ b/src/commands/serve.rs @@ -971,22 +971,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 +1013,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()