Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions system/lib/libc/emscripten_syscall_stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 0 additions & 2 deletions system/lib/libc/musl/arch/emscripten/bits/syscall.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions system/lib/libc/musl/arch/emscripten/syscall_arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 11 additions & 0 deletions system/lib/libc/musl/src/select/ppoll.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
15 changes: 15 additions & 0 deletions system/lib/libc/musl/src/select/pselect.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
}
6 changes: 2 additions & 4 deletions test/codesize/test_codesize_hello_dylink_all.json
Original file line number Diff line number Diff line change
Expand Up @@ -1889,9 +1889,7 @@
"__syscall_munlockall",
"__syscall_munmap",
"__syscall_pause",
"__syscall_ppoll",
"__syscall_prlimit64",
"__syscall_pselect6",
"__syscall_recvmmsg",
"__syscall_sendmmsg",
"__syscall_setdomainname",
Expand Down Expand Up @@ -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",
Expand Down
221 changes: 221 additions & 0 deletions test/core/test_ppoll_blocking.c
Original file line number Diff line number Diff line change
@@ -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 <poll.h>
#include <time.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <sys/time.h>

#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;
}
Loading
Loading