Skip to content

wasip3: Fix fallback code when buffering writes#788

Merged
alexcrichton merged 4 commits intoWebAssembly:mainfrom
alexcrichton:test-fs-nonblocking
Apr 22, 2026
Merged

wasip3: Fix fallback code when buffering writes#788
alexcrichton merged 4 commits intoWebAssembly:mainfrom
alexcrichton:test-fs-nonblocking

Conversation

@alexcrichton
Copy link
Copy Markdown
Collaborator

This commit fixes mistakes from #782 where the fallback code for buffering writes wasn't handled properly. Specifically when a completion event came in through poll it didn't fully update the internal state of the stream, such as kicking off further writes or cleaning up the internal buffer. This is fixed by refactoring the logic already present during a normal write to get executed in this event.

Testing this is unfortunately not trivial. There aren't really all that many streams we have access to natively in Wasmtime, and TCP is "well behaved" and doesn't execute any of these fallbacks anyway. The closest way to test this is filesystem-based streams, but Wasmtime's default behavior is to lie and just do blocking operations on these streams regardless. Through changes in Wasmtime, and flags to Wasmtime, however, it's possible to Wasmtime in a mode where filesystem streams are "ill behaved" where zero-length reads don't signal readiness, triggering these fallback paths. With those changes the added test here, and minor additionally support for nonblocking reads/writes, exercise these paths and triggers the previous bug.

This commit fixes mistakes from WebAssembly#782 where the fallback code for
buffering writes wasn't handled properly. Specifically when a completion
event came in through `poll` it didn't fully update the internal state
of the stream, such as kicking off further writes or cleaning up the
internal buffer. This is fixed by refactoring the logic already present
during a normal `write` to get executed in this event.

Testing this is unfortunately not trivial. There aren't really all that
many streams we have access to natively in Wasmtime, and TCP is "well
behaved" and doesn't execute any of these fallbacks anyway. The closest
way to test this is filesystem-based streams, but Wasmtime's default
behavior is to lie and just do blocking operations on these streams
regardless. Through changes in Wasmtime, and flags to Wasmtime, however,
it's possible to Wasmtime in a mode where filesystem streams are "ill
behaved" where zero-length reads don't signal readiness, triggering
these fallback paths. With those changes the added test here, and minor
additionally support for nonblocking reads/writes, exercise these paths
and triggers the previous bug.
@alexcrichton alexcrichton requested a review from dicej April 21, 2026 17:35
@alexcrichton
Copy link
Copy Markdown
Collaborator Author

The companion Wasmtime PR for this is bytecodealliance/wasmtime#13163 which exercises these tests more thoroughly.

read->offset = &file->offset;
read->timeout = 0;
read->blocking = true;
read->blocking = (file->oflag & O_NONBLOCK) == 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean read/write can start returning EWOULDBLOCK for regular files?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does, yeah. I'm not sure how useful that is in practice, but it's the only way I could think of to test this PR

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose if an application requested O_NONBLOCK, then getting EWOULDBLOCK is exactly what it asked for. Nonetheless, I wonder how many programs are actually prepared to handle that for regular files, given ~40 years of assumptions that the flag is ignored for those.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Er sorry meant to think about this a bit more before landing. Do you feel that this should get backed out? I don't know how to test anything related to "ill behaving streams" otherwise since files are basically the only host-defined stream that doesn't handle zero-length reads/writes like TCP does.

Copy link
Copy Markdown
Member

@badeend badeend Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Linux man page mentions:

Note that this flag has no effect for regular files and block devices; that is, I/O operations will (briefly) block when device activity is required, regardless of whether O_NONBLOCK is set.

And also:

Since O_NONBLOCK semantics might eventually be implemented, applications should not depend upon blocking behavior when specifying this flag for regular files and block devices.

So yeah,. there's that.. :)


I feel this leaks testing concerns into user-visible behavior so I'm leaning toward keeping the fix but backing out the nonblocking behavior for regular files. I don't have a super strong well-funded opinion for it though.

That would indeed leave some of the new code untested. As long as the test suite uses off-the-shelf, unmodified Wasm engines, we're unlikely to hit all code paths anyway. A longer-term solution might be some kind of host-side "chaos mode" that deterministically generates all kinds of odd-but-technically-correct stream behaviors. That could also be more generally useful for other languages and toolchains targeting the component model directly. I realize that is completely out of scope for this PR.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm yeah ok I agree and it seems prudent. I've posted #796 to disable this, and for further testing -- in addition to a theoretical chaos mode this'd be a good use of #766 I believe.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah right, this could indeed be a nice use case for component composition

Comment thread libc-bottom-half/sources/file_utils.c Outdated
@alexcrichton alexcrichton enabled auto-merge (squash) April 22, 2026 14:18
@alexcrichton alexcrichton merged commit fc3bc46 into WebAssembly:main Apr 22, 2026
33 checks passed
@alexcrichton alexcrichton deleted the test-fs-nonblocking branch April 22, 2026 15:01
alexcrichton added a commit that referenced this pull request Apr 22, 2026
This commit refactors how `offset` is updated during the internal
read/write operations on streams. This isn't applicable for TCP but
matters for files, for example. There's not a great way to test the
paths that were previously missing the update but the code is structured
differently now to make it much harder to forget to do an update. The
test modified here is a light test which doesn't reproduce the original
issue but at least ensures that nonblocking paths update the offset as
well.

> **Note**: this is temporarily built on #788
alexcrichton added a commit that referenced this pull request Apr 23, 2026
Concerns brought up in #788 rightfully point out that this doesn't work
on other platforms and it's been such a core assumption for so long we
run more of a risk of breaking code by supporting it than we do
continuing to not support it. This means that some recent changes no
longer have test coverage, but that seems best addressed by #766 so it's
deferred to that.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants