dicej requested alexcrichton for a review on PR #11325.
dicej opened PR #11325 from dicej:stream-future-refactor-upstream to bytecodealliance:main:
This makes a several changes to how
{Stream,Future}|{Reader,Writer}work to make them more efficient and, in some ways, more ergonomic:
The background tasks have been removed, allowing reads and writes to complete without task context switching. We now only allocate and use oneshot channels lazily when the other end is not yet ready; this improves real world performance benchmarks (e.g. wasi-http request handling) considerably.
Instances of
{Stream,Future}Readercan now be lifted and lowered directly; no need forHost{Stream,Future}anymore.The type parameter for
Stream{Reader,Writer}no longer refers to the buffer type -- just the payload type (i.e.StreamReader<u8>instead ofStreamReader<Vec<u8>>), meaning any buffer type may be used for a given read or write operation. This also means the compiler needs help with type inference less often when callingInstance::stream.Instances of
{Stream,Future}|{Reader,Writer}now require access to the store in order to be disposed of properly. I've added RAII wrapper structs (WithAccessor[AndValue]) to help with this, and also updatedStore::dropandInstance::run_concurrentto ensure the store thread-local is set when dropping futures closing over&Accessors.In order to ensure that resources containing
{Stream,Future}|{Reader,Writer}instances are disposed of properly, I've addedLinkerInstance::resource_concurrentand have updatedwasmtime-wit-bindgento use it. This gives resource drop functions access to aStoreContextMutvia anAccessor, allowing the stream and future handles to be disposed of.
- In order to make this work, I had to changeAccessor::instancefrom aInstanceto anOption<Instance>, which is awkward but temporary since we're planning to removeAccessor::instanceentirely once we've moved concurrent state fromComponentInstancetoStore.That problem of disposal is definitely the most awkward part of all this. In simple cases, it's easy enough to ensure that read and write handles are disposed of properly, but both
wasmtime-wasiandwasmtime-wasi-httphave some pretty complicated functions where handles are passed between tasks and/or stored inside resources, so it can be tricky to ensure proper disposal on all code paths. I'm open to ideas for improving this, but I suspect we'll need new Rust language features (e.g. linear types) to make it truly ergonomic, robust, and efficient.While testing the above, I discovered an issue with
Instance::poll_untilsuch that it would prematurely give up and return a "deadlock" trap error, believing that there was no further work to do, even though the future passed to it was ready to resolve the next time it was polled. I've fixed this by polling it one last time and only trapping if it returns pending.Note that I've moved a few associated functions from
ConcurrentStatetoInstance(e.g.guest_drop_writableand others) since they now need access to the store; they're unchanged otherwise. Apologies for the diff noise.Finally, I've tweaked how
wasmtime serveto poll the guest for content before handing the response to Hyper, which helps performance by ensuring the first content chunk can be sent with the same TCP packet as the beginning of the response.<!--
Please make sure you include the following information:
If this work has been discussed elsewhere, please include a link to that
conversation. If it was discussed in an issue, just mention "issue #...".Explain why this change is needed. If the details are in an issue already,
this can be brief.Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.htmlPlease ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->
dicej requested wasmtime-wasi-reviewers for a review on PR #11325.
dicej requested wasmtime-core-reviewers for a review on PR #11325.
dicej updated PR #11325.
alexcrichton submitted PR review.
alexcrichton created PR review comment:
I believe this isn't sound due to the
self.value.take()in the destructor, notably that moves the future meaning self-poitners are all invalidated, meaning that thedropcould go wrong.Destructors and
Pinare quite subtle which would make me want to trend more towards a safer version of this rather than havingunsafehere. Thepin_project_lite!macro has support for Drop but unfortunately I'm not sure it would help in this case because that'd just give youPin<&mut V>in the destructor which isn't sufficient for actual destruction.I know that at least one way to model this is to use
ManuallyDropto wrap the field insted ofOption, meaning that theDrop for Dropperwould unsafely drop-in-place inside oftls::set. That's still unsafe code, however, either with or withoutpin_project!. To me though that's probably the best we can do for now, so how about:
- Add
pin_project_liteas a dependency of Wasmtime component-model-async and use it here.
- Unrelated to this PR we should probably use it for
vm::{Instance, ComponentInstance}to remove some unsafe code there.- Use
ManuallyDropto storevalueinstead ofOption- Use
unsafecode to, in the destructor, run drop-in-place onPin<&mut ManuallyDrop<V>>by unsafely getting access to&mut ManuallyDrop<V>.
alexcrichton created PR review comment:
WDYT about changing this to
&mut self? I feel like that's a bit more idiomatic and maps well toDrop?Also I think this trait will want to be clearly documented in its relation to
Drop. Specifically this trait implementation does nothing outside ofWithAccessorand that's required to do anything here.
alexcrichton created PR review comment:
Similar to the above, as an async-cancel point this'll need some sort of destructor to run the
self.drop(store)below.
alexcrichton created PR review comment:
Could this be surfaced as a
pub fn close(...)onStreamWriteritself? (and the reader, possibly futures too)Basically I think it'd be good to document this irrespective of the
DropWithStoreimpl
alexcrichton created PR review comment:
Something here will need to change, but I'm not exactly sure how and what. I think this is sound but it's panick-y because
host_writecan callaccept_readerwhich can lower a value into wasm which isn't valid to do unless we're on a fiber. That runs the risk of suspending Rust code when there's embedder frames on the stack, which ok that's unsound and not just panick-y.One option is to drop this all entirely and have documentation indicating that a future cannot be closed without sending a value. That's not fantastic, however, as cancellation in Rust has no means of propagating into wasm (e.g. sending a
Noneor something like that).Another alternative would be to change
host_writeto not execute the lowering immediately but to instead kick of a wasm task in the background of sorts to perform the lowering. Unsure.
alexcrichton created PR review comment:
This is a bit tricky because this method takes
self-by-value which means there's noWith*on the stack that can run the destructor, and as an async cancel point here dropping this future wouldn't actually clean up the future itself.This sort of relates to the
DropWithStoreAndValuecomment I have below. On one hand this is a fundamental mismatch with "channel semantics" and "that's a future" where things are coming to a clash trying to model WIT futures in Rust in the face of cancellation. On the other hand this is solvable via similar mechanisms to below by retaining the default-value-to-send closure and arranging that to be sent at some point during destruction.
alexcrichton created PR review comment:
In the abstract I think I personally prefer to keep the closure-to-manufacture-the-value in the constructor of a future rather than having this trait. Would that still work?
alexcrichton created PR review comment:
Bikeshedding this a bit, this feels like it should be called
DropWithAccessoror somehow have "drop" related to the name since "with accessor" is a super generic name that doesn't quite imply what's happening under the hood here. Not that I like the nameDropWithAccessorthough, that also feels bad...
alexcrichton commented on PR #11325:
This is going to have conflicts with https://github.com/bytecodealliance/wasmtime/pull/11328, and I'll help work through those.
dicej submitted PR review.
dicej created PR review comment:
Good point. Yeah, I think we might need a background task here; I forgot this is one of the reasons I was doing _everything_ on a background task until this PR.
dicej updated PR #11325.
dicej submitted PR review.
dicej created PR review comment:
I've switched to
ManuallyDrop. It turned outpin-project-litedidn't buy us much, since we needed to unsafely convert fromPin<&mut ManuallyDrop<F>>toPin<&mut F>anyway, so I didn't end up using it.
dicej updated PR #11325.
dicej submitted PR review.
dicej created PR review comment:
I've updated this code to use
WithAccessorand eliminateDropWithStoreAndValue.
dicej submitted PR review.
dicej created PR review comment:
This is no longer part of the public API, so we can change the name if/when we have a better one, or just not worry about it.
I've addressed all the feedback so far except that we're still lowering writes using same stack the host embedder called us on; I'll address that by using a fiber for lowering.
dicej updated PR #11325.
alexcrichton submitted PR review.
alexcrichton created PR review comment:
Think about this in the context of rebasing with wasi:sockets -- is this necessary? Tasks can only be cancelled for two reasons:
- The store is dropped - in this case there's no need to do further cleanup
- The
AbortHandleis triggered, but in this case it's statically known we just forget about that and ignore it.Given that does WASI actually need
Guarded*?
dicej submitted PR review.
dicej created PR review comment:
Perhaps we don't _need_ it, but it saves us from having to remember to close the handle explicitly before returning, which isn't too onerous for this simple task, but could become a maintenance hazard later.
wasmtime-wasi-httphas some pretty complicated control flow, where keeping track of what to close when without RAII would be a real pain.
dicej updated PR #11325.
dicej submitted PR review.
dicej created PR review comment:
I just pushed an update to ensure that all host writes use a background task to lower when there may be embedder frames on the stack.
dicej updated PR #11325.
dicej updated PR #11325.
dicej updated PR #11325.
dicej updated PR #11325.
@alexcrichton I believe I've addressed all your feedback so far. Let me know if you have anything else.
BTW, the optimization for lowering flat payloads during host writes is pretty trivial:
diff --git a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs index 2809cb6b12..ed56fd7bce 100644 --- a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs +++ b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs @@ -1777,9 +1777,7 @@ impl Instance { anyhow::Ok(result) }; - if - // TODO: Check if payload is "flat" - false { + if T::IS_FLAT_TYPE { // Optimize flat payloads (i.e. those which do not require // calling the guest's realloc function) by lowering // directly instead of using a oneshot::channel and diff --git a/crates/wasmtime/src/runtime/component/func/typed.rs b/crates/wasmtime/src/runtime/component/func/typed.rs index ad4328fce4..f27120957a 100644 --- a/crates/wasmtime/src/runtime/component/func/typed.rs +++ b/crates/wasmtime/src/runtime/component/func/typed.rs @@ -687,6 +687,9 @@ pub unsafe trait ComponentType: Send + Sync { #[doc(hidden)] const IS_RUST_UNIT_TYPE: bool = false; + #[doc(hidden)] + const IS_FLAT_TYPE: bool = false; + /// Returns the number of core wasm abi values will be used to represent /// this type in its lowered form. /// @@ -1016,6 +1019,8 @@ macro_rules! integers { const ABI: CanonicalAbiInfo = CanonicalAbiInfo::$abi; + const IS_FLAT_TYPE: bool = true; + fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> { match ty { InterfaceType::$ty => Ok(()),
alexcrichton submitted PR review.
alexcrichton merged PR #11325.
Last updated: Dec 06 2025 at 06:05 UTC