In context of dotnet sockets I would like to be able to wasi:poll_poll on mix of handles
- which were created by consuming other WASI APIs, like
wasi:http
- and file handles obtained by using libc sockets APIs
The motivation to do it on single "system" call is to
- be able to make progress on events from HTTP while socket is not ready and vice versa
- if we called each
select/poll or poll_poll separately, we would get blocked only on part of pollables
- to be able to block (stop spinning) if there are no I/O events (and no CPU bound jobs)
At the moment, we can hack it via using libc internal descriptor_table_get_ref and it's data structures to obtain underlying pollables.
@dicej made similar hack for mio
But it's fragile and bad practice.
I can see few possible solutions
- expose libc function accepting both lists of resources
void wasi_poll_plus_fd(poll_list_borrow_pollable_t *in, list_pollfd_t *in, wasip2_list_u32_t *ret)
- this is with wit-bindgen generated C structures on APIs and with list of
pollfd struct
- we can try simpler C types, something like
void wasi_poll_plus_fd(int *pollables, int pollables_count, struct pollfd *fds, int fds_count, int **ret, int *ret_count)
- or we can expose just the mapping from
fds to list of pollables
void socket_fds_to_wasi_pollables(struct pollfd *fds, int fds_count, int **pollables, int pollables_count)
- this would have to be called before each call to
wasi:poll_poll
- create
libc file descriptor by registering external pollable
- this would have to be un-registered later
- calling socket oriented
poll() API on any pollable (clock) feels confusing to me
Relevant code, possibly to be refactored and reused for above.
|
switch (entry->tag) { |
|
case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET: { |
|
tcp_socket_t *socket = &(entry->tcp_socket); |
|
switch (socket->state.tag) { |
|
case TCP_SOCKET_STATE_CONNECTING: |
|
case TCP_SOCKET_STATE_LISTENING: { |
|
if ((pollfd->events & |
|
(POLLRDNORM | POLLWRNORM)) != 0) { |
|
states[state_index++] = (state_t){ |
|
.pollable = |
|
socket->socket_pollable, |
|
.pollfd = pollfd, |
|
.entry = entry, |
|
.events = pollfd->events |
|
}; |
|
} |
|
break; |
|
} |
|
|
|
case TCP_SOCKET_STATE_CONNECTED: { |
|
if ((pollfd->events & POLLRDNORM) != |
|
0) { |
|
states[state_index++] = (state_t){ |
|
.pollable = |
|
socket->state |
|
.connected |
|
.input_pollable, |
|
.pollfd = pollfd, |
|
.entry = entry, |
|
.events = POLLRDNORM |
|
}; |
|
} |
|
if ((pollfd->events & POLLWRNORM) != |
|
0) { |
|
states[state_index++] = (state_t){ |
|
.pollable = |
|
socket->state |
|
.connected |
|
.output_pollable, |
|
.pollfd = pollfd, |
|
.entry = entry, |
|
.events = POLLWRNORM |
|
}; |
|
} |
|
break; |
|
} |
|
|
|
case TCP_SOCKET_STATE_CONNECT_FAILED: { |
|
if (pollfd->revents == 0) { |
|
++event_count; |
|
} |
|
pollfd->revents |= pollfd->events; |
|
break; |
|
} |
|
|
|
default: |
|
errno = ENOTSUP; |
|
return -1; |
|
} |
|
break; |
|
} |
|
|
|
case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET: { |
|
udp_socket_t *socket = &(entry->udp_socket); |
|
switch (socket->state.tag) { |
|
case UDP_SOCKET_STATE_UNBOUND: |
|
case UDP_SOCKET_STATE_BOUND_NOSTREAMS: { |
|
if (pollfd->revents == 0) { |
|
++event_count; |
|
} |
|
pollfd->revents |= pollfd->events; |
|
break; |
|
} |
|
|
|
case UDP_SOCKET_STATE_BOUND_STREAMING: |
|
case UDP_SOCKET_STATE_CONNECTED: { |
|
udp_socket_streams_t *streams; |
|
if (socket->state.tag == |
|
UDP_SOCKET_STATE_BOUND_STREAMING) { |
|
streams = &( |
|
socket->state |
|
.bound_streaming |
|
.streams); |
|
} else { |
|
streams = &( |
|
socket->state.connected |
|
.streams); |
|
} |
|
if ((pollfd->events & POLLRDNORM) != |
|
0) { |
|
states[state_index++] = (state_t){ |
|
.pollable = |
|
streams->incoming_pollable, |
|
.pollfd = pollfd, |
|
.entry = entry, |
|
.events = POLLRDNORM |
|
}; |
|
} |
|
if ((pollfd->events & POLLWRNORM) != |
|
0) { |
|
states[state_index++] = (state_t){ |
|
.pollable = |
|
streams->outgoing_pollable, |
|
.pollfd = pollfd, |
|
.entry = entry, |
|
.events = POLLWRNORM |
|
}; |
|
} |
|
break; |
|
} |
|
|
|
default: |
|
errno = ENOTSUP; |
|
return -1; |
|
} |
|
break; |
|
} |
|
|
|
default: |
|
errno = ENOTSUP; |
|
return -1; |
|
} |
|
} else { |
|
abort(); |
|
} |
cc @badeend
In context of dotnet sockets I would like to be able to
wasi:poll_pollon mix of handleswasi:httpThe motivation to do it on single "system" call is to
select/pollorpoll_pollseparately, we would get blocked only on part of pollablesAt the moment, we can hack it via using
libcinternaldescriptor_table_get_refand it's data structures to obtain underlying pollables.@dicej made similar hack for mio
But it's fragile and bad practice.
I can see few possible solutions
void wasi_poll_plus_fd(poll_list_borrow_pollable_t *in, list_pollfd_t *in, wasip2_list_u32_t *ret)pollfdstructvoid wasi_poll_plus_fd(int *pollables, int pollables_count, struct pollfd *fds, int fds_count, int **ret, int *ret_count)fdsto list of pollablesvoid socket_fds_to_wasi_pollables(struct pollfd *fds, int fds_count, int **pollables, int pollables_count)wasi:poll_polllibcfile descriptor by registering external pollablepoll()API on any pollable (clock) feels confusing to meRelevant code, possibly to be refactored and reused for above.
wasi-libc/libc-bottom-half/sources/poll-wasip2.c
Lines 27 to 151 in 7d4d3b8
cc @badeend