@@ -16,26 +16,6 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
1616 __wasi_subscription_t subscriptions [maxevents ];
1717 size_t nsubscriptions = 0 ;
1818
19- // POLLPRI (exceptional/out-of-band data) is not supported in WASI.
20- // If all requested events across all fds are POLLPRI, return ENOSYS.
21- // Otherwise, remove POLLPRI so it never fires (same as no OOB data).
22- {
23- bool has_pri_only = false;
24- bool has_non_pri = false;
25- for (size_t i = 0 ; i < nfds ; ++ i ) {
26- if (fds [i ].fd < 0 || fds [i ].events == 0 )
27- continue ;
28- if (fds [i ].events & ~POLLPRI )
29- has_non_pri = true;
30- else
31- has_pri_only = true;
32- }
33- if (has_pri_only && !has_non_pri ) {
34- errno = ENOSYS ;
35- return -1 ;
36- }
37- }
38-
3919 for (size_t i = 0 ; i < nfds ; ++ i ) {
4020 struct pollfd * pollfd = & fds [i ];
4121 if (pollfd -> fd < 0 )
@@ -105,12 +85,6 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
10585 return -1 ;
10686 }
10787
108- // Clear revents fields.
109- for (size_t i = 0 ; i < nfds ; ++ i ) {
110- struct pollfd * pollfd = & fds [i ];
111- pollfd -> revents = 0 ;
112- }
113-
11488 // Set revents fields.
11589 for (size_t i = 0 ; i < nevents ; ++ i ) {
11690 const __wasi_event_t * event = & events [i ];
@@ -196,29 +170,6 @@ void __wasilibc_poll_ready(poll_state_t *state, short events) {
196170}
197171
198172static int poll_impl (struct pollfd * fds , size_t nfds , int timeout ) {
199- for (size_t i = 0 ; i < nfds ; ++ i ) {
200- fds [i ].revents = 0 ;
201- }
202-
203- // POLLPRI (exceptional/out-of-band data) is not supported in WASI.
204- // If all requested events across all fds are exclusively POLLPRI,
205- // return ENOSYS. Otherwise, strip POLLPRI and proceed.
206- {
207- bool has_pri_only = false;
208- bool has_non_pri = false;
209- for (size_t i = 0 ; i < nfds ; ++ i ) {
210- if (fds [i ].fd < 0 || fds [i ].events == 0 )
211- continue ;
212- if (fds [i ].events & ~POLLPRI )
213- has_non_pri = true;
214- else
215- has_pri_only = true;
216- }
217- if (has_pri_only && !has_non_pri ) {
218- errno = ENOSYS ;
219- return -1 ;
220- }
221- }
222173
223174 size_t max_pollables = (2 * nfds ) + 1 ;
224175 state_t states [max_pollables ];
@@ -330,18 +281,237 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
330281
331282#elif defined(__wasip3__ )
332283
284+ typedef struct {
285+ struct pollfd * pollfd ;
286+ uint32_t waitable ;
287+ // Callback/data pointer to invoke when `waitable` receives an event, and that
288+ // runs any completion logic and figures out what to do next.
289+ poll_ready_t ready ;
290+ void * ready_data ;
291+ } state_t ;
292+
293+ struct poll_state_t {
294+ state_t * states ;
295+ size_t len ;
296+ size_t cap ;
297+
298+ wasip3_waitable_set_t set ;
299+
300+ int event_count ;
301+ struct pollfd * pollfd ;
302+ };
303+
304+ int __wasilibc_poll_add (poll_state_t * state , uint32_t waitable ,
305+ poll_ready_t ready , void * ready_data ) {
306+ if (state -> len >= state -> cap ) {
307+ errno = ENOMEM ;
308+ return -1 ;
309+ }
310+ state -> states [state -> len ].pollfd = state -> pollfd ;
311+ state -> states [state -> len ].waitable = waitable ;
312+ state -> states [state -> len ].ready = ready ;
313+ state -> states [state -> len ].ready_data = ready_data ;
314+ wasip3_waitable_join (waitable , state -> set );
315+ state -> len += 1 ;
316+ return 0 ;
317+ }
318+
319+ void __wasilibc_poll_ready (poll_state_t * state , short events ) {
320+ if (events != 0 ) {
321+ if (state -> pollfd -> revents == 0 ) {
322+ ++ state -> event_count ;
323+ }
324+ state -> pollfd -> revents |= events ;
325+ }
326+ }
327+
333328static int poll_impl (struct pollfd * fds , size_t nfds , int timeout ) {
334- (void ) fds ;
335- (void ) nfds ;
336- (void ) timeout ;
337- errno = ENOTSUP ;
338- return -1 ;
329+ // TODO(wasip3) this calculation of 2n+1 is a coarse approximation but not
330+ // necessarily accurate. This worked well for wasip2 but this should be
331+ // revisited for wasip3. Maybe make this a dynamically allocated array?
332+ size_t max_pollables = (2 * nfds ) + 1 ;
333+ state_t states [max_pollables ];
334+
335+ poll_state_t state ;
336+ state .set = wasip3_waitable_set_new ();
337+ state .states = states ;
338+ state .len = 0 ;
339+ state .cap = max_pollables ;
340+ state .event_count = 0 ;
341+ state .pollfd = fds ;
342+
343+ int ret = -1 ;
344+ wasip3_subtask_t timeout_subtask = 0 ;
345+
346+ for (size_t i = 0 ; i < nfds ; ++ i ) {
347+ struct pollfd * pollfd = fds + i ;
348+ if (pollfd -> fd < 0 )
349+ continue ;
350+ state .pollfd = pollfd ;
351+ descriptor_table_entry_t * entry = descriptor_table_get_ref (pollfd -> fd );
352+ if (!entry ) {
353+ errno = EBADF ;
354+ goto out ;
355+ }
356+
357+ // If this descriptor has a custom registration function then
358+ // use that exclusively.
359+ if (entry -> vtable -> poll_register ) {
360+ if (entry -> vtable -> poll_register (entry -> data , & state , pollfd -> events ) < 0 )
361+ goto out ;
362+ continue ;
363+ }
364+
365+ // Strip POLLPRI, it is never reported in WASI (no OOB data).
366+ short events = pollfd -> events & ~POLLPRI ;
367+
368+ // Without a custom registration handle read/write readiness
369+ // below, but everything else is unsupported.
370+ if (events & ~(POLLRDNORM | POLLWRNORM )) {
371+ errno = EOPNOTSUPP ;
372+ goto out ;
373+ }
374+
375+
376+ 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 ;
382+ }
383+
384+ if (events & POLLWRNORM ) {
385+ // TODO(wasip3): use `get_write_stream` to implement this (see
386+ // `POLLRDNORM` above).
387+ errno = EOPNOTSUPP ;
388+ goto out ;
389+ }
390+ }
391+
392+ // If something ended up being ready during registration then zero out the
393+ // timeout and continue proceeding as if this is a non-blocking call.
394+ if (state .event_count > 0 )
395+ timeout = 0 ;
396+
397+ // If a timeout is specified then spawn a subtask using
398+ // `monotonic-clock#wait-for`. This subtask is then added to the
399+ // waitable-set. If the timeout completes immediately then pretend like
400+ // there's no timeout and keep going.
401+ //
402+ // Note that in `out`, the exit of this function, the subtask is cleaned up
403+ // if it's still in-progress.
404+ if (timeout > 0 ) {
405+ wasip3_subtask_status_t status = monotonic_clock_wait_for (timeout * 1000000 );
406+ if (WASIP3_SUBTASK_STATE (status ) == WASIP3_SUBTASK_RETURNED ) {
407+ timeout = 0 ;
408+ } else {
409+ timeout_subtask = WASIP3_SUBTASK_HANDLE (status );
410+ wasip3_waitable_join (timeout_subtask , state .set );
411+ }
412+ }
413+
414+ // Perform the actual blocking operation, if applicable. If the timeout is
415+ // still nonzero at this point then it means that it's time to actually
416+ // block. That handles the case where the timeout is negative as no subtask
417+ // was spawned and this will wait indefinitely. It also handles the case of a
418+ // timeout where a subtask was spawned and it's in the set being waited on.
419+ //
420+ // If the timeout is 0, however, then just perform a poll-style operation to
421+ // see what happened.
422+ wasip3_event_t event ;
423+ if (timeout != 0 ) {
424+ wasip3_waitable_set_wait (state .set , & event );
425+ } else {
426+ wasip3_waitable_set_poll (state .set , & event );
427+ }
428+ while (event .event != WASIP3_EVENT_NONE ) {
429+ // If this event is for the timeout subtask then that's handled directly here
430+ // as it's not related to any fds. Upon receiving the terminal status of
431+ // the subtask it's no longer candidate for cancellation so it's immediately
432+ // dropped and zero'd out to avoid processing in the `out` label below.
433+ if (event .waitable == timeout_subtask ) {
434+ assert (event .event == WASIP3_EVENT_SUBTASK );
435+ assert (event .code == WASIP3_SUBTASK_RETURNED );
436+ wasip3_subtask_drop (timeout_subtask );
437+ timeout_subtask = 0 ;
438+ } else {
439+ // If this event is not for the timeout subtask then it's something that
440+ // was added to the waitable-set. That's processed here directly. Search
441+ // through the list of `state_t` waitables, find the right one, and invoke
442+ // its `ready` callback. The `ready` callback will dictate what to do
443+ // with `event` and will call `__wasilibc_poll_ready` as appropriate.
444+ //
445+ // TODO: having to search through the list isn't great, but short of
446+ // having some sort of hash table or map going from waitable->state_t
447+ // that's the best that can be done right. At least `poll` as a POSIX
448+ // interface is expected to take linear time, right?
449+ for (size_t i = 0 ; i < state .len ; i ++ ) {
450+ state_t * p = & state .states [i ];
451+ if (p -> waitable != event .waitable )
452+ continue ;
453+ 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.
458+ p -> waitable = 0 ;
459+ p -> ready (p -> ready_data , & state , & event );
460+ }
461+ }
462+
463+ // After this event has finished being processed try to get another event.
464+ // This will drain the waitable-set of all ready events in one invocation of
465+ // this function and avoid unncessary setup/teardown in `poll`, in theory.
466+ wasip3_waitable_set_poll (state .set , & event );
467+ }
468+
469+ ret = state .event_count ;
470+
471+ out :
472+ if (timeout_subtask != 0 ) {
473+ wasip3_subtask_cancel (timeout_subtask );
474+ wasip3_subtask_drop (timeout_subtask );
475+ }
476+ for (size_t i = 0 ; i < state .len ; i ++ ) {
477+ uint32_t waitable = state .states [i ].waitable ;
478+ if (waitable )
479+ wasip3_waitable_join (waitable , 0 );
480+ }
481+ wasip3_waitable_set_drop (state .set );
482+
483+ return ret ;
339484}
340485
341486#else
342487# error "Unknown WASI version"
343488#endif
344489
490+ // POLLPRI (exceptional/out-of-band data) is not supported in WASI.
491+ // If all requested events across all fds are exclusively POLLPRI,
492+ // return ENOSYS. Otherwise, strip POLLPRI and proceed.
493+ static int validate_something_not_pollpri (struct pollfd * fds , size_t nfds ) {
494+ bool has_pri_only = false;
495+ bool has_non_pri = false;
496+ for (size_t i = 0 ; i < nfds ; ++ i ) {
497+ if (fds [i ].fd < 0 || fds [i ].events == 0 )
498+ continue ;
499+ if (fds [i ].events & ~POLLPRI )
500+ has_non_pri = true;
501+ else
502+ has_pri_only = true;
503+ }
504+ if (has_pri_only && !has_non_pri ) {
505+ errno = ENOSYS ;
506+ return -1 ;
507+ }
508+ return 0 ;
509+ }
510+
345511int poll (struct pollfd * fds , nfds_t nfds , int timeout ) {
346- return poll_impl (fds , nfds , timeout );
512+ if (validate_something_not_pollpri (fds , nfds ) < 0 )
513+ return -1 ;
514+ for (size_t i = 0 ; i < nfds ; ++ i )
515+ fds [i ].revents = 0 ;
516+ return poll_impl (fds , nfds , timeout );
347517}
0 commit comments