Skip to content

Implement pipe and pipe2 on WASIp3#826

Open
alexcrichton wants to merge 3 commits into
WebAssembly:mainfrom
alexcrichton:implement-pipe
Open

Implement pipe and pipe2 on WASIp3#826
alexcrichton wants to merge 3 commits into
WebAssembly:mainfrom
alexcrichton:implement-pipe

Conversation

@alexcrichton

Copy link
Copy Markdown
Collaborator

This is expected to assist with porting applications to WASI as these
are quite common syscalls. With WASIp3 these are also now able to be
natively implemented. This commit fills out a simple-ish implementation
which isn't focused on speed but is intended to at least be correct.

One important note is that pipes aren't backed directly by stream<u8>
as might otherwise be expected. This is to be able to implement
buffering behavior which the component model does not support.
Specifically applications frequently expect write on a pipe that's
known to be empty to generally not block, and this implementation
preserves that assumption.

Internally, however, stream is still used. This enables pipe to get
connected to poll, for example.

Closes #726

Note: this is built on #825

This commit goes through the I/O subsystems of wasi-libc for WASIp3
(TCP, UDP, files, and stdio) and adds necessary internal locks and
synchronization. None of this has been previously required because
threading was not possible so it wasn't possible to have parallel
operations issued, but with WASIp3 coop threads on the horizon this will
soon be possible. The goal of this commit is to ensure that I/O objects
all behave "reasonably" for operations issued in parallel.

The general strategy here is to add a libc-internal lock-per-object. All
operations on the object start by acquiring the lock and then end by
releasing the lock. The main question to deal with is what to do with
blocking operations where a thread blocked in `read` shouldn't block a
sibling thread doing a `write`, for example. To handle this each
blocking operation drops the object's `lock` before issuing the blocking
operation. State is left behind to indicate that a blocking operation is
in-progress, however. Another part to handle here is that calls to
`poll` leave objects in "blocking I/O in progress" state while the call
to `poll` is active as well.

The general problems that need to be protected against are:

1. Component model primitives can only reside in one waitable-set at a
   time. This means that if a thread is blocked on a waitable-set with
   stream and another thread puts that stream into a different
   waitable-set then the first thread won't operate correctly any more.
2. Component model streams only support at most one active operation on
   a stream at a time. Once issued another thread can't start a new
   operation.
3. Blocking operations in the component model cannot be cancelled. For
   example a thread blocked in `read` can't get cancelled by a thread
   calling `close`.

For wasi-libc this effectively gives rise to the constraint that once a
thread is blocked in an I/O operation the object in question can't
handle any other requests for I/O. For example one thread blocked in
`read` means that another thread doing a nonblocking `read` basically
can't be supported. This also happens for other issues like another
thread doing `poll`, for example. For now these sorts of concurrent I/O
requests return `EOPNOTSUPP` to at least indicate that wasi-libc doesn't
support what's happening. I'm not sure if this is going to be a viable
strategy long-term, but my hope is that this covers the vast majority of
real-world use cases of concurrent operations on file descriptors.

Note that parallel reads/writes on the same file descriptors are
intentionally supported. This is a known use case in the wild for many
libraries so this is explicitly allowed and handled within wasi-libc,
but mostly just because it falls out that these operations are happening
on separate streams.
This is expected to assist with porting applications to WASI as these
are quite common syscalls. With WASIp3 these are also now able to be
natively implemented. This commit fills out a simple-ish implementation
which isn't focused on speed but is intended to at least be correct.

One important note is that pipes aren't backed directly by `stream<u8>`
as might otherwise be expected. This is to be able to implement
buffering behavior which the component model does not support.
Specifically applications frequently expect `write` on a pipe that's
known to be empty to generally not block, and this implementation
preserves that assumption.

Internally, however, `stream` is still used. This enables `pipe` to get
connected to `poll`, for example.

Closes WebAssembly#726
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.

1 participant