Skip to content

Commit 5885dd9

Browse files
alexcrichtondicej
andauthored
wasip3: Finish nonblocking I/O implementation for TCP (#782)
This commit fills out the final bits of nonblocking I/O for TCP sockets. This necessitates an implementation of nonblocking I/O for `stream<u8>` on both the reading and the writing side of things. Additionally things like TCP accepts now work, too. All tests are now passing on the WASIp3 target and the `FAILP3` directive is now removed since it's no longer necessary. The internals of the implementation here are pretty gnarly. This is due to the need to bridge the readiness-based-model of `poll` with the completion-based model of the component model. This implementation contains optimizations for 0-length reads/writes to use those to signal readiness if the host supports that, but this falls back to internally-buffered reads/writes if that is not supported. This should resolve all remaining TODOs for WASIp3 that I know of at least in wasi-libc, so after this it'd be switching to bug-finding mode and handling bug reports as projects are compiled with wasip3. --------- Co-authored-by: Joel Dice <joel.dice@akamai.com>
1 parent aa9b5c0 commit 5885dd9

11 files changed

Lines changed: 807 additions & 109 deletions

File tree

expected/wasm32-wasip3/defined-symbols.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ __wasilibc_populate_preopens
336336
__wasilibc_pthread_self
337337
__wasilibc_random
338338
__wasilibc_read
339+
__wasilibc_read_poll
339340
__wasilibc_rename_newat
340341
__wasilibc_rename_oldat
341342
__wasilibc_reset_preopens
@@ -350,9 +351,11 @@ __wasilibc_tell
350351
__wasilibc_unlinkat
351352
__wasilibc_unspecified_addr
352353
__wasilibc_utimens
354+
__wasilibc_waitable_block_on
353355
__wasilibc_wasi_family_to_libc
354356
__wasilibc_wasi_to_sockaddr
355357
__wasilibc_write
358+
__wasilibc_write_poll
356359
__wasm_call_dtors
357360
__wcscoll_l
358361
__wcsftime_l

libc-bottom-half/cloudlibc/src/libc/poll/poll.c

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -372,20 +372,30 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
372372
goto out;
373373
}
374374

375-
376375
if (events & POLLRDNORM) {
377-
// TODO(wasip3): use `get_read_stream` and use that I/O to do something.
378-
// Requires a lot more integration with nonblocking I/O to get that
379-
// working.
380-
errno = EOPNOTSUPP;
381-
goto out;
376+
if (entry->vtable->get_read_stream) {
377+
wasi_read_t read;
378+
if (entry->vtable->get_read_stream(entry->data, &read) < 0)
379+
goto out;
380+
if (__wasilibc_read_poll(read.state, &state) < 0)
381+
goto out;
382+
} else {
383+
errno = EOPNOTSUPP;
384+
goto out;
385+
}
382386
}
383387

384388
if (events & POLLWRNORM) {
385-
// TODO(wasip3): use `get_write_stream` to implement this (see
386-
// `POLLRDNORM` above).
387-
errno = EOPNOTSUPP;
388-
goto out;
389+
if (entry->vtable->get_write_stream) {
390+
wasi_write_t write;
391+
if (entry->vtable->get_write_stream(entry->data, &write) < 0)
392+
goto out;
393+
if (__wasilibc_write_poll(write.state, &state) < 0)
394+
goto out;
395+
} else {
396+
errno = EOPNOTSUPP;
397+
goto out;
398+
}
389399
}
390400
}
391401

@@ -451,10 +461,14 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
451461
if (p->waitable != event.waitable)
452462
continue;
453463
state.pollfd = p->pollfd;
454-
// Clear the `waitable` since this event has fired and it doesn't need
455-
// to be join'd to 0 below. Then invoke the custom callback originally
456-
// added for this which will handle any necessary completion logic and
457-
// updating `state.pollfd` with various events.
464+
// Remove this waitable from the `waitable-set` as the `ready`
465+
// operation might end up deleting the handle. Set the list here to 0
466+
// so it's not removed down below.
467+
//
468+
// Then invoke the custom callback originally added for this
469+
// which will handle any necessary completion logic and updating
470+
// `state.pollfd` with various events.
471+
wasip3_waitable_join(p->waitable, 0);
458472
p->waitable = 0;
459473
p->ready(p->ready_data, &state, &event);
460474
}

