Late last year we decided that we'd like to start running a standup style update and to use the Ship WASIp3 project board (see topic for background) to coordinate work.
Let's kick things off with an async style standup for this week!
Folks that have active work streams, will you post a quick update?
cc @Victor Adossi @Roman Volosatovs @Joel Dice
Bailey Hayes: WASI clocks and random 0.3.0-draft WIT definitions have been reviewed and merged. We adjusted the API for wasi-clocks now that fn's are async, see: https://github.com/WebAssembly/wasi-clocks/blob/main/wit-0.3.0-draft/monotonic-clock.wit#L34-L44
We decided to rev the since
versions as 0.3.0 will make breaking changes to the 0.2.x series. Expect all 0.3.0-draft WIT's to align to this. Once @Roman Volosatovs PR's for filesystem and sockets merge, wasi-cli is the next target. I believe we're very close to having a draft for WASIP2 interfaces (http draft already exists but will be updated once deps land).
Once the above is complete, adding support for WASIP3 interfaces in wasmtime and JCO can begin
If you're interested in jumping in, I see a couple items that could begin now:
non-blocking
attribute issue on board. Before starting this one, check-in with @Luke Wagner When picking up a new issue on the board, set yourself as the assignee.
Async/future/stream/error-context support has been merged into wasm-tools
and wit-bindgen
and is available in their latest releases. Still iterating on the Wasmtime PR, knocking out todo items one by one. Hoping to move that one to "ready-for-review" by the end of the month. Meanwhile, it is feature-complete (modulo the error-context
work Victor is doing, which I'm reviewing now), so feel free to clone the branch, kick the tires, and start adding WASIp3 support to wasmtime-wasi
and wasmtime-wasi-http
.
Hey all, here's what I'm working on right now:
wit-bindgen-rt
and a draft to Joel's wasmtime branch (immense thanks to @Joel Dice who walked me through this)asyncify
faster (unlikely to pan out)CC @Calvin Prewitt who is doing the actual bulk of the work of jco host async imports
wasi-sockets
and wasi-filesystem
WIT update PRs are ready for review
will work on removing wasi:io/error.error
dep from wasi:http
and after that start WASI implementation in Wasmtime
Thanks. I've got a few remarks on the wasi-sockets part. Hope to get it written down soon
What’s the status of implementing P3 streams and futures in wasmtime::component? That’s a prerequisite to a wasi impl, correct?
@Pat Hickey see my update above; that's feature-complete and has decent test coverage. Working to get that PR in a reviewable state (hopefully in the next week or two), but meanwhile you can use my PR branch.
Thanks. I will take a look. I’ve been factoring out wasi-io from wasmtime-wasi so that at least that much can be built nostd, idk how that will intersect with these new streams and futures but hopefully they can plug together
Just a heads-up, I've started working on stream
types with no <T>
, added this ticket to the board: https://github.com/orgs/bytecodealliance/projects/16?pane=issue&itemId=94157967
i.e. implementing the spec change from https://github.com/WebAssembly/component-model/pull/440 in wasm-tools and wit-bindgen
Opened a draft PR to wasi:cli
for 0.3.0 https://github.com/WebAssembly/wasi-cli/pull/52
a few smaller-scoped wasi:http
updates to 0.3.0 https://github.com/WebAssembly/wasi-http/pull/143 https://github.com/WebAssembly/wasi-http/pull/142
wasi:random
fix https://github.com/WebAssembly/wasi-random/pull/52
Added a draft PR for stream
of unit: https://github.com/bytecodealliance/wasm-tools/pull/1978
started the wasmtime WASI 0.3 impl https://github.com/bytecodealliance/wasmtime/pull/10061
and now with actual async: https://github.com/bytecodealliance/wasmtime/pull/10063/commits/171a532910790379ebbfd85ddbb62b6f60a1fbff
component build fails with:
thread 'main' panicked at crates/test-programs/artifacts/build.rs:176:10:
module can be translated to a component: failed to validate component output
Caused by:
canonical option `async` requires the component model async feature (at offset 0x82da1a)
Will try basing on async
branch
btw, I've noticed that wit-bindgen-rt
dependency, which appears to be used by async
is missing out-of-the-box.
I believe deps are normally reexported by wit-bindgen
, but in this case it appears that macro attempts to import from ::wit-bindgen-rt
cc @Joel Dice idk, if that's a known/fixed issue or do you want me to take a look?
filed https://github.com/bytecodealliance/wit-bindgen/issues/1135, working on a fix
this seems to have fixed the issue: https://github.com/bytecodealliance/wit-bindgen/pull/1136
FYI, I'm starting to work on wit-bindgen C support for async/streams/futures, which will unblock wasip3 support in wasi-libc
and the toolchains which depend on it.
I've rebased my Wasmtime WASI changes on latest async
from @Joel Dice in https://github.com/rvolosatovs/wasmtime/tree/async-wasi
wasi:clocks@0.3.0
test fails for now, but looks like we're pretty close to getting this working end-to-end:
on https://github.com/rvolosatovs/wasmtime/commit/8ea812e04a77ab7a396bd74876d8c8ef6f0b017b
---- async_::preview3_sleep stdout ----
preopen: TempDir { path: "/var/folders/bq/thy1_b7x29l7s2wqw39r62yw0000gn/T/wasi_components_preview3_sleep.component_cp7fbR" }
thread 'async_::preview3_sleep' panicked at /Users/rvolosatovs/src/github.com/bytecodealliance/wasmtime/crates/wasi/src/runtime.rs:108:15:
Cannot start a runtime from within a runtime. This happens because a function (like `block_on`) attempted to block the current thread while the thread is being used to drive asynchronous tasks.
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
---- sync::preview3_sleep stdout ----
preopen: TempDir { path: "/var/folders/bq/thy1_b7x29l7s2wqw39r62yw0000gn/T/wasi_components_preview3_sleep.component_bQriUp" }
thread 'sync::preview3_sleep' panicked at /Users/rvolosatovs/src/github.com/bytecodealliance/wasmtime/crates/wasmtime/src/runtime/component/concurrent.rs:534:54:
called `Option::unwrap()` on a `None` value
failures:
async_::preview3_sleep
sync::preview3_sleep
I'll start digging :)
I got wasi:clocks@0.3.0
test passing with async runtime in https://github.com/rvolosatovs/wasmtime/commit/c3406b2cb02a6013add16867a8c6debad8bbef39
thanks @Joel Dice for the help!
I noticed the same "current task missing" panic when attempting to run the same test with async_support(false)
, i.e. using sync host bindings. It makes sense, since in that case none of the async machinery seem to be used
What is the current thinking around that - are we planning to support wasip3 on hosts without async_support
at all?
@Alex Crichton and I discussed this a while ago. We don't think WASIp3 or the component model async ABI make sense to support in Wasmtime with async_support(false)
, and we're actually considering deprecating Wasmtime's component model sync APIs (i.e. making async_support(true)
the default and, eventually, only option for running components).
That sounds good to me and simplifies this effort - I won't be adding the sync
bindings to wasip3 interfaces at all then :)
thanks Joel, that sounds right - we'll have to see about deprecating the sync stuff for p2 we already provide (hacky as it is, it does actually meet some users needs) but I agree we shouldn't worry about sync interfaces to p3 or async CM
Do I understand correctly that the plan is that the CM host must support async, but that all components can still be fully sync (if one wants that)? If wasmtime will always use async, it may be useful to support compiling with a very cheap async runtime so that e.g. embedded usecases don’t need to pull in a costly featureful runtime
Yes, you should basically just need tasks (which can be implemented with a library e.g. https://docs.rs/futures/latest/futures/task/index.html), and whatever your platform needs for IO to make a custom executor
I'm working on exactly that for P2 right now, the need is not limited to P3
the existing P2 wasmtime-wasi always runs inside tokio, with the sync bindings it just hides that tokio in the background.
@Pat Hickey @Joel Dice @Alex Crichton @Victor Adossi I've sent you all invites to a 30 min call tomorrow, please let me know if that time slot does not work for you.
If it does, then first item on the agenda would be discussing time slots and making this a regular meeting :)
(I used emails from Wasmtime git log mostly)
just realized I scheduled the meeting for next week :thinking: , just rescheduled for tomorrow, also moved it to a bit later to accommodate for US West Coast
works for me yeah :+1:
joel/I won't be around next monday but don't let my absence block y'all the monday after that from meeting
I've created https://github.com/bytecodealliance/wasip3-prototyping and we should all have admin access there
I avoided a literal fork b/c i don't think github lets you fork a repo within the same org
just pushed up wasmtime's current main
to the main
there as well
Small follow-up to the discussion from yesterday, just for the sake of completeness - without any changes to the wasmtime bindgen, we should be able to use dynamic dispatch to avoid the complex state machines in implementations. I haven't tested it, but I don't see why returning a Box<dyn FnOnce(..)>
wouldn't work.
It's essentially the same thing we've been doing for a long time with async_trait
, but it does seem as a bit of a setback, since we'd not be able to "just" use Rust async. It also seems non-obvious for embedders, especially those not completely familiar with dynamic dispatch/async (e.g. explicit Box<dyn ..>
bounds would likely be required to avoid compiler errors)
Just throwing it out there as a potential solution, I believe having "true" Rust async trait support would be a lot nicer/better
Can you clarify how returning a Box<dyn ...>
would help? I'm not seeing what part of the problem it solves.
FWIW, I just sketched out a potential API based on a thread-local variable which is only set to a valid value when Wasmtime is polling futures created by functions registered using LinkerInstance::func_wrap_concurrent
: https://gist.github.com/dicej/21519f9cf2e4d57a3316ea0b2167d281
diff --git a/crates/wasi/src/p3/sockets/host/types/tcp.rs b/crates/wasi/src/p3/sockets/host/types/tcp.rs
index e2a45eb6c..c5f3cbb7f 100644
--- a/crates/wasi/src/p3/sockets/host/types/tcp.rs
+++ b/crates/wasi/src/p3/sockets/host/types/tcp.rs
@@ -45,64 +45,65 @@ where
let ctx = store.data().sockets();
let allowed = ctx.allowed_network_uses.tcp;
let socket_addr_check = ctx.socket_addr_check.clone();
- let sock = store
+ let tcp_socket = match store
.data_mut()
.table()
.get_mut(&mut socket)
.context("failed to get socket resource from table")
- .map(|socket| {
- let tcp_state = mem::replace(&mut socket.tcp_state, TcpState::BindStarted);
- if let TcpState::Default(sock) = tcp_state {
- Some((sock, socket.family))
- } else {
- socket.tcp_state = tcp_state;
- None
- }
+ {
+ Ok(sock) => sock,
+ Err(err) => {
+ return Box::pin(async move {
+ for_any(Box::new(for_any(move |_| Err(err)))) as Box<dyn FnOnce(_) -> _>
+ }) as Pin<Box<dyn Future<Output = _>>>
+ }
+ };
+ let tcp_state = mem::replace(&mut tcp_socket.tcp_state, TcpState::BindStarted);
+ let sock = if let TcpState::Default(sock) = tcp_state {
+ sock
+ } else {
+ tcp_socket.tcp_state = tcp_state;
+ return Box::pin(async {
+ for_any(
+ Box::new(move |_| Ok(Err(ErrorCode::InvalidState))) as Box<dyn FnOnce(_) -> _>
+ )
});
+ };
let local_address = SocketAddr::from(local_address);
- async move {
- let res = match sock {
- Ok(sock)
- if !allowed
- || !socket_addr_check(local_address, SocketAddrUse::TcpBind).await =>
- {
- if let Some((sock, ..)) = sock {
- Ok(Ok((sock, Err(ErrorCode::AccessDenied))))
- } else {
- Ok(Err(ErrorCode::AccessDenied))
- }
- }
- Ok(Some((sock, family))) => {
- let res = bind(&sock, local_address, family);
- Ok(Ok((sock, res)))
- }
- Ok(None) => Ok(Err(ErrorCode::InvalidState)),
- Err(err) => Err(err),
- };
- for_any(move |mut store: StoreContextMut<'_, Self::TcpSocketData>| {
- let sock = res?;
- let socket = store
+ Box::pin(async move {
+ if !allowed || !socket_addr_check(local_address, SocketAddrUse::TcpBind).await {
+ return Box::new(move |mut store: StoreContextMut<'_, Self::TcpSocketData>| {
+ let tcp_socket = store
+ .data_mut()
+ .table()
+ .get_mut(&mut socket)
+ .context("failed to get socket resource from table")?;
+ tcp_socket.tcp_state = TcpState::Default(sock);
+ Ok(Err(ErrorCode::AccessDenied))
+ }) as Box<dyn FnOnce(_) -> _>;
+ }
+ Box::new(move |mut store: StoreContextMut<'_, Self::TcpSocketData>| {
+ let tcp_socket = store
.data_mut()
.table()
.get_mut(&mut socket)
.context("failed to get socket resource from table")?;
- let (sock, res) = match sock {
- Ok(sock) => sock,
- Err(err) => return Ok(Err(err)),
- };
ensure!(
- matches!(socket.tcp_state, TcpState::BindStarted),
+ matches!(tcp_socket.tcp_state, TcpState::BindStarted),
"corrupted socket state"
);
- if let Err(err) = res {
- socket.tcp_state = TcpState::Default(sock);
- Ok(Err(err))
- } else {
- socket.tcp_state = TcpState::Bound(sock);
- Ok(Ok(()))
+ match bind(&sock, local_address, tcp_socket.family) {
+ Ok(()) => {
+ tcp_socket.tcp_state = TcpState::Bound(sock);
+ Ok(Ok(()))
+ }
+ Err(err) => {
+ tcp_socket.tcp_state = TcpState::Default(sock);
+ Ok(Err(err))
+ }
}
})
- }
+ })
}
fn connect(
something roughly like this. I did not play the lifetime bingo to solve the "one type is more general than the other" and "implementation of FnOnce
is not general enough", but that's a potential workaround (which, it seems, would require even more helpers akin to for_any
). I'm not sure if it's more ergonomic or clear.
Your sketch looks good to me, however, and I think it makes a lot of sense.
FYI, I just pushed my async
branch to the main
branch of the wasip3-prototyping repo. I won't rebase it anymore after that; we can merge from upstream main as necessary, which should be less disruptive than rebasing.
I missed wherever this came up on github - whats the reasoning behind dropping the -draft
suffix on wasi 0.3 wit packages versions?
Looks like I've discovered, what appears to be, a bindgen bug https://github.com/bytecodealliance/wasip3-prototyping/issues/2, but not entirely sure yet.
ok got it. so we can restore the -draft once that bug is fixed?
I think so, however is there a particular reason we'd want the -draft
suffix?
Since the interfaces are in a separate (draft) directory within proposal repositories anyway, the fact that those interfaces are "draft" should be clear and not introducing the suffix seem to minimize the churn
It's a precedent we set with wasip2. We don't want anyone to accidentally think wasi 0.3.0 is ready. We're also expecting to make a series of updates as these are unlikely to be the final versions of the interfaces. -draft
makes that easier.
yep. we have meaningful semver there, so lets use it.
it also means we can publish the draft versions to an OCI registry fearlessly.
Some fast-feedback would be great on this issue in wasi-http:
https://github.com/WebAssembly/wasi-http/issues/153
It looks like a signature (body.finish
) needs to change given that internally it produces an error-context
. There are many options for how to fix it, but it would be great to get that fix in upstream on the 0.3.0-draft
A quick summary of the status meeting earlier today, please let me know if I missed something:
wasi-http/types.body#finish
: reads from future, which is fallible, but there is currently no way to propagate the error-context
from the method. We need an issue discussion in wasi-http
about how to handle that@Roman Volosatovs this fixes the borrow issue: https://github.com/bytecodealliance/wasip3-prototyping/pull/4
Awesome, thanks @Joel Dice ! Just tested it and can confirm that it fixed the issue I've encountered :)
Here's the wasmtime-wit-bindgen ergonomics refactor: https://github.com/bytecodealliance/wasip3-prototyping/pull/6
Looks great! Here's bind
and connect
with new API https://github.com/bytecodealliance/wasip3-prototyping/blob/7dbc8a9fd6a2348e772a32382c3e29c88cad397e/crates/wasi/src/p3/sockets/host/types/tcp.rs#L67-L153
See also https://github.com/bytecodealliance/wasip3-prototyping/pull/6#pullrequestreview-2612364221
first, quick, incomplete attempt at TCP send looks roughly like that https://github.com/bytecodealliance/wasip3-prototyping/blob/e84bce870bf7bafed014b0a536445e1e0deb22fa/crates/wasi/src/p3/sockets/host/types/tcp.rs#L263-L366
once I can get listen
working, I'll be able to run some tests
(this also lacks shutdown on EOF, but just testing out the API for now)
@Roman Volosatovs this should unblock listen
: https://github.com/bytecodealliance/wasip3-prototyping/pull/8
Thanks @Joel Dice !
Working on listen
I'm getting a sefault with this API and I've also ran into the failed to get promise
due to cannot remove owned resource while borrowed
again, looks like there's indeed a bug, which is likely similar to what happened with borrows.
I've also added a little commit to allow spawn
on store directly (from within with
) (https://github.com/bytecodealliance/wasip3-prototyping/pull/1/commits/e4e4e5531aee37c0a99ea5e2d9364c30c001752d), however even after reverting that I'm still getting the same result.
What's the most efficient way to proceed here?
Should I file issues with reproduction steps?
Would it help to get on a call and pair?
I've pushed my commit trace to https://github.com/bytecodealliance/wasip3-prototyping/pull/1
Filing issues would be ideal. They don't need detailed descriptions -- just brief descriptions of each issue with steps to repro.
BTW, I've been wondering how listen: func() -> result<stream<tcp-socket>, error-code>
is supposed to work given that the tcp-socket
borrow only lasts until listen
returns, i.e. the callee no longer has access to it after returning, so it can't use it in the spawned task. Curious if @Luke Wagner has guidance on this.
filed the issue at https://github.com/bytecodealliance/wasip3-prototyping/issues/9
BTW, I've been wondering how
listen: func() -> result<stream<tcp-socket>, error-code>
is supposed to work given that thetcp-socket
borrow only lasts untillisten
returns, i.e. the callee no longer has access to it after returning, so it can't use it in the spawned task.
Roman and I discussed this in today's Wasmtime meeting and he clarified that the host implementation of listen
does not attempt to use the borrowed resource as such after the function returns -- instead it clones the backing implementation and uses that, which makes sense. And I guess a component-implemented version could do the same thing by making the exported tcp-socket
resource implementation contain a cloneable and interior-ly mutable state which may be cloned for post-return use.
Filed an issue for the second error I've encountered: https://github.com/bytecodealliance/wasip3-prototyping/issues/10
@Roman Volosatovs The segfault you found is the same as the one I discovered last week in the context of epoch interruption (although it turns out it can happen even with epoch interruption disabled) and am in the process of addressing. Meanwhile, this tiny PR makes it panic instead of segfault.
Awesome, thanks for looking into it. Can confirm that it does not segfault anymore! :)
@Roman Volosatovs This fixes issue #10: https://github.com/bytecodealliance/wasip3-prototyping/pull/12. I'll continue working on #9, but might not finish it before the end of my day, in which case I should have a PR up tomorrow.
Do we want to get CI running on the prototyping repo? I must have missed the how/when/why of when it was disabled, but having some tests run at PR time is likely better than none.
It's not a showstopper as I think a forking workflow would allow all the changes anyone wanted, but just bringing it up here for discussion.
Yes, that would be great.
@Roman Volosatovs This PR addresses #9. BTW, I merged my other outstanding PRs (despite not yet being approved) because juggling 4 different branches was irritating me. I'd also like to merge yours as soon as it's somewhat stable (#[ignore
ing tests as necessary) so we don't need to jump back and forth so much.
With that PR, the tests that were failing due to #9 no longer panic, but they hang indefinitely, consuming no CPU. Not sure if that's a bug in the tests or in Wasmtime; I haven't had a chance to study them yet.
I general, I think we can be pretty aggressive about merging PRs for now and avoid long-lived branches. We're going to split it all up again for upstreaming anyway; for now let's keep things simple. Enabling CI as Victor suggested will help avoid stepping on each others' toes.
Sounds good, I've rebased https://github.com/bytecodealliance/wasip3-prototyping/pull/1 on latest main
and marked it ready for review.
Unfortunately, I was not able to enable all TCP tests yet, as it seems I'm hitting a deadlock with stream I/O, the host never receives the data that the guest sends. I'll file an issue later today
Yeah, that deadlock sounds like the hang I saw last week. I can look into that today. If you can minimize the test case that reproduces it at all, that would definitely help.
I tracked it down to these two:
guest: https://github.com/bytecodealliance/wasip3-prototyping/blob/33263c939f09ec0c68d200b7b8048441812657d4/crates/test-programs/src/bin/sockets_0_3_tcp_bind.rs#L71
host: https://github.com/bytecodealliance/wasip3-prototyping/blob/33263c939f09ec0c68d200b7b8048441812657d4/crates/wasi/src/p3/sockets/host/types/tcp.rs#L435
Neither of these calls ever resolve, as if the futures are not polled
I'm using the Promise::into_future
in the host, which seems to be the right thing to do according to the docs, I have not tried it with Promise::get
Yeah, Promise::into_future
is the only one that makes sense in this context. I'll take a look.
Based on println
debugging the guest is not even reaching the line you linked to. Since the guest has lowered thetcp-socket.send
import synchronously, and the implementation is trying to read the entire stream<u8>
before returning, there's no way it will ever return; the guest is blocked on that call and will never write anything to the stream.
I think you'll want to lower the tcp-socket.send
import asynchronously and await
it concurrently with a future that feeds the write end of the stream (e.g. using join or FuturesUnordered). Happy to pair on this when you're available.
Right, yeah, that makes sense, thanks!
I'll push a fix later tonight if I'll get a chance, tomorrow otherwise
had a few minutes to try it out https://github.com/bytecodealliance/wasip3-prototyping/pull/17, will need to figure out a way to properly adapt the wasip2 test expectations regarding the behavior, but looks like it works. tcp_streams
still hangs, but that's likely due to some write handle not being dropped. Will continue tomorrow.
Thanks for taking a look, looks like we'll be able to have the complete TCP test suite working tomorrow
Interesting, looks like I found another issue - the BackgroundTask
is not awoken by the executor after returning Poll::Pending
.
I think it would be beneficial to pair later today!
if this is the waker used to poll these tasks, it would explain what I'm seeing: https://github.com/bytecodealliance/wasip3-prototyping/blob/f5fefc6f4e63b311850421c75d5cdb2e96784a7c/crates/wasmtime/src/runtime/component/concurrent.rs#L1459-L1469
https://github.com/bytecodealliance/wasip3-prototyping/blob/f5fefc6f4e63b311850421c75d5cdb2e96784a7c/crates/wasmtime/src/runtime/component/concurrent.rs#L1561
specifically, this future never gets polled again after returning Poll::Pending
, even though oneshot::Receiver
poll should have registered the wakup event once the oneshot::Sender
is dropped (I have verified that it indeed gets dropped) https://docs.rs/tokio/latest/tokio/sync/oneshot/struct.Receiver.html
@Roman Volosatovs dummy_waker
is only used in first_poll
and poll_and_block
, and those are only used for guest->host calls in host.rs
, meaning dummy_waker
should never be used for spawn
ed tasks. Instead, spawn
ed tasks are pushed directly to ConcurentState::futures
, and that is always polled with the Context
received from the embedder's executor (i.e. Tokio in this case).
My guess is that we're not polling ConcurrentState::futures
between when spawn
is called and when we suspend the fiber and return control to the executor, i.e. the spawn
ed future is not being polled at all. Or have you already verified that it's being polled at least once?
Happy to pair today when you're available. Meanwhile, I'll take a closer look.
It gets polled once for sure. I'll be available in a few minutes, we could jump on a call if you're free
https://meet.google.com/sta-snii-ffj
Ok, after looking at the code, I think you're right that the issue is related to the guest synchronously dropping the listening tcp socket, which in the host drops the oneshot::Sender
, but the guest code keeps running after the drop call returns, before the host has a chance to poll ConcurrentState::futures
again.
So we get the trap because the guest tries to bind again before the old socket has really stopped listening.
I'm wondering if there's a way we can make HostTcpSocket::drop
finish everything it needs to do (including waiting for the spawned future to complete) before returning.
It's like the spawned future needs to send a confirmation back to HostTcpSocket::drop
indicating it's done.
For my understanding then, are you saying the observed behavior is a race condition effectively?
Would the future eventually get polled on it's own or it does require something to trigger the poll?
Kind of, except that it's 100% deterministic.
yes, I think the future would be polled if the guest were to yield between the drop and the bind
Want to jump back on the call to chat for a bit?
or if (like I was saying), the host drop implementation were to wait for confirmation before returning
yup
https://meet.google.com/sta-snii-ffj
actually, following-up to our discussion just now, I might try taking the task out from the drop directly, by wrapping the ListenTask
itself
It does seem like a generally useful pattern, but I'm not actually sure if the abort handle should be responsibility of the runtime if we can figure that out by wrapping the task directly
Makes sense; want to try that before I start messing with the runtime code?
sounds good, let me try a few things!
diff --git a/crates/wasi/src/p3/sockets/host/types/tcp.rs b/crates/wasi/src/p3/sockets/host/types/tcp.rs
index d2b43129b..db000c471 100644
--- a/crates/wasi/src/p3/sockets/host/types/tcp.rs
+++ b/crates/wasi/src/p3/sockets/host/types/tcp.rs
@@ -4,8 +4,8 @@ use core::net::SocketAddr;
use core::pin::{pin, Pin};
use core::task::Poll;
-use std::net::Shutdown;
use std::sync::Arc;
+use std::{net::Shutdown, os::fd::AsRawFd as _};
use anyhow::{ensure, Context as _};
use io_lifetimes::AsSocketlike as _;
@@ -769,7 +769,8 @@ where
.delete(rep)
.context("failed to delete socket resource from table")?;
match sock.tcp_state {
- TcpState::Listening { abort, .. } => {
+ TcpState::Listening { abort, listener } => {
+ unsafe { libc::close(listener.as_raw_fd()) };
eprintln!("DROP LISTENER");
_ = abort.send(());
Ok(())
just as a very quick PoC, since I did not really find a nice way to close the listener yet, this seems to have fixed the issue.
Of course, I got
fatal runtime error: IO Safety violation: owned file descriptor already closed
but I guess that's expected :D
I tried also wrapping the listener in an Option<std::sync::Mutex<TcpListener>>
, but that got pretty awkward pretty quick although it potentially can work as well
Yeah, I figured we'd need to use a Mutex
somewhere given that the futures need to be Send + Sync
ah, actually, we could also use tokio::spawn
directly and communicate with the BackgroundTask
via a channel
that way the drop
can just use tokio task machinery directly
yeah, I could see that working
hmm, it mostly works, hitting some non-determinism, want to take a look?
sure, same google meet link?
yes, will be there in a sec
you people are rocking
took me 3 different channels of 3 different types to get there, but got tcp_bind
test consistently working https://github.com/bytecodealliance/wasip3-prototyping/pull/17 @Joel Dice
Yikes, that drop
implementation is complicated. This should make it a lot simpler:
type Spawned =
Arc<Mutex<Option<Pin<Box<dyn Future<Output = Result<()>> + Send + Sync + 'static>>>>>;
/// Handle to a spawned task which may be used to abort it.
pub struct SpawnHandle(Spawned);
impl SpawnHandle {
/// Abort the task.
pub fn abort(&self) {
*self.0.lock().unwrap() = None;
}
/// Return an [`AbortOnDropHandle`] which, when dropped, will abort the
/// task.
pub fn abort_handle(&self) -> AbortOnDropHandle {
AbortOnDropHandle(self.0.clone())
}
}
/// Handle to a spawned task which will abort the task when dropped.
pub struct AbortOnDropHandle(Spawned);
impl Drop for AbortOnDropHandle {
fn drop(&mut self) {
*self.0.lock().unwrap() = None;
}
}
I should have a PR up by the end of the day.
Actually, the SpawnHandle
/AbortOnDropHandle
stuff is a bit trickier than I realized. I need to not just clear the Option
but also wake the waker (i.e. the FuturesUnordered
waker, in this case) so it knows to poll the wrapper future one last time and remove it from the set. Otherwise the wrapper future will be stuck in the FuturesUnordered
forever.
It's doable, just need to spend some more time on it.
Got all TCP tests working https://github.com/bytecodealliance/wasip3-prototyping/pull/20
you people POUNDING!
@Joel Dice there's a merge conflict in https://github.com/bytecodealliance/wasip3-prototyping/pull/18 currently, however https://github.com/bytecodealliance/wasip3-prototyping/pull/20 can be merged cleanly and it increases test coverage.
How about we merge https://github.com/bytecodealliance/wasip3-prototyping/pull/20 now and I rebase https://github.com/bytecodealliance/wasip3-prototyping/pull/18 on the resulting main
?
pushed a rebased version to https://github.com/bytecodealliance/wasip3-prototyping/tree/dicej/another-bindgen-refactor-rebase
Yup, sounds good; I just merged #20
Awesome, should I push to your branch then?
Sure, thanks!
pushed. just a heads-up, I'm noticing some flakiness in tcp_connect
test, let's see if switching to the new spawn functionality fixes that. Otherwise, we can ignore it for now
FYI, in the past when I've seen non-deterministic test behavior, I've been able to make it more consistent by adding/removing/adjusting sleep
calls in host functions (e.g. artificially delaying tcp socket operations), so if we still see issues even with the new spawn feature, that might help with debugging.
I cancelled the CI run, if I'll manage to fix it, I'll push a fix within next 1.5h, otherwise I'll push an ignore for the flaky ones
@Joel Dice the fix for the race condition we discovered was fairly trivial https://github.com/bytecodealliance/wasip3-prototyping/pull/23
Note, that I cannot actually use the abort handle machinery out-of-the-box just yet due to the need of explicitly closing the stream write handle. Let's chat about it later today at the status update!
Hey @Pat Hickey wanted to get your opinion on https://github.com/WebAssembly/wasi-http/pull/154
After talking with Joel at the P3 sync, we want to have body.finish
not async (since it doesn't have to be, it's simply ferrying along the trailers), but right now creating a new future requires access to the store which currently is only doable from async
functions.
This somewhat inconvenient, but brought up the idea that the lack of trailers and trailers that are present but empty are different -- which means option<future<trailers>>
might be a reasonable thing to put on the table. Right now, to make future<trailers>
work, if there are no trailers, we create a new future to essentially and make sure to write nothing.
I think you were against option<future<trailers>>
, am I remembering correctly?
To elaborate on what Victor said above: there's currently a limitation in wasmtime-wit-bindgen
's Component Model Async support such that sync functions can't create streams or futures, but that's a temporary limitation we plan to address soon, so no need to design around it.
thanks for the explanation, I'm fine with what you came up with
Thanks! Just to make sure I fully understand -- are you going for creating the future here (no option
, i.e. returning future<trailers>
no matter what) or returning an option<future<trailers>>
?
If the former, a review on https://github.com/WebAssembly/wasi-http/pull/154 would be appreciated! If the latter -- I'll get to making that change ASAP
following the discussion from yesterday, I've replaced the Tokio task/channel magic by AbortOnDropHandle
machinery in https://github.com/bytecodealliance/wasip3-prototyping/pull/32, which does not rely on multi-threading anymore.
I think we need to talk a bit more about send
and connect
.
cc @Alex Crichton @Joel Dice
UDP implementation and tests: https://github.com/bytecodealliance/wasip3-prototyping/pull/33 - with this, we have latest wasip3 wasi:sockets
draft fully implemented and tested using adapted wasip2 test suite. Will be starting with wasi:filesystem
tomorrow.
I got sidetracked yesterday fixing the soundness issue Alex raised in wasmtime-wit-bindgen
's concurrent_imports
option, as well as restoring support for &mut T
forwarding impl
s so we can generate add_to_linker
functions again. The good news is I got it working (should have a PR up today). The bad news is I didn't work on anything else. These are on my TODO list, though:
{Stream|Future}{Writer|Reader}
on drop@Roman Volosatovs when you switched to AbortOnDropHandle
, how did you handle closing the StreamWriter
? Or is that still a TODO blocked on the "auto-closing {Stream|Future}{Writer|Reader}
on drop" item I mentioned above?
I actually did not make any changes in that regard - there are two tasks, both spawned via the store. The tasks communicate over an mpsc.
The producer side deals with all the OS-level stuff, I/O etc. , e.g. it owns the file descriptor (e.g. TCP stream) and it does not use the Accessor
at all.
The consumer side owns the StreamWriter and is responsible for closing it once mpsc is closed and/or an unrecoverable error is encountered.
The abort handle of the producer task is stored as part of the resource state and so it's dropped once resource is dropped, triggering, e.g. close of a TCP stream FD.
The consumer task will eventually(likely on next poll) find out that the producer is closed and close the StreamWriter
Ah, that makes sense -- thanks for the explanation.
@Alex Crichton @Victor Adossi I've been thinking some more about how to support creating futures and streams in sync host functions, and the easiest thing for the time being is to just tell wasmtime-wit-bindgen
to generate an async function if it needs to create a future or stream (even if it doesn't need to await
) -- which is exactly what Victor's PR already does. The guest can call that synchronously and everything will work as it should.
Alex and I have ideas about making both the async and sync bindings more flexible and ergonomic in the future, but for the time being I think it's fine to tell the binding generator to generate an async function any time the implementation needs to await
or create futures or streams.
Hello :wave: this is probably a silly question but could someone explain what's the reason for changing the cli/stdio
interface from:
getStdout()->output-stream
and getStderr()->output-stream
tosetStdout(stream)
and setStderr(stream)
?In the component model, the stream
type always represents the readable end of a stream; there's no equivalent to output-stream
for the writable end; which is to say that the component which creates a stream
using stream.new
can only give away the readable end and keep the writable end for itself. So the only way to hook a stream
up to stdout or stderr is to create that stream
using stream.new
, hold on to the writable end, and pass the readable end to e.g. setStdout
.
The setStdout
/setStderr
API does raise an interesting question, though: If I compose two components and they both call setStdout
, will one overwrite the other? I.e. is only one of the components going to be able to write to stdout?
I imagine the host could maintain some kind of magic per-component state so that they don't overwrite each other, but I'm not sure that would be virtualizable by a guest component.
@Joel Dice @Alex Crichton following our earlier discussion: https://github.com/WebAssembly/wasi-sockets/pull/119
I'll also address the listen/receive stream capturing the lifetime of the socket resource later. (on that note, lazy question: does Rust guest wit-bindgen
actually support this correctly, or does a returned stream capture the lifetime of the resource borrow the method was called on, in other words, are returned streams/futures 'static
?)
I just opened an issue about the set-stdout
/set-stderr
question I raised above: https://github.com/WebAssembly/wasi-cli/issues/64
Joel there's changes in wasm-tools now to update various intrinsics and line up with the spec, do you want to coordinate about landing this within wasmtime? I ask because jsturtevant is working on a bugfix at wasm-tools#2076 which will unblock wasi-tls landing (this is completely unrelated to async), but I bring this up as that'll require updating wasm-tools.
The literal update is pretty modest but I suspect the actual changes to trampolines/tests to be a bit more invasive. In particular core wasm signatures are changing and things like waitable sets now need to be both created and destroyed around usages too
Getting that upstream sounds fine to me and won't disrupt the WASIp3 work until we try to merge it into the wasip3-prototyping
repo, but we don't need to do that right away. So no need to delay the former, and I'm happy to help if anything comes up.
ok sounds good, I'll forge ahead when the update is necessary
although same question, but this time from wit-bindgen -- should I be careful about updating there or "update at will"?
I'd say update at will; we can delay updating the dep in the wasip3-prototyping
repo as well.
@Joel Dice https://github.com/bytecodealliance/wasip3-prototyping/pull/40
test scenario::round_trip::async_round_trip_stackless_sync_import has been running for over 60 seconds
looks like CI caught it as well
filed an issue https://github.com/bytecodealliance/wasip3-prototyping/issues/41
question on CI: there's a copy of main.yml
but trimmed down -- is this because when I originally made the repo I disabled main.yml
(named "CI") and it was thought we needed to copy that to get something running?
I'm discovering in https://github.com/bytecodealliance/wasip3-prototyping/pull/43 that there's some sort of mixture or something going on where CI isn't actually running right now
Probably. In any case we're using wasip3-prototyping.yml (which we copied and modified) for PRs
or rather some jobs aren't running
is the goal to basically match the wasmtime repo?
if so I can set that up
(merge queues and all)
kind of, except we're skipping cargo vet
for now
just vet? anything else?
That would be awesome -- I set up the copy there to mostly do the same thing as main but hopefully run slightly faster without tests that we wouldn't impact
and I think we added another test job for async specifically
the async tests aren't run otherwise?
maybe not? I haven't looked closely :shrug:
Yep, and another job for async specifically, mostly for visibility, the original goal was to only run those but figured there could be downstream effects
ok I'll work to try to figure out what's going on and see if changes are necessary
the async tests are under misc
I'm not sure if that's covered under test by default but entirely could be!
Yup happy with any changes there -- having merge queues and any tests that are necessary re-enabled would be nice
To be clear, the extra job Victor added runs cargo test -p component-async-tests
, but there are other async-related tests elsewhere (e.g. bindgen and WA(S)T tests)
I would expect cargo test --all
to run the component-async-tests
, but maybe it wasn't for some reason?
I don't even know if CI runs cargo test --all
Ah it does by default, run-tests.sh
uses --workspace
, wasn't necessary for me to use -p component-async-tests
by default, but did like having it broken out like fiber. Certainly happy with having it all get back to a state more similar to upstream
ok in the main merge I'm deleting wasip3-prototyping.yml, I disabled cargo vet
for anything outside of bytecodealliance/wasmtime (I'll land that upstream) and I'll work through the remaining failures
I'll have a follow-up which splits out the component-async-tests package to its own CI bucket which should still run on PRs by default.
I'm also enabling the merge queue. For now it's set as "merge & commit" as the strategy, but I'm only going to leave that for this one PR. The merge strategy is defined at the repo level, not the PR level, so my plan is to change that to "squash & merge" like wasmtime has after this merge lands
Squash & merge would complicate the eventual upstream contribution - I've set the strategy to "rebase" to allow for cherry-picking commits later
If others prefer squashes - that's fine, but I'd like to be able to keep merging my WASI PRs with rebase
oh sure I can leave that turned on yeah
it'll mostly just be annoying that if a merge from upstream back to wasip3 happens we'll have to update merge queue settings around that
Giving it a bit more thought, I don't think I actually mind squash merges at this point, my upcoming PRs should be rather small/well-scoped, so rebase or squash should not really matter any more, the "incremental" commits I cared about are already in main
ok set to squash + merge and the next PR should go through smoothly in theory
Looks like CI is broken https://github.com/bytecodealliance/wasip3-prototyping/actions/runs/13586320199/job/37982051541
This occurred twice trying to merge https://github.com/bytecodealliance/wasip3-prototyping/pull/46
I'll go ahead and merge manually on CLI
wasi:filesystem
https://github.com/bytecodealliance/wasip3-prototyping/pull/38
going to investigate the flaky UDP test and start wasi:cli
implementation
ci should hopefully be fixed here
awesome, thanks @Alex Crichton !
Heads up that I had a flash of inspiration last night regarding improving the{Stream|Future}{Writer|Reader}
host API ergonomics, and the implementation is looking good so far; aiming to have PR up by EOD. In short, you'll be able to read, write, and close either end of a stream or future without needing a StoreContextMut
. Ideally you wouldn't need one for creating streams or futures either, but that's going to be more difficult to achieve, so I'm punting on that for now.
Here's the PR: https://github.com/bytecodealliance/wasip3-prototyping/pull/50
@Roman Volosatovs I haven't added a way to get a Future
that resolves when the other end of a stream/future is closed yet, but I'm planning to do that Monday.
I'm traveling today, so won't be able to make it to the status update call. On my way I'll be working on porting the rest of the p2 tests and finishing up the wasi:cli
.
@Joel Dice do I understand correctly that you already have wasip3 wasi:http
implementation complete or should I work on that as well?
wasi:cli
https://github.com/bytecodealliance/wasip3-prototyping/pull/47
assuming that wasi:http
is already complete, all of WASIp3 is implemented in the prototyping repo.
I'll be focusing on testing, refactoring and bug fixes now. hopping on a plane soon
Nice work!
All I've done for wasi:http
is sketch an implementation for testing purposes, based on a now-out-of-date draft. @Victor Adossi has done some work to partially sync it with the latest draft, but not entirely.
In any case, that implementation was only ever meant for runtime testing and examples; it has a bunch of TODOs, and I haven't tried to get the existing wasmtime-wasi-http
tests ported over. So if you have bandwidth to work on any of that, it would certainly be appreciated!
Ok, I'll work on HTTP then!
I'll also have to dip out a bit early from the sync to run an errand too, I've mostly been working on the low-level tooling of wasmparser/etc sync'ing that with the spec and then working my way up to wit-bindgen with the goal of getting a C generator in mind (but also understanding the Rust generator at the same time)
It appears that wasm32-wasip2
target is not currently supported by Rust wasip3 components, is that right?
I'm getting:
= note: error: failed to parse core wasm for componentization
Caused by:
0: decoding custom section component-type:wit-bindgen:0.39.0:wasi:cli@0.3.0:command:encoded world
1: invalid leading byte (0x66) for component defined type (at offset 0xda)
when trying to build a component with cargo build --target wasm32-wasip2 --release
on:
cargo 1.84.1 (66221abde 2024-11-19)
wit-bindgen e0d2b889411acf60738dcd976ed5373f7a81e651
Ah, yeah, I guess that makes sense given that wasm-component-ld
is using an older version of wit-encoder
. We'd need a build with an updated dep.
so for now we should build for wasm32-unknown-unknown
and componentize manually then?
or wasm32-wasip1
we're building with wasm32-wasip1 and adapting right now in the build scripts
wasmtime run
runs wasip3 components now https://github.com/bytecodealliance/wasip3-prototyping/pull/60 :)
as a heads up I'm going to start the process today of updating wasm-tools everywhere. Stuff's probably gonna be broken for awhile.
:thumbs_up: BTW, I'm finishing up addressing your feedback on https://github.com/bytecodealliance/wasmtime/pull/10106 and plan to merge it when I'm done, in case you want to wait for that.
will do
tune in at 7 for today's episode of "race to rebase"
@Alex Crichton #10106 is merged
@Alex Crichton I'll merge upstream Wasmtime into wasip3-prototyping
unless you've already started that.
After that, I'm planning to work on https://github.com/bytecodealliance/wasmtime/issues/10338 and then host support for waitable sets. @Alex Crichton are you planning to work on guest (i.e. wit-bindgen
) support for waitable sets?
I haven't started the merge feel free
You might have to temporarily disable tests I don't think my wit bindgen tests are sufficient just yet
@Joel Dice https://github.com/bytecodealliance/wit-bindgen/pull/1197
the fix was pretty simple, as discussed.
btw, I noticed that wit-bindgen in main
appears to be broken, and I had to roll back to the last-known-good commit for testing this in my wasi
fork. I'll file an issue later upstream, but for now it can be reproduced with https://github.com/rvolosatovs/wasi/tree/feat/p3 and using latest main
of wit-bindgen
. Just thought it's important to call it out
Yeah, I hit that too. Fixing it now
oh is it the double fn new
problem?
yup
I ran into that on https://github.com/bytecodealliance/wit-bindgen/pull/1192 as well, I can also split out the fix
it wasn't caught on CI because it's only a problem when things are compiled to wasm which the codegen tests aren't
I think for the time being we need to test async-related wit-bindgen changes manually against wasip3-prototyping
to catch stuff like that. Not ideal (and maybe not practical when stuff is out of sync), but something to aim for, at least.
wasip3-prototyping
is the only place we have runtime tests involving guest bindings for now
yeah I forgot to do it on that change :(
No worries; looking forward to having runtime tests in the wit-bindgen repo so we don't have to remember anymore.
Making progress on the merge, but now hitting this:
thread 'main' panicked at crates/test-programs/artifacts/build.rs:184:10:
module can be translated to a component: failed to decode world from module
Caused by:
0: module was not valid
1: failed to resolve import `$root::[task-backpressure]`
2: no top-level imported function `[task-backpressure]` specified
No surprise; wit-bindgen
hasn't been updated to match the latest wit-encoder
. @Alex Crichton if you haven't already started that, I can dive in.
no please do
but I also don't mind taking this
I'm studying async tests right now
this is also making me rethinking if we should organize things a bit different w.r.t wit-bindgen and wasip3-prototyping,b ut that's for the side
Well you are faster than me at pretty much everything, so feel free to take it; I could work on the task.return options stuff while you do that.
wanna share your p3 branch-in-progress?
yup, just a sec
https://github.com/bytecodealliance/wasip3-prototyping/pull/68
oops, just realized I left a [patch]
in the Cargo.toml to point to a local wit-bindgen; I'll revert that part
(although it might be useful for what you're doing :)
heh that was step 1 for me lol
I've hit a wit-component bug, will take a moment to fix
@Alex Crichton can you confirm that I'm remembering this correctly: In order to make the runtime task.return
validation a single comparison, we're going to intern the dfg::CanonicalOptions
in ComponentTypesBuilder
(call it e.g. CanonicalOptionsIndex
), then pair that with the TypeTupleIndex
we're already using to intern the component return type, producing a third interned thing (call it e.g. TaskReturnIndex
) that represents the pair. Then at runtime, we compare the two TaskReturnIndex
es and trap if they don't match. Does that sound right?
Eventually yeah, but for now it's probably easier to just shepherd along options individually and check then all
Already started down the intern path :). Shouldn't be a lot of work; just wanted to make sure it wasn't the wrong approach.
Be careful to only intern a few things though
Since the spec says we only check a few minor options
ok, so create a new struct with only the relevant options, then
I also realize we need to confirm what function equality means in the spec because interning misses runtime reexpots
But yeah new steuct
Er memory equality I mean, but functions too with realloc
ok, so "unusual" components might fail the the runtime test even though they're actually following the rules?
but we'll still definitely trap any components that aren't following the rules?
Right
Would RuntimeMemoryIndex
, RuntimeReallocIndex
, etc. help at all with the false traps?
No, that's what you're interning over basically
We basically treat core modules as black boxes right now and don't detect reexports
Should I be doing the interning in ComponentTypesBuilder
? It currently doesn't know anything about memories or reallocs; those appear to be interned in LinearizeDfg
instead. Is it okay to intern a struct that has e.g. a RuntimeMemoryIndex
field in a ComponentTypes{Builder}
?
Probably in dfg
Alex Crichton said:
I've hit a wit-component bug, will take a moment to fix
the rabbit hole is going deeper...
Let me know if I can help with anything.
nah it's all preexisting issues in wit-component
tl;dr; the --dummy
support didn't emit intrinsics for things like futures/streams/task.return/etc, meaning that was never exercised in fuzzing, and the buggy module originally in wasip3-prototyping was exercising this
so I first updated --dummy
to import all async intrinsics, and now that's turning up at least one other independent bug in wit-component
so need to sort through these all basically
this all has to do with type encoding and making sure things are encoded in the right spot, and that's all super subtle and I always forget about it and just hope the fuzzer finds things (which it is now)
Yeah, I've been eager to start (and also dreading) fuzzing all the things; glad we're finally starting to do that.
oh this is the fuzzer already going in roundtrip_wit
in wasm-tools, and it's been doing async things for awhile
this is just expanding the set of things it does
ok 4 other bugs fixed and the fuzzer now reached the original one I started with, yay!
I've done the task.return
type/options interning we discussed. It compiles, at least. Will test once the merge is complete.
this is going to take me a second, I pushed up my work-in-progress to your PR
the next step is adding struct WaitableSet
to async_support.rs
. Not hard but the fuzzer is finding more bugs in the background, you want to take over updating wit-bindgen from here?
Yup; I'll start on that tomorrow morning. Thanks for getting it this far!
Nevermind, I started it this evening. I've got wit-bindgen
generating code to use the waitable-set
intrinsics, and wit-component
is happy with that, but wit-component
is now choking on https://github.com/bytecodealliance/wasip3-prototyping/blob/main/crates/test-programs/src/bin/async_transmit_callee.rs:
thread 'main' panicked at /home/dicej/.cargo/git/checkouts/wasm-tools-3a47d954f2d24d2f/311b6fa/crates/wit-component/src/encoding.rs:557:47:
IndexMap: key not found
I'll try debugging it; might be related to one of the issues the fuzzer found?
yeah I found another issue or two
update the wit-component dep
via cargo update -p wit-component
and I think it should pick up a fix I just pushed
and if it still panics I'll dig in more
same panic
guh ok if you can push your work I'll dig in
oh wait
fuzzer just hit it
time to spelunk
I'll push what I have, anyway. It's just a stub so far; not merge-able.
oh wait maybe I have a bad rebase
I broke a preexisting test
ok try pulling again
looks good; hitting another issue now, but an expected one
nice
gogo fuzzers
nice, got cargo clippy --workspace --all-targets
green now
the next gauntlet: acutally running the test heh
I know; I kind of want to make that a problem for Tomorrow Joel
Today Joel thanks you for your service
about half of the integration tests are passing; not bad. The rest are dying with cranelift signature mismatch errors; presumably something wrong with in compiler/component.rs
or fact/trampoline.rs
maybe
either way, it's Tomorrow Joel's problem
All the integration tests are now passing. Just some WAT tests failing now; should be easy to fix.
@Alex Crichton How would you feel about merging https://github.com/dicej/wit-bindgen/commit/b518c80c3707e5df8e890acb0444cf9f2588a8fc (which hard-codes the waitable-set
handle to zero, since Wasmtime is currently ignoring it) as a temporary measure just to get things working again? I'll follow that up with proper waitable-set
PRs for both wit-bindgen
and wasip3-prototyping
, but I don't want to block getting the merge into main
while I'm doing that.
I'm hitting the unreachable!
in https://github.com/bytecodealliance/wit-bindgen/blob/b4d62a45c2c0d697a0ce88f0686fe05a7cba4805/crates/guest-rust/rt/src/async_support/stream_support.rs#L342-L349
thread '<unnamed>' panicked at /Users/rvolosatovs/.cargo/git/checkouts/wit-bindgen-b65103eb40fd7b8c/554b482/crates/guest-rust/rt/src/async_support/stream_support.rs:347:48:
internal error: entered unreachable code
has anyone seen it before? Any ideas what might be going wrong?
@Joel Dice happy to jump on a call to look at it together if you're available
That means the guest has received a stream
handle it thinks it already owns. Yeah, a call would be good.
As usual, if you can provide a (somewhat) minimized test case that would be ideal.
are you free now?
yup
I'll send you a link in a few minutes
How would you feel about merging https://github.com/dicej/wit-bindgen/commit/b518c80c3707e5df8e890acb0444cf9f2588a8fc
sounds reasonable to me yeah
my hope is to soon start digging more into refactorings for the rust guest bindings with more test cases using wit-bindgen test
BTW, I just chatted with Luke, pointing out that a realloc
option for task.return
doesn't make sense because it only needs to lift values. He's going to remove that from the spec, and we may need to tweak the validation rules in wasmparser to match.
right!
that reminds me to open an issue about memory equality
I posted https://github.com/bytecodealliance/wasm-tools/pull/2086 for removing the realloc
option (shouldn't affect much) and https://github.com/WebAssembly/component-model/issues/464 for clarifying memory equality (which affects what sort of interning we can do)
@Alex Crichton Roman can provide more detail, but the issue he ran into above is the one you pointed out last week: writing to a "local" stream, then giving the read end away before that write completes. He's going to work around it, but now we have a real-world example.
I guess that should be easier to fix than the opposite case (e.g. writing to a "remote" stream and then receiving the read end before the write completes).
makes sense yeah, and once we have things lined up again I'll start working on tests and fixes for these issues
I'm almost done fixing the remaining tests, then will push what I have.
All tests are green; I've pushed what I have to your draft wit-bindgen PR and my draft wasip3-prototyping PR. Now reviewing https://github.com/bytecodealliance/wasm-tools/pull/2084
Coming back to the task-return
interning and runtime validation thing: I have all but two tests passing. The two failures appear to be invalid components generated by wit-encoder
such that the async-lifted export has a memory
option (and indeed should have one), but the corresponding task.return
does not. My guess it's due to the return type not needing a memory
but the parameter types needing one. Will debug.
ok that's a good point, and probably is more spec updates to have
basically a "match" saying that a non-present memory is ok to have when the original intrinsic had a memory
b/c task.return validation will already require memory
if it's needed and otherwise let it lie if it's specified and not needed
(which I think this may be the nail-in-the-coffin for the interning strategy)
Also, given Luke's response on the upstream issue, interning definitely won't work here
spec-wise it's the wrong behavior
so I need to do three comparisons, then? type, memory, and string-encoding?
we could technically intern type+string-encoding, but I don't think that's worth it, so yeah 3 compares
just to clarify: if either or both the task.return
or the lift specify a memory that's not needed, that's ok? I.e. Wasmtime will need to do its own check to see if the memory is really needed based on the return type and set them both to null e.g. during instantiation so that we don't get spurious comparison failures?
I think no, what I'd expect is that if they're specified they're asserted to be equal
if they happen to not get used, then they just happen to not get used
ok, so wit-encoder
does need to be fixed given that it's generating a lift where it's specified and a corresponding task.return
where it's not
and it's not wasmparser's responsibility to check that, because it can't in general (hence the runtime check)
right on wasmparser can't check this, but why would wit-component change?
b/c the lift may need it for arguments but not for the results, so it's valid that lift
has it but task.return
doesn't
(and this is where we should update the spec where if task.return
is missing it and lift
has it then that's ok)
ok, so then it's not just a simple comparison at runtime is it? One will be null and the other will be non-null, and yet it shouldn't trap because we're saying that's okay.
yeah
personally I wouldn't worry too much about the cost of these checks, a few conditionals compared to the boxing/copying at the moment is where we're at
Yeah, not worried about the cost, just want to make sure I've got it correct.
so if the task.return
memory is null and the lift
memory is non-null, then we say that's okay because wasmparser
would have rejected a task.return
that was missing a memory but needed it, correct?
correct
(or at least that's what I'm envisioning)
@Alex Crichton is there a way I can pass a reference to a memory to a intrinsic from a fused adapter module such that the host receives a *mut VMMemoryDefinition
?
no the host will receive Runtime...Index
and translate that using *mut ComponentInstance
to the definition
Follow up question: If I have a Option<MemoryIndex>
can I turn that into a Option<RuntimeMemoryIndex>
somehow? Or is MemoryIndex
what I actually want anyway?
no you want RuntimeMemoryIndex
, and if you plumb everything through dfg/etc I'd expect that to pop out
so maybe some layer forgot to do a translation?
AdapterOptions
in fact.rs
has a Option<MemoryIndex>
or well, at some layer you'll have like a CoreExport
, and somewhere during linearization that ...
hm adapters
gimme 5 then wanna pair on this?
the EOF parsing the component issue I've mentioned in the call turned out to be wasm-tools
version mismatch after all.
One thing I also ran into was multi memory being required now where it wasn't before. Not sure how new that is, but moving from one commit of wasip3-prototyping to another it changed
(i.e. Before, my jco branch was using WASM2 | COMPONENT_MODEL | COMPONENT_MODEL_ASYNC and I needed to add MULTI_MEMORY)
hm that I'm not entirely sure where that would be coming from...
As far as the multi-memory thing goes that MIGHT be unrelated, so I wouldn't pay too much attention to it for now, still going through things on the jco side, there are still a few tests failing there.
But if you want to :rabbit: :hole: :
Update commit
Adding multi memory just a little bit later
unfortunately there's no green test run before the 227.1 update but maybe once the dust settles I'll go back and bisect to get at it properly -- will avoid squashing until then
I've briefly mentioned it during the call earlier today, but I'll update in the thread as well.
I've managed to make Rust https://github.com/launchbadge/sqlx crate run using wasi:sockets@0.3.0-draft
, in particular I was able to make the Postgres todos
example in the sqlx
repo work with real Postgres instance running in a container.
The code is available here: https://github.com/rvolosatovs/sqlx/tree/feat/wasip3
The component can be run using the Wasmtime CLI from this PR: https://github.com/bytecodealliance/wasip3-prototyping/pull/60
This, of course, was not meant to be a production-ready implementation, but really just a validation, and it looks like we're in a pretty good state.
I've recorded a video, but I'll be happy to write down the step-by-step instructions for running this if anyone is interested (please ask if you'd like that!)
Screen Recording 2025-03-07 at 17.01.24.mov
@Alex Crichton It turns out we can't postpone supporting context.{get|set}
in wasm-tools
, wit-bindgen
, and wasmtime
after all. That's now the only way to save the guest-side task state (i.e. wit-bindgen-rt
's FutureState
) when returning from an async-with-callback-lifted export. We used to return a pointer to that state so the host could pass it back to us when calling the callback, but now we have to return a CallbackCode
ORed with a waitable-set
, and since we're avoiding using multivalue, the only way to stash and retrieve our task-local state is with context.{get|set}
.
Anyway, assuming you haven't started adding support for those intrinsics anywhere, I'm going to start adding them, since I'll be blocked otherwise.
I haven't started yet no
feel free
makes sense though I see
Continuing our discussion today of close-with-error -- I opened https://github.com/WebAssembly/component-model/issues/472 on this topic since I think the current behavior (assuming I'm understanding everything right) is hazardous as trapping conditions depend on what other components are doing, when I'd prefer to have a trapping condition be a purely local decision within a component
along the lines of "maybe can change things?" I'm also curious in that issue if it's possible to in theory remove error-context
altogether (or basically consider it a WIT extension)
but I know I'm missing context, so I'd appreciate it if folks could help fill in the gaps
Ok another follow-up here on error-context: https://github.com/WebAssembly/component-model/issues/474 -- basically I'm curious if anyone has thoughts/objections on delaying the error-context
type to after 0.3.0 entirely.
another issue -- https://github.com/WebAssembly/component-model/issues/475 -- with a change to futures/streams where they're likely to be represented in the future with two handles, not just one, and how we're still not 100% certain how this would all work
In case anyone is interested in discussing wasi:http
error handling, we have a call scheduled with @Joel Dice and @Luke Wagner in 30 minutes to discuss https://github.com/WebAssembly/wasi-http/issues/164 and https://github.com/WebAssembly/wasi-http/issues/163
meeting link: meet.google.com/jdu-rpka-gfk
Feel free to join, especially, if you're well familiar with HTTP libraries in various guest languages
apologies for the short notice, I've been hoping to sum this up and open for discussion earlier this week, but I've been out sick for last 2 days
@Till Schneidereit @Luke Wagner I've moved away from the auto-close semantics in wasi:http
, proposed in https://github.com/WebAssembly/wasi-http/pull/162, instead throwing an error explicitly if live handles exist. Please take a look!
@Luke Wagner @Till Schneidereit I've updated https://github.com/WebAssembly/wasi-http/pull/162 yet again, would appreciate a review! I'll proceed with the implementation
Great work on this!
FYI, I'm doing a pretty deep refactor of concurrent.rs
and futures_and_streams.rs
in wasip3-prototyping
, having realized yesterday that most of the state I had put in Store
actually belonged in ComponentInstance
. I don't anticipate any major public API changes, so it shouldn't affect the wasmtime-wasi(-http)
work, but please avoid making changes to the aforementioned files until my PR is merged (or at least chat with me first).
I'm running late to the status meeting, feel free to start without me
Hey all, rough notes from the status meeting are below (Hey @Joel Dice unfortunately I missed your update so please feel free to fill it in :bow:):
2025/03/31 P3 Sync Meeting Notes
thanks for taking notes!
Thanks for taking notes! My update:
Store
to ComponentInstance
, fix wasmtime-wast
, remove Sync
bounds on Future
s, etc.StreamWriter
(i.e. returning unwritten items)Looks like I've run into another guest trap while working on wasi:http
https://github.com/bytecodealliance/wasip3-prototyping/issues/95
cc @Joel Dice
Thanks for opening the issue; I'll take a look when I get a chance.
that one might "just go away" once the wit-bindgen update is merged
Should I try pulling in some of the changes that you all were working on? Any chance https://github.com/bytecodealliance/wasip3-prototyping/pull/94 could help here?
I'll try rebasing and report
that's under the assumption the bug lies in the previous wit-bindgen code, which could also be wrong
Alex Crichton said:
that one might "just go away" once the wit-bindgen update is merged
I think there are still places in the host code that are too restrictive; we'll see.
I don't think #94 will make a difference (regarding the trap), but I could wrong.
ok @Roman Volosatovs I've merged main
with your branch
looks like http_0_3_outbound_request_content_length.rs
is failing because trailers fail to get sent (I guess something hung up unexpectedly?) and p3::wasi_http_proxy_tests
is not finishing so seems like a deadlock of some kind
Thanks Alex! Both are actually expected for now. For content length one we can't directly translate that from wasip3 - we'll have to call handle
to actually try and send a request/response, was just chatting about it with @Pat Hickey
For the incoming HTTP I'll still have to wire up the set of promises to drive guest tasks after return
@Joel Dice what's the status on https://github.com/bytecodealliance/wasip3-prototyping/pull/97 (I just approved it)? Is it ready for me to build upon in wasi:http
work?
I was planning to add some unit tests this morning, but I can go ahead and merge it and add the tests in a separate PR.
Also, I should probably add BytesBuffer::into_inner
and VecBuffer::into_inner
methods for retrieving the unwritten items if desired. Let me know if you need either of those; they're trivial to add.
I think incremental changes are good and since the WASI tests pass, that's good enough for me, so I'd say go ahead and merge
Done!
(well, queued anyway)
I definitely will need some way to go from BytesBuffer
to Bytes
, I'd prefer that to be a From<BytesBuffer> for Bytes
I'm a bit confused though as to why this conversion for BytesMut
would not care about offset https://github.com/bytecodealliance/wasip3-prototyping/blob/245d7e1c5a81d3db8c6d88e4f42a1634ba598f8d/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams/buffers.rs#L284 while Deref does https://github.com/bytecodealliance/wasip3-prototyping/blob/245d7e1c5a81d3db8c6d88e4f42a1634ba598f8d/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams/buffers.rs#L298 :thinking:
To be more specific, I'd want to get the Bytes
from BytesBuffer
, but I'd expect to only get the filled part of it, not the whole underlying buffer
The idea with BytesMutBuffer::into_inner
is that you'd call it when you just want to reuse the BytesMut
without caring which parts were written and which weren't. Whereas the Deref
impl gives you just the parts which haven't been written yet.
I could alternatively change BytesMutBuffer::into_inner
into fn into_parts(self) -> (BytesMut, usize)
if that would be useful.
(where the usize
is the offset to the start of the unwritten bytes)
With VecBuffer<T>
, it will be a bit different since we need to account for non-Copy
T
, so VecBuffer::into_inner
will actually need to do a memmove
to shift the unwritten items to the start of the Vec
before returning it.
I guess what I'm looking for is roughly https://docs.rs/tokio/latest/tokio/io/struct.ReadBuf.html#method.filled
Maybe there could be a way to just query the offset for BytesBuffer
? I'd then be able to call into_inner
and do the split myself
Sure. The main idea is that you might either only care about the unwritten parts or just want to clear reuse the whole buffer or both. As long as we support all those options, we should be good.
let n = buf.filled().len();
let mut buf = buf.into_inner();
buf.truncate(n);
that'd be the usage I'd imagine
btw, any way we could integrate with std::io::Cursor
here https://doc.rust-lang.org/nightly/std/io/struct.Cursor.html# ?
Does BytesBuffer
do cosiderably more than std::io::Cursor<Bytes>
could?
Maybe I'm not quite understanding. If you start with a BytesMutBuffer
with, say, 16 bytes, then you do a write
, and the write delivers 7 bytes, then offset
will be 7 and remaining(&self)
will return 9, and (currently) into_inner
will return BytesMut
with all 16 bytes. So what's the purpose of your truncate
call above? Do you want the result to have the first 7 bytes or the last 9 bytes?
Regarding Cursor
: I sort of assumed it only worked with Copy
payloads, but maybe I was wrong. Let me take a closer look.
I guess it doesn't matter, since Bytes
is only about u8
, so yes, I guess we could get rid of BytesMutBuffer
and BytesBuffer
in favor of Cursor<BytesMut>
and Cursor<Bytes>
respectively, but keep VecBuffer
since it supports moving owned values, which Cursor
doesn't AFAICT.
I was actually thinking about the other way round, so doing a read, I'm still getting familiar with this new API. What I need is a way to go from StreamReader<BytesBuffer>
to Stream<Bytes>
. It appears that naive BytesBuffer::into_inner
might give more bytes than the guest might have written
All BytesBuffer::into_inner
would do is give you back the entire Bytes
(i.e. including both the written and unwritten bytes), which is what Cursor<Bytes>::into_inner
also does if I'm reading correctly.
But you can always split a Bytes
if you only want a subset, of course
Oh, hold on. If you have a StreamReader
, then you don't need to mess with BytesBuffer
at all. BytesBuffer
is only for writing -- it doesn't make sense for (and indeed won't work with) StreamReader
.
Just use StreamReader<BytesMut>
.
yeah, that's what I'd guess
Alright, this makes sense to me then
er, I guess I mean StreamReader<BytesMut>
since there's no way to add items to a Bytes
If you have a moment, we could jump on a call and I could quickly show you the use case I'm working with
Yup; want to send me a link?
https://meet.google.com/hqc-yvwc-hgd
@Roman Volosatovs : This adds an Instance::promise
method for wrapping a Future
in a Promise
: https://github.com/bytecodealliance/wasip3-prototyping/pull/98
BTW, if all you're doing is calling Promise::into_future
the then later converting that back to a Promise
, you could avoid both steps and just leave it as a Promise
.
I'll start working on replacing Bytes{Mut}Buffer
with Cursor<Bytes{Mut}>
next
I'd prefer leaving it as Promise
, but that would prevent me from polling it multiple times in the guest task, since I have to move it to call into_future
That's the reason I'm suggesting the poll(cx, store)
method on Promise
, actually
btw, impls for std::io::Cursor<impl Buf{,Mut}>
could be even more useful https://docs.rs/bytes/latest/bytes/buf/trait.Buf.html https://docs.rs/bytes/latest/bytes/buf/trait.BufMut.html
and would allow using e.g. std::io::Cursor<Box<[u8]>>
as a buffer passed to write
I think promise.poll(cx, store)
would be equivalent to doing pin!(promise.get(store)).poll(cx)
that's what I'm doing now, but not sure if that breaks any runtime assumptions
e.g. is this "cancel safe"?
I think it should be fine; let me know if you see anything weird.
Yes, should be cancel safe, although that partly depends on the underlying Future
wrapped by the Promise
. All the existing public APIs that return Promise
s wrap cancel-safe Future
s AFAIK.
if I keep the promises around, looks like I can do
pin!(store.with(|mut view| contents_rx.get(view))).poll(cx)
in guest tasks rather than using into_future
where contents_rx
is a promise
is this a bad idea?
I could also add a Promise::get_mut() -> Pin<&mut dyn Future ...>
method (or something like that; not sure about the types exactly)
Hmm, but then you'd have no way to feed it a store, so nevermind
is this a bad idea?
Not if it works :shrug:
actually, I think the borrow checker will reject it
and if it doesn't then something's very wrong, because the future should close over the store ref
meaning you won't be able to return the future from Accessor::with
oh, the get
also moves the Promise
so that does not work
oh, right
I just realized Instance::promise
will redundantly Box::pin
the future you give it if it's already a Pin<Box<dyn Future>>>
-- I should add a promise_from_boxed
that takes an already-boxed future.
maybe it could take impl Into<Pin<Box<dyn Future>>>>
?
Also, updated the PR and ran into:
thread '<unnamed>' panicked at /Users/rvolosatovs/.cargo/git/checkouts/witx-bindgen-80318ec43c150aef/40ca9b1/crates/guest-rust/rt/src/async_support/stream_support.rs:167:9:
assertion failed: buf.remaining() == 0 || matches!(status, StreamResult::Closed)
Roman Volosatovs said:
maybe it could take
impl Into<Pin<Box<dyn Future>>>>
?
Do arbitrary futures implement that? I want to make sure you can do e.g. let p = instance.promise(store, async move { ... });
Roman Volosatovs said:
Also, updated the PR and ran into:
thread '<unnamed>' panicked at /Users/rvolosatovs/.cargo/git/checkouts/witx-bindgen-80318ec43c150aef/40ca9b1/crates/guest-rust/rt/src/async_support/stream_support.rs:167:9: assertion failed: buf.remaining() == 0 || matches!(status, StreamResult::Closed)
If you can push what you have and tell me how to repro, I'll take a look.
Joel Dice said:
Do arbitrary futures implement that? I want to make sure you can do e.g.
let p = instance.promise(store, async move { ... });
Alternatively, we could just make Instance::promise
require already boxed futures and make the caller box if necessary.
yep, working on that
Joel Dice said:
Roman Volosatovs said:
maybe it could take
impl Into<Pin<Box<dyn Future>>>>
?Do arbitrary futures implement that? I want to make sure you can do e.g.
let p = instance.promise(store, async move { ... });
not really, there's Box
-> Pin
(https://doc.rust-lang.org/std/pin/struct.Pin.html#impl-From%3CBox%3CT,+A%3E%3E-for-Pin%3CBox%3CT,+A%3E%3E), which sort of helps, but given that this API is meant to be pretty low-level, I don't feel like it's an issue
https://github.com/bytecodealliance/wasip3-prototyping/issues/100
Almost done with the Cursor
PR, will debug #100 after that
awesome, thank you!
Just pushed the Cursor
PR; taking a break for lunch, but will take a look at #100 after that if @Alex Crichton hasn't already figured it out.
@Joel Dice if I were to construct a Promise
using Instance::promise
, am I right in assuming that I can use Promise::into_future
inside the future I passed to Instance::promise
? (since it's running inside a Promise
)
Yes, correct. I started adding more docs with examples yesterday. I'll push a PR with those today.
Awesome, I used that trick to get the incoming HTTP working https://github.com/bytecodealliance/wasip3-prototyping/pull/58/commits/d4c69cf62ab83117f7a5cce684f03942b1050f2e
I also reworked the API a bit after our discussion, now everything is promise-based, so it's up to the caller to figure out how to poll it, meaning multiple concurrent calls are possible using the same store, while still getting back a fully-compliant http_body::Body
, e.g. for a single call https://github.com/bytecodealliance/wasip3-prototyping/blob/d4c69cf62ab83117f7a5cce684f03942b1050f2e/crates/wasi-http/tests/all/p3/mod.rs#L183-L207
So the actual store is never moved
And we have an HTTP roundtrip :) https://github.com/rvolosatovs/p3-http-test
using wasmtime from https://github.com/bytecodealliance/wasip3-prototyping/pull/58 + https://github.com/bytecodealliance/wasip3-prototyping/commit/b007214c67243d00078b444937562c2413664eb0 applied on top
I'm marking https://github.com/bytecodealliance/wasip3-prototyping/pull/58 ready for review, since it grew pretty big at this point, I feel like we(I) can continue porting the rest of incoming HTTP tests etc in follow-ups
Awesome! Yeah, it will be great to get that merged finally so we're not juggling branches a rebasing so much. I'll take a look today.
any idea what this could mean https://github.com/bytecodealliance/wasip3-prototyping/issues/105 ?
I saw that, and I'm as mystified as you. @Alex Crichton have you seen anything like that before (i.e. assert_eq!
failing for integer values but then printing an error message that they're the same)?
hm no I've not seen that before
my first guess though is some sort of memory corruption
where it's not-equal during the compare but equal by the time the print happens
in theory also UB where the compare is optimized to always false for some UB reason but then the print actually shows they're equal
riscv64 is optimized where my guess is none of us have been optimizing locally
@Roman Volosatovs have you tried running those tests locally with cargo test --release
by any chance?
I haven't, trying it now
Succeeded on aarch64-darwin
I can try x86_64-linux
later tonight as well
Feels like maybe the right thing to do is run these in qemu to see if it's riscv64 specific
there's always the possibility of qemu bugs as well yeah
also FYI I'm going to try an attempt with merge now
we're... extremely far behind
and the conflict is massive
I'm switching the p3-prototyping repo to use merge commits instead of squash, I think squashing is making merging with main worse
still running into the same bug on riscv64 after rebase, so I ignored these tests on riscv64 for now https://github.com/bytecodealliance/wasip3-prototyping/pull/58/commits/5e8f880803098f244ef000297ec03f7151d22e01
Ok, I'll get QEMU set up and see if I can learn anything.
I just ran the tests on main
using cargo test -p wasmtime-wasi-http --test all --target riscv64gc-unknown-linux-gnu --release
with QEMU 9.1.2 after removing all the ignore
attributes from the http_0_3_*
tests and they all passed. Am I doing something wrong?
try this: CARGO_INCREMENTAL=0 CARGO_PROFILE_DEV_DEBUG=0 CARGO_PROFILE_TEST_DEBUG=0 CARGO_PROFILE_DEV_OPT_LEVEL=2 WASMTIME_TEST_NO_HOG_MEMORY=1 cargo test -p wasmtime-wasi-http --test all --target riscv64gc-unknown-linux-gnu p3::outgoing::http_0_3_outbound_request_invalid_header
That worked (i.e. the test failed). Thanks!
just a small note that if you rebase on latest main
, the test will be called p3::outgoing::p3_http_outbound_request_invalid_header
Notes from the meeting this week:
P3 sync meeting for 2025/04/07
For the record: the riscv64 failure appears to be a pre-existing Cranelift codegen bug; @Alex Crichton is going to investigate further.
ok yeah I've fixed this locally now
I wasn't present during this meeting, so I'll be extrapolating purely based on the notes. The following cherry picked items resonated with me:
Alex: There aren't enough maintainers for wasi-libc
Alex: the main problem is not many people want to write C
Victor: Why is wasi-libc not a Rust thing?
Alex: Easiest option was Musl compiled to wasi. Only I/O system layer is what we have to maintain -- very different from having to maintain 100% of the libc
I am not much a C developer either. I've only worked on wasi-libc during the initial implementation of wasi-sockets together with Joel, but I wouldn't describe my time working on wasi-libc as "fun". To the point that I dread working on it again, despite the socket work not being complete. :neutral: To make things worse, once shared-everything-threading becomes a thing, I suspect wasi-libc will start breaking left and right because I'm not confident at all that what I wrote is thread safe.
I realize there's a lot of value in continuing off the work provided by MUSL. So I wonder if there's another middle ground here where e.g. we keep using MUSL for pretty much everything, but write the POSIX<->WASI IO interop layer in a statically linked Rust library, similar to the p1-to-p2 adapter.
Joel: (...) is the suggestion that they need to decide they're going all-in on POSIX (& base on wasi-sockets) or go all-in on WASI HTTP (& do socket stuff without wasi-libc)? i.e. all in on native wasi or POSIX, but not mix?
Alex: Essentially yes, it's probably a bad idea to support a mix
I worry this all-or-nothing approach will be a tough pill to swallow for language/framework authors. But I'm fine deferring this until real world users come knocking
std will probably link against wasi-libc forever, because its expected for C library interop, even if nothing in std strictly needs to use it
AFAIK many folks are in a similar boat of "would be nice to write I/O stuff in rust". I don't think there's 100% alignment amongst all stakeholders but tbh there aren't that many super active stakeholders to begin with anyway, so I don't think it would necessarily require tons of buy-in.
Otherwise for mixing things and posix-vs-wasi it's probably best to discuss in the context of possible additions to wasi-libc. My high-level goal is to avoid any one particular language/runtime leaking into wasi-libc, e.g. taking everything needed by Python and putting it in wasi-libc when something like rust's libstd wouldn't want it at all. How exactly to achieve that high level goal without Python rewriting wasi-libc is going to be a balancing act and I agree the goal is for Python and Rust, for example, to uset he same wasi-libc
also, we're working on hiring a contractor specifically to help us with wasi-libc
Pat Hickey said:
std will probably link against wasi-libc forever, because its expected for C library interop, even if nothing in std strictly needs to use it
I may be misinterpreting Dave but my read is that we'd in theory rewrite parts of wasi-libc in Rust rather than having everything in C, specifically the non-musl portions in Rust
(but rust would still use wasi-libc)
ah ok sorry i misread that as being just some separate rust code from wasi-libc itself
Otherwise for mixing things and posix-vs-wasi it's probably best to discuss in the context of possible additions to wasi-libc.
What I've been envisioning is public APIs which allow code built on wasi-libc to access (and optionally swap out) the WASI-level resource(s), streams, etc. corresponding to a file descriptor. That would include taking ownership of the WASI-level handles by removing the descriptor entirely and also doing the opposite: giving wasi-libc ownership of WASI-level handles and getting a new descriptor back for them.
I may be misinterpreting Dave but my read is that we'd in theory rewrite parts of wasi-libc in Rust rather than having everything in C, specifically the non-musl portions in Rust
That's indeed what I meant. :+1: The periphery of such a Rust library would still be rather C-esque of course, but me personally would still be more comfortable maintaining awkward looking Rust compared to the C implementation. Though I might be biased towards Rust :melting_face:
My high-level goal is to avoid any one particular language/runtime leaking into wasi-libc
Agree :+1:
Last updated: Apr 07 2025 at 21:03 UTC