stdio: Update interfaces to support error results#82
Conversation
|
Returning a tuple on |
|
I already changed it 🙂 stdout is special; with line buffering |
wit-0.3.0-draft/stdio.wit
Outdated
| interface stdin { | ||
| @since(version = 0.3.0-rc-2025-08-15) | ||
| get-stdin: func() -> stream<u8>; | ||
| read-stdin: func() -> tuple<stream<u8>, future<result<_, error-code>>>; |
There was a problem hiding this comment.
so, is the idea that you're supposed to call read-stdin each time or just once? if just once that does have the issue that you can't represent stdin that returns EOF (e.g. pressing Ctrl+Z on Linux) and then can read more data after that.
There was a problem hiding this comment.
Either should work, though I'm not aware of any recoverable errors from reading stdin that would apply here (e.g. I don't think EINTR is applicable with WASI).
There was a problem hiding this comment.
If this can be called multiple times (wouldn't that be required to enable e.g. composition without virtualizing this interface?), what content do consequent streams get?
let's say we do something roughly like this:
echo "hello, world" | wasmtime --inherit-stdio test.wasm
(stream1, fut1) = read-stdin()
stream1.read(7).await // reads `hello, `
(stream2, fut2) = read-stdin()
stream2.read(7).await // does this read `hello, ` or `world` ?
stream1.read(5).await // does this read `world` or EOF ?
Personally, I think I'd expect that if multiple streams exist simultaneously data read is effectively random, i.e. there's no concept of stdin buffer/fan-out in the host and no guarantee on the ordering with multiple concurrent streams. Each stream acts as a "view" into the singular stdin stream and if the guest read N bytes, next read will start reading from byte N+1 regardless of which stream is used. If guest invokes 2 concurrent reads of N and M bytes on different streams, the host only guarantees that at most N + M sequential bytes will be read, but there is no way of knowing which stream read will get the "head" and which - the "tail"
There was a problem hiding this comment.
I think I'd expect that if multiple streams exist simultaneously data read is effectively random
Yeah that's what I'm trying to say in the last chunk of the doc comment:
/// Multiple streams may be active at the same time. The behavior of concurrent
/// reads is implementation-specific.|
This looks reasonable to me, although one thing I'm now thinking about as well -- one option is there could be an entirely separate function for "get the last error". That would remove the need for broadcast-semantics where there are multiple stdin handles, for example, or multiple invocations of |
|
I'm not sure how stdin is supposed to signal EOF, here or for |
|
"Get the last error" could be fine if there are no recoverable errors, though I'm not sure if that is realistic. Notably I just added |
|
The design of component-model streams don't really allow for recoverable errors right now since, for example, when you write to a stream all you get back was "ok I took N bytes" and "here's whether I'm closed". Given that there's no way to say "I'm still open but there's an error somewhere else for you to look at". AFAIK everywhere else in WASI errors on streams are considered unrecoverable as part of WASIp3 basically |
|
It seems like any approach that would apply here would also apply to opening the same wasi-filesystem file twice, right? i.e. if there are multiple concurrent |
|
Thinking more on it I believe stdin is the most affected. For stdout and files N concurrent operations correspond to N different calls that all get to figure out how they want to do errors. For stdin though we have infrastructure to share that amongst all threads and stores in the same process and that's where the broadcast behavior would come into play because we'd explicitly have a list of things blocked on a lock or something like that. Overall though I wouldn't really place weight on the concern, while "return last error" is technically viable it feels too different from http/filesystem that's probably worth sticking to "here's the result for this operation you just started" |
| @since(version = 0.3.0-rc-2025-08-15) | ||
| enum error-code { | ||
| illegal-byte-sequence, | ||
| pipe, |
There was a problem hiding this comment.
I think pipe is already expressible, by stream.write returning copy-result.dropped.
There was a problem hiding this comment.
Oh, is it the case that copy-result.dropped means the stream is over, and that's when the error code is delivered, so we need a pipe-like code for distinguishing that case from others?
There was a problem hiding this comment.
I think for reading dropped is eos. I'm not sure what the intent is for writes but the docs say it "doesn't signify an error." 🤷
|
Adapted docs from wasi:filesystem. Ready for review. |
wit-0.3.0-draft/stdio.wit
Outdated
| /// | ||
| /// If the stream is dropped by the writer (`stream.write` returns `dropped`), | ||
| /// the future will resolve either successfully if stdout was closed by the other | ||
| /// end or an error if the write failed. |
There was a problem hiding this comment.
Should line buffering be specified or should that be up to wasi-libc?
There was a problem hiding this comment.
Personally I think that should be up to wasi-libc as that's an in-guest thing rather than a host thing
|
If there are no objections I'm going to plan to merge this to unblock implementation work which can inform future revisions. |
sunfishcode
left a comment
There was a problem hiding this comment.
This looks good to me, with one minor bikeshed suggestion which I'll leave to your discression:
Adapted from
wasi:filesystem/types@0.3.0-rc-2025-08-15descriptor.read-via-stream/append-via-stream.Fixes #81