libc-bottom-half/cloudlibc/src/libc/sys/select/pselect.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
// SPDX-License-Identifier: BSD-2-Clause
44

55
#include <common/time.h>
6-
76
#include <sys/select.h>
8-
97
#include <wasi/api.h>
108
#include <errno.h>
119
#include <poll.h>

libc-bottom-half/headers/private/wasi/descriptor_table.h

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,49 @@
66
#ifndef __wasip1__
77
#include <assert.h>
88
#include <netinet/in.h>
9+
#include <stdlib.h>
10+
#include <string.h>
911
#include <sys/stat.h>
1012
#include <wasi/poll.h>
1113

1214
#ifdef __wasip3__
1315

16+
/// The stream is complete and no further operations are allowed on it.
17+
#define WASIP3_IO_DONE (1 << 0)
18+
/// An I/O operation, be it a read or write, is in flight.
19+
#define WASIP3_IO_INPROGRESS (1 << 1)
20+
/// The in-flight I/O operation is a zero-length operation. This means that if
21+
/// it's finished we expect that the next operation finishes immediately.
22+
#define WASIP3_IO_ZERO_INPROGRESS (1 << 2)
23+
/// A zero-sized in-flight I/O operation just finished so the next I/O op
24+
/// should finish immediately. If it doesn't it'll turn on the next flag.
25+
#define WASIP3_IO_SHOULD_BE_READY (1 << 3)
26+
/// This stream isn't compatible with zero-length reads/writes signaling
27+
/// readiness, so libc must buffer data internally for reads/writes.
28+
#define WASIP3_IO_MUST_BUFFER (1 << 4)
29+
1430
/// Helper structure to package up state related to a wasip3 `stream<u8>`.
1531
///
1632
/// This is used by various helpers to coordinate reading/writing/etc on a
1733
/// stream. This simultaneously represents both readers and writers.
1834
typedef struct wasip3_io_state_t {
1935
uint32_t stream;
20-
bool done;
36+
/// Bitset of `WASIP3_IO_*` flags.
37+
uint32_t flags;
38+
/// Malloc'd buffer used for reads/writes. NULL if not present.
39+
uint8_t *buf;
40+
/// Start of `buf` that has data in-flight or ready.
41+
size_t buf_start;
42+
/// End of `buf` that has data in-flight or ready.
43+
size_t buf_end;
2144
} wasip3_io_state_t;
2245

2346
/// Initializes `state` with the `stream` provided.
2447
static inline void wasip3_io_state_init(wasip3_io_state_t *state,
2548
uint32_t stream) {
2649
assert(stream != 0);
50+
memset(state, 0, sizeof(*state));
2751
state->stream = stream;
28-
state->done = false;
2952
}
3053

