From 20816eeb542f775b015c5c6d34195841dfdac863 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 18 Mar 2026 08:04:54 -0700 Subject: [PATCH] Implement ppoll and pselect in terms of poll and select Because we don't support true async signals this is safe to do in userspace. Followup to #26084 --- system/lib/libc/emscripten_syscall_stubs.c | 2 - .../libc/musl/arch/emscripten/bits/syscall.h | 2 - .../libc/musl/arch/emscripten/syscall_arch.h | 2 - system/lib/libc/musl/src/select/ppoll.c | 11 + system/lib/libc/musl/src/select/pselect.c | 15 ++ .../test_codesize_hello_dylink_all.json | 6 +- test/core/test_ppoll_blocking.c | 221 +++++++++++++++++ test/core/test_pselect_blocking.c | 228 ++++++++++++++++++ test/test_core.py | 8 + 9 files changed, 485 insertions(+), 10 deletions(-) create mode 100644 test/core/test_ppoll_blocking.c create mode 100644 test/core/test_pselect_blocking.c diff --git a/system/lib/libc/emscripten_syscall_stubs.c b/system/lib/libc/emscripten_syscall_stubs.c index 1240f7d5e3541..9ba7e411733e1 100644 --- a/system/lib/libc/emscripten_syscall_stubs.c +++ b/system/lib/libc/emscripten_syscall_stubs.c @@ -262,8 +262,6 @@ weak int __syscall_setsockopt(int sockfd, int level, int optname, intptr_t optva UNIMPLEMENTED(acct, (intptr_t filename)) UNIMPLEMENTED(mincore, (intptr_t addr, size_t length, intptr_t vec)) -UNIMPLEMENTED(pselect6, (int nfds, intptr_t readfds, intptr_t writefds, intptr_t exceptfds, intptr_t timeout, intptr_t sigmaks)) -UNIMPLEMENTED(ppoll, (intptr_t fds, int nfds, intptr_t timeout, intptr_t sigmask, int size)) UNIMPLEMENTED(recvmmsg, (int sockfd, intptr_t msgvec, size_t vlen, int flags, ...)) UNIMPLEMENTED(sendmmsg, (int sockfd, intptr_t msgvec, size_t vlen, int flags, ...)) UNIMPLEMENTED(shutdown, (int sockfd, int how, int dummy, int dummy2, int dummy3, int dummy4)) diff --git a/system/lib/libc/musl/arch/emscripten/bits/syscall.h b/system/lib/libc/musl/arch/emscripten/bits/syscall.h index db679242a8028..e3465a65c7406 100644 --- a/system/lib/libc/musl/arch/emscripten/bits/syscall.h +++ b/system/lib/libc/musl/arch/emscripten/bits/syscall.h @@ -65,8 +65,6 @@ #define SYS_readlinkat __syscall_readlinkat #define SYS_fchmodat2 __syscall_fchmodat2 #define SYS_faccessat __syscall_faccessat -#define SYS_pselect6 __syscall_pselect6 -#define SYS_ppoll __syscall_ppoll #define SYS_utimensat __syscall_utimensat #define SYS_fallocate __syscall_fallocate #define SYS_dup3 __syscall_dup3 diff --git a/system/lib/libc/musl/arch/emscripten/syscall_arch.h b/system/lib/libc/musl/arch/emscripten/syscall_arch.h index 0a2e506b23659..e2dd384acd78b 100644 --- a/system/lib/libc/musl/arch/emscripten/syscall_arch.h +++ b/system/lib/libc/musl/arch/emscripten/syscall_arch.h @@ -89,8 +89,6 @@ int __syscall_symlinkat(intptr_t target, int newdirfd, intptr_t linkpath); int __syscall_readlinkat(int dirfd, intptr_t path, intptr_t buf, size_t bufsize); int __syscall_fchmodat2(int dirfd, intptr_t path, int mode, int flags); int __syscall_faccessat(int dirfd, intptr_t path, int amode, int flags); -int __syscall_pselect6(int nfds, intptr_t readfds, intptr_t writefds, intptr_t exceptfds, intptr_t timeout, intptr_t sigmask); -int __syscall_ppoll(intptr_t fds, int nfds, intptr_t timeout, intptr_t sigmask, int size); int __syscall_utimensat(int dirfd, intptr_t path, intptr_t times, int flags); int __syscall_fallocate(int fd, int mode, off_t offset, off_t len); int __syscall_dup3(int fd, int suggestfd, int flags); diff --git a/system/lib/libc/musl/src/select/ppoll.c b/system/lib/libc/musl/src/select/ppoll.c index 9a0bf929584d7..4145c88a00a97 100644 --- a/system/lib/libc/musl/src/select/ppoll.c +++ b/system/lib/libc/musl/src/select/ppoll.c @@ -9,6 +9,16 @@ int ppoll(struct pollfd *fds, nfds_t n, const struct timespec *to, const sigset_t *mask) { +#ifdef __EMSCRIPTEN__ + // Emscripten does not support true async signals so we just implement ppoll + // in terms of poll here in userspace. + int timeout = (to == NULL) ? -1 : (to->tv_sec * 1000 + to->tv_nsec / 1000000); + sigset_t origmask; + pthread_sigmask(SIG_SETMASK, mask, &origmask); + int rtn = poll(fds, n, timeout); + pthread_sigmask(SIG_SETMASK, &origmask, NULL); + return rtn; +#else time_t s = to ? to->tv_sec : 0; long ns = to ? to->tv_nsec : 0; #ifdef SYS_ppoll_time64 @@ -23,4 +33,5 @@ int ppoll(struct pollfd *fds, nfds_t n, const struct timespec *to, const sigset_ #endif return syscall_cp(SYS_ppoll, fds, n, to ? ((long[]){s, ns}) : 0, mask, _NSIG/8); +#endif } diff --git a/system/lib/libc/musl/src/select/pselect.c b/system/lib/libc/musl/src/select/pselect.c index 54cfb291bba2c..a379549488331 100644 --- a/system/lib/libc/musl/src/select/pselect.c +++ b/system/lib/libc/musl/src/select/pselect.c @@ -9,6 +9,20 @@ int pselect(int n, fd_set *restrict rfds, fd_set *restrict wfds, fd_set *restrict efds, const struct timespec *restrict ts, const sigset_t *restrict mask) { +#ifdef __EMSCRIPTEN__ + // Emscripten does not support true async signals so we just implement pselect + // in terms of select here in userspace. + struct timeval tv_timeout; + if (ts) { + tv_timeout.tv_sec = ts->tv_sec; + tv_timeout.tv_usec = ts->tv_nsec / 1000; + } + sigset_t origmask; + pthread_sigmask(SIG_SETMASK, mask, &origmask); + int rtn = select(n, rfds, wfds, efds, ts ? &tv_timeout : NULL); + pthread_sigmask(SIG_SETMASK, &origmask, NULL); + return rtn; +#else syscall_arg_t data[2] = { (uintptr_t)mask, _NSIG/8 }; time_t s = ts ? ts->tv_sec : 0; long ns = ts ? ts->tv_nsec : 0; @@ -23,4 +37,5 @@ int pselect(int n, fd_set *restrict rfds, fd_set *restrict wfds, fd_set *restric #endif return syscall_cp(SYS_pselect6, n, rfds, wfds, efds, ts ? ((long[]){s, ns}) : 0, data); +#endif } diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index 861acd79a8904..0a916657b9e3d 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1889,9 +1889,7 @@ "__syscall_munlockall", "__syscall_munmap", "__syscall_pause", - "__syscall_ppoll", "__syscall_prlimit64", - "__syscall_pselect6", "__syscall_recvmmsg", "__syscall_sendmmsg", "__syscall_setdomainname", @@ -3751,13 +3749,13 @@ "$__syscall_msync", "$__syscall_munmap", "$__syscall_pause", - "$__syscall_ppoll", "$__syscall_prlimit64", - "$__syscall_pselect6", + "$__syscall_recvmmsg", "$__syscall_setdomainname", "$__syscall_setpgid", "$__syscall_setpriority", "$__syscall_setsockopt", + "$__syscall_shutdown", "$__syscall_sync", "$__syscall_umask", "$__syscall_uname", diff --git a/test/core/test_ppoll_blocking.c b/test/core/test_ppoll_blocking.c new file mode 100644 index 0000000000000..c36077d3610ee --- /dev/null +++ b/test/core/test_ppoll_blocking.c @@ -0,0 +1,221 @@ +/* + * Copyright 2025 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +// Duplicate of test_poll_blocking.c using ppoll() instead of poll() + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TIMEOUT_MS 300 +#define TIMEOUT_NS (TIMEOUT_MS * 1000000) + +// It is possible for the node timers (such as setTimeout or Atomics.wait) to wake up +// slightly earlier than requested. Because we measure times accurately using +// clock_gettime, we give tests a 5 milliseconds error margin to avoid flaky timeouts. +#define TIMEOUT_MARGIN_MS 5 + +void sleep_ms(int ms) { + usleep(ms * 1000); +} + +int64_t timespec_delta_ms(struct timespec* begin, struct timespec* end) { + int64_t delta_sec = end->tv_sec - begin->tv_sec; + int64_t delta_nsec = end->tv_nsec - begin->tv_nsec; + + assert(delta_sec >= 0); + assert(delta_nsec > -1000000000 && delta_nsec < 1000000000); + + int64_t delta_ms = (delta_sec * 1000) + (delta_nsec / 1000000); + assert(delta_ms >= 0); + return delta_ms; +} + +// Check if timeout works without fds +void test_timeout_without_fds() { + printf("test_timeout_without_fds\n"); + struct timespec begin, end; + + clock_gettime(CLOCK_MONOTONIC, &begin); + struct timespec timeout; + timeout.tv_sec = 0; + timeout.tv_nsec = TIMEOUT_NS; + assert(ppoll(NULL, 0, &timeout, NULL) == 0); + clock_gettime(CLOCK_MONOTONIC, &end); + + int64_t duration = timespec_delta_ms(&begin, &end); + printf(" -> duration: %lld ms\n", duration); + assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS); +} + +// Check if timeout works with fds without events +void test_timeout_with_fds_without_events() { + printf("test_timeout_with_fds_without_events\n"); + struct timespec begin, end; + int pipe_a[2]; + + assert(pipe(pipe_a) == 0); + + clock_gettime(CLOCK_MONOTONIC, &begin); + struct pollfd fds = {pipe_a[0], 0, 0}; + struct timespec timeout; + timeout.tv_sec = 0; + timeout.tv_nsec = TIMEOUT_NS; + assert(ppoll(&fds, 1, &timeout, NULL) == 0); + clock_gettime(CLOCK_MONOTONIC, &end); + + int64_t duration = timespec_delta_ms(&begin, &end); + printf(" -> duration: %lld ms\n", duration); + assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS); + + close(pipe_a[0]); close(pipe_a[1]); +} + +int pipe_shared[2]; + +void *write_after_sleep(void * arg) { + const char *t = "test\n"; + + sleep_ms(TIMEOUT_MS); + write(pipe_shared[1], t, strlen(t)); + + return NULL; +} + +// Check if ppoll can unblock on an event +void test_unblock_ppoll() { + printf("test_unblock_ppoll\n"); + struct timespec begin, end; + pthread_t tid; + int pipe_a[2]; + + assert(pipe(pipe_a) == 0); + assert(pipe(pipe_shared) == 0); + + struct pollfd fds[2] = { + {pipe_a[0], POLLIN, 0}, + {pipe_shared[0], POLLIN, 0}, + }; + clock_gettime(CLOCK_MONOTONIC, &begin); + assert(pthread_create(&tid, NULL, write_after_sleep, NULL) == 0); + assert(ppoll(fds, 2, NULL, NULL) == 1); + clock_gettime(CLOCK_MONOTONIC, &end); + assert(fds[1].revents & POLLIN); + + int64_t duration = timespec_delta_ms(&begin, &end); + printf(" -> duration: %lld ms\n", duration); + assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS); + + pthread_join(tid, NULL); + + close(pipe_a[0]); close(pipe_a[1]); + close(pipe_shared[0]); close(pipe_shared[1]); +} + +int threads_running = 0; +pthread_mutex_t running_lock = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t running_cv = PTHREAD_COND_INITIALIZER; + +void *do_ppoll_in_thread(void * arg) { + struct timespec begin, end; + + clock_gettime(CLOCK_MONOTONIC, &begin); + struct pollfd fds = {pipe_shared[0], POLLIN, 0}; + pthread_mutex_lock(&running_lock); + threads_running++; + pthread_cond_signal(&running_cv); + pthread_mutex_unlock(&running_lock); + struct timespec timeout; + timeout.tv_sec = 4; + timeout.tv_nsec = 0; + assert(ppoll(&fds, 1, &timeout, NULL) == 1); + clock_gettime(CLOCK_MONOTONIC, &end); + assert(fds.revents & POLLIN); + + int64_t duration = timespec_delta_ms(&begin, &end); + printf(" -> duration: %lld ms\n", duration); + assert((duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS) && (duration < 4000)); + + return NULL; +} + +// Check if ppoll works in threads +void test_ppoll_in_threads() { + printf("test_ppoll_in_threads\n"); + pthread_t tid1, tid2; + const char *t = "test\n"; + + assert(pipe(pipe_shared) == 0); + + assert(pthread_create(&tid1, NULL, do_ppoll_in_thread, NULL) == 0); + assert(pthread_create(&tid2, NULL, do_ppoll_in_thread, NULL) == 0); + pthread_mutex_lock(&running_lock); + while (threads_running != 2) { + pthread_cond_wait(&running_cv, &running_lock); + } + pthread_mutex_unlock(&running_lock); + + sleep_ms(2 * TIMEOUT_MS); + write(pipe_shared[1], t, strlen(t)); + + pthread_join(tid1, NULL); + pthread_join(tid2, NULL); + + close(pipe_shared[0]); close(pipe_shared[1]); +} + +// Check if ppoll works with ready fds +void test_ready_fds() { + printf("test_ready_fds\n"); + struct timespec zero_timeout; + zero_timeout.tv_sec = 0; + zero_timeout.tv_nsec = 0; + fd_set readfds; + const char *t = "test\n"; + int pipe_c[2]; + int pipe_d[2]; + + assert(pipe(pipe_c) == 0); + assert(pipe(pipe_d) == 0); + + write(pipe_c[1], t, strlen(t)); + write(pipe_d[1], t, strlen(t)); + + struct pollfd fds[2] = { + {pipe_c[0], POLLIN, 0}, + {pipe_d[0], POLLIN, 0}, + }; + + assert(ppoll(fds, 2, &zero_timeout, NULL) == 2); + assert(fds[0].revents & POLLIN); + assert(fds[1].revents & POLLIN); + + fds[0].revents = 0; + fds[1].revents = 0; + + assert(ppoll(fds, 2, &zero_timeout, NULL) == 2); + assert(fds[0].revents & POLLIN); + assert(fds[1].revents & POLLIN); + + close(pipe_c[0]); close(pipe_c[1]); + close(pipe_d[0]); close(pipe_d[1]); +} + +int main() { + test_ppoll_in_threads(); + test_timeout_without_fds(); + test_timeout_with_fds_without_events(); + test_unblock_ppoll(); + test_ready_fds(); + printf("done\n"); + return 0; +} diff --git a/test/core/test_pselect_blocking.c b/test/core/test_pselect_blocking.c new file mode 100644 index 0000000000000..258547d9cd218 --- /dev/null +++ b/test/core/test_pselect_blocking.c @@ -0,0 +1,228 @@ +/* + * Copyright 2025 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + * + * This file is essentially a copy of test_select_blocking.c but with select + * calls converted to pselect. This basically entails chaning the timeout from + * a `timeval` to `timespec` and passing an extra NULL argument for the sigmask. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define TIMEOUT_MS 300 +#define TIMEOUT_NS (TIMEOUT_MS * 1000000) + +// It is possible for the node timers (such as setTimeout or Atomics.wait) to wake up +// slightly earlier than requested. Because we measure times accurately using +// clock_gettime, we give tests a 5 milliseconds error margin to avoid flaky timeouts. +#define TIMEOUT_MARGIN_MS 5 + +void sleep_ms(int ms) { + usleep(ms * 1000); +} + +int64_t timespec_delta_ms(struct timespec* begin, struct timespec* end) { + int64_t delta_sec = end->tv_sec - begin->tv_sec; + int64_t delta_nsec = end->tv_nsec - begin->tv_nsec; + + assert(delta_sec >= 0); + assert(delta_nsec > -1000000000 && delta_nsec < 1000000000); + + int64_t delta_ms = (delta_sec * 1000) + (delta_nsec / 1000000); + assert(delta_ms >= 0); + return delta_ms; +} + +// Check if timeout works without fds +void test_timeout_without_fds() { + printf("test_timeout_without_fds\n"); + struct timespec begin, end; + struct timespec timeout; + + timeout.tv_sec = 0; + timeout.tv_nsec = TIMEOUT_NS; + clock_gettime(CLOCK_MONOTONIC, &begin); + assert(pselect(0, NULL, NULL, NULL, &timeout, NULL) == 0); + clock_gettime(CLOCK_MONOTONIC, &end); + + int64_t duration = timespec_delta_ms(&begin, &end); + printf(" -> duration: %lld ms\n", duration); + assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS); +} + +// Check if timeout works with fds without events +void test_timeout_with_fds_without_events() { + printf("test_timeout_with_fds_without_events\n"); + struct timespec begin, end; + struct timespec timeout; + fd_set readfds; + int pipe_a[2]; + + assert(pipe(pipe_a) == 0); + + timeout.tv_sec = 0; + timeout.tv_nsec = TIMEOUT_NS; + FD_ZERO(&readfds); + FD_SET(pipe_a[0], &readfds); + clock_gettime(CLOCK_MONOTONIC, &begin); + assert(pselect(pipe_a[0] + 1, &readfds, NULL, NULL, &timeout, NULL) == 0); + clock_gettime(CLOCK_MONOTONIC, &end); + + int64_t duration = timespec_delta_ms(&begin, &end); + printf(" -> duration: %lld ms\n", duration); + assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS); + + close(pipe_a[0]); close(pipe_a[1]); +} + +int pipe_shared[2]; + +void *write_after_sleep(void * arg) { + const char *t = "test\n"; + + sleep_ms(TIMEOUT_MS); + write(pipe_shared[1], t, strlen(t)); + + return NULL; +} + +// Check if select can unblock on an event +void test_unblock_pselect() { + printf("test_unblock_pselect\n"); + struct timespec begin, end; + fd_set readfds; + pthread_t tid; + int pipe_a[2]; + + assert(pipe(pipe_a) == 0); + assert(pipe(pipe_shared) == 0); + + FD_ZERO(&readfds); + FD_SET(pipe_a[0], &readfds); + FD_SET(pipe_shared[0], &readfds); + int maxfd = (pipe_a[0] > pipe_shared[0] ? pipe_a[0] : pipe_shared[0]); + clock_gettime(CLOCK_MONOTONIC, &begin); + assert(pthread_create(&tid, NULL, write_after_sleep, NULL) == 0); + assert(pselect(maxfd + 1, &readfds, NULL, NULL, NULL, NULL) == 1); + clock_gettime(CLOCK_MONOTONIC, &end); + assert(FD_ISSET(pipe_shared[0], &readfds)); + + int64_t duration = timespec_delta_ms(&begin, &end); + printf(" -> duration: %lld ms\n", duration); + assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS); + + pthread_join(tid, NULL); + + close(pipe_a[0]); close(pipe_a[1]); + close(pipe_shared[0]); close(pipe_shared[1]); +} + +int threads_running = 0; +pthread_mutex_t running_lock = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t running_cv = PTHREAD_COND_INITIALIZER; + +void *do_pselect_in_thread(void * arg) { + struct timespec begin, end; + struct timespec timeout; + fd_set readfds; + timeout.tv_sec = 4; + timeout.tv_nsec = 0; + + FD_ZERO(&readfds); + FD_SET(pipe_shared[0], &readfds); + int maxfd = pipe_shared[0]; + + clock_gettime(CLOCK_MONOTONIC, &begin); + pthread_mutex_lock(&running_lock); + threads_running++; + pthread_cond_signal(&running_cv); + pthread_mutex_unlock(&running_lock); + assert(pselect(maxfd + 1, &readfds, NULL, NULL, &timeout, NULL) == 1); + clock_gettime(CLOCK_MONOTONIC, &end); + assert(FD_ISSET(pipe_shared[0], &readfds)); + + int64_t duration = timespec_delta_ms(&begin, &end); + printf(" -> duration: %lld ms\n", duration); + assert((duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS) && (duration < 4000)); + + return NULL; +} + +// Check if select works in threads +void test_pselect_in_threads() { + printf("test_pselect_in_threads\n"); + pthread_t tid1, tid2; + const char *t = "test\n"; + + assert(pipe(pipe_shared) == 0); + + assert(pthread_create(&tid1, NULL, do_pselect_in_thread, NULL) == 0); + assert(pthread_create(&tid2, NULL, do_pselect_in_thread, NULL) == 0); + pthread_mutex_lock(&running_lock); + while (threads_running != 2) { + pthread_cond_wait(&running_cv, &running_lock); + } + pthread_mutex_unlock(&running_lock); + + sleep_ms(2 * TIMEOUT_MS); + write(pipe_shared[1], t, strlen(t)); + + pthread_join(tid1, NULL); + pthread_join(tid2, NULL); + + close(pipe_shared[0]); close(pipe_shared[1]); +} + +// Check if pselect works with ready fds +void test_ready_fds() { + printf("test_ready_fds\n"); + struct timespec timeout; + fd_set readfds; + const char *t = "test\n"; + int pipe_c[2]; + int pipe_d[2]; + + assert(pipe(pipe_c) == 0); + assert(pipe(pipe_d) == 0); + + write(pipe_c[1], t, strlen(t)); + write(pipe_d[1], t, strlen(t)); + int maxfd = (pipe_c[0] > pipe_d[0] ? pipe_c[0] : pipe_d[0]); + + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + FD_ZERO(&readfds); + FD_SET(pipe_c[0], &readfds); + FD_SET(pipe_d[0], &readfds); + assert(pselect(maxfd + 1, &readfds, NULL, NULL, &timeout, NULL) == 2); + assert(FD_ISSET(pipe_c[0], &readfds)); + assert(FD_ISSET(pipe_d[0], &readfds)); + + FD_ZERO(&readfds); + FD_SET(pipe_c[0], &readfds); + FD_SET(pipe_d[0], &readfds); + assert(pselect(maxfd + 1, &readfds, NULL, NULL, NULL, NULL) == 2); + assert(FD_ISSET(pipe_c[0], &readfds)); + assert(FD_ISSET(pipe_d[0], &readfds)); + + close(pipe_c[0]); close(pipe_c[1]); + close(pipe_d[0]); close(pipe_d[1]); +} + +int main() { + test_pselect_in_threads(); + test_timeout_without_fds(); + test_timeout_with_fds_without_events(); + test_unblock_pselect(); + test_ready_fds(); + printf("done\n"); + return 0; +} diff --git a/test/test_core.py b/test/test_core.py index a6b982413e2f9..0d2d8478d1f49 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -9622,10 +9622,18 @@ def test_syscall_intercept(self): def test_select_blocking(self): self.do_runf('core/test_select_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1']) + @requires_pthreads + def test_pselect_blocking(self): + self.do_runf('core/test_pselect_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1']) + @requires_pthreads def test_poll_blocking(self): self.do_runf('core/test_poll_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1']) + @requires_pthreads + def test_ppoll_blocking(self): + self.do_runf('core/test_ppoll_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1']) + @with_asyncify_and_jspi def test_poll_blocking_asyncify(self): if self.get_setting('JSPI') and engine_is_v8(self.get_current_js_engine()):