3154
/// Tests whether `state` has been initialized with a stream yet.
@@ -37,22 +60,26 @@ static inline bool wasip3_io_state_present(wasip3_io_state_t *state) {
3760
///
3861
/// Internally the stream must be a reader-half of a `stream<u8>`.
3962
static inline void wasip3_read_state_close(wasip3_io_state_t *state) {
40-
if (state->stream != 0) {
63+
if (state->flags & WASIP3_IO_INPROGRESS)
64+
filesystem_stream_u8_cancel_read(state->stream);
65+
if (state->buf)
66+
free(state->buf);
67+
if (state->stream != 0)
4168
filesystem_stream_u8_drop_readable(state->stream);
42-
state->stream = 0;
43-
}
44-
state->done = false;
69+
memset(state, 0, sizeof(*state));
4570
}
4671

4772
/// Closes out the streams/etc internal to `state`.
4873
///
4974
/// Internally the stream must be a writer-half of a `stream<u8>`.
5075
static inline void wasip3_write_state_close(wasip3_io_state_t *state) {
51-
if (state->stream != 0) {
76+
if (state->flags & WASIP3_IO_INPROGRESS)
77+
filesystem_stream_u8_cancel_write(state->stream);
78+
if (state->buf)
79+
free(state->buf);
80+
if (state->stream != 0)
5281
filesystem_stream_u8_drop_writable(state->stream);
53-
state->stream = 0;
54-
}
55-
state->done = false;
82+
memset(state, 0, sizeof(*state));
5683
}
5784
#endif
5885

@@ -195,6 +222,7 @@ typedef struct descriptor_vtable_t {
195222
/// and `get_write_stream`, if present, to handle `POLL{RD,WR}NORM` events.
196223
int (*poll_register)(void *, poll_state_t *state, short events);
197224

225+
#ifdef __wasip2__
198226
/// Invoked when `poll` has already run and detected that this object was
199227
/// ready. The `events` provided are the same as those provided to
200228
/// `__wasilibc_poll_add` originally. This function should call
@@ -204,6 +232,7 @@ typedef struct descriptor_vtable_t {
204232
/// If this function is not provided then `events` will automatically
205233
/// be placed into the `revents` field of `pollfd`.
206234
int (*poll_finish)(void *, poll_state_t *state, short events);
235+
#endif // __wasip2__
207236
} descriptor_vtable_t;
208237

209238
/// A "fat pointer" which is placed inside of the descriptor table.

libc-bottom-half/headers/private/wasi/file_utils.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <errno.h>
1010
#include <fcntl.h>
1111
#include <wasi/descriptor_table.h>
12+
#include <wasi/poll.h>
1213
#include <wasi/wasip2.h>
1314

1415
#ifdef __wasip2__
@@ -119,6 +120,18 @@ dir_entry_type_to_d_type(filesystem_descriptor_type_t *ty) {
119120
}
120121
}
121122

123+
#ifndef __wasip2__
124+
/// Starts the process of `poll` for `iostate` assuming it's backed by a
125+
/// readable stream.
126+
///
127+
/// This will handle all the internal logic for zero-length reads/writes/etc
128+
/// and add to `state` as necessary.
129+
int __wasilibc_read_poll(wasip3_io_state_t *iostate, poll_state_t *state);
130+
131+
/// Write dual of `__wasilibc_read_poll`.
132+
int __wasilibc_write_poll(wasip3_io_state_t *iostate, poll_state_t *state);
133+
#endif // !__wasip2__
134+
122135
#endif // __wasip2__ || __wasip3__
123136

124137
#endif // __wasi_file_utils_h

libc-bottom-half/headers/private/wasi/tcp.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,25 @@ typedef struct {
3535
wasip3_subtask_t subtask;
3636
#endif
3737
} tcp_socket_state_connecting_t;
38+
39+
/// The `stream` in `tcp_socket_state_listening_t` is finished and should not
40+
/// be read again.
41+
#define TCP_LISTENING_DONE (1 << 0)
42+
/// There is an active read on `stream` which has yet to complete.
43+
#define TCP_LISTENING_ACCEPTING (1 << 1)
44+
/// The `accept_result` field is valid and ready to be processed.
45+
#define TCP_LISTENING_ACCEPT_READY (1 << 2)
46+
3847
typedef struct {
3948
#ifdef __wasip2__
4049
int dummy;
4150
#else
4251
// The `stream<tcp-socket>` that this is reading to receive accepted sockets.
4352
sockets_stream_own_tcp_socket_t stream;
44-
bool done;
53+
/// In-flight result of the read of `stream`.
54+
sockets_own_tcp_socket_t accept_result;
55+
/// Bitset of `TCP_LISTENING_*`.
56+
uint32_t flags;
4557
#endif
4658
} tcp_socket_state_listening_t;
4759

libc-bottom-half/headers/private/wasi/wasip3_block.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@
99

1010
typedef wasip3_waitable_status_t (*wasip3_cancel_t)(uint32_t);
1111

12+
/// Wait at most `timeout` duration for `waitable` to become ready.
13+
///
14+
/// If `timeout` is 0 this will wait indefinitely. Returns `true` if `waitable`
15+
/// has become ready. Returns `false` if `timeout` elapsed. Upon returning
16+
/// `true` the `event` field is filled in.
17+
bool __wasilibc_waitable_block_on(uint32_t waitable, wasip3_event_t *event,
18+
monotonic_clock_duration_t timeout);
19+
1220
// Waits for a subtask to return
1321
void __wasilibc_subtask_block_on_and_drop(wasip3_subtask_t subtask);
1422

0 commit comments

Comments
 (0)