Skip to content

Commit 43be3a1

Browse files
authored
Implement ppoll and pselect in terms of poll and select (#26482)
Because we don't support true async signals this is safe to do in userspace. Followup to #26084
1 parent 080aa28 commit 43be3a1

File tree

9 files changed

+485
-10
lines changed

9 files changed

+485
-10
lines changed

system/lib/libc/emscripten_syscall_stubs.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,6 @@ weak int __syscall_setsockopt(int sockfd, int level, int optname, intptr_t optva
262262

263263
UNIMPLEMENTED(acct, (intptr_t filename))
264264
UNIMPLEMENTED(mincore, (intptr_t addr, size_t length, intptr_t vec))
265-
UNIMPLEMENTED(pselect6, (int nfds, intptr_t readfds, intptr_t writefds, intptr_t exceptfds, intptr_t timeout, intptr_t sigmaks))
266-
UNIMPLEMENTED(ppoll, (intptr_t fds, int nfds, intptr_t timeout, intptr_t sigmask, int size))
267265
UNIMPLEMENTED(recvmmsg, (int sockfd, intptr_t msgvec, size_t vlen, int flags, ...))
268266
UNIMPLEMENTED(sendmmsg, (int sockfd, intptr_t msgvec, size_t vlen, int flags, ...))
269267
UNIMPLEMENTED(shutdown, (int sockfd, int how, int dummy, int dummy2, int dummy3, int dummy4))

system/lib/libc/musl/arch/emscripten/bits/syscall.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@
6565
#define SYS_readlinkat __syscall_readlinkat
6666
#define SYS_fchmodat2 __syscall_fchmodat2
6767
#define SYS_faccessat __syscall_faccessat
68-
#define SYS_pselect6 __syscall_pselect6
69-
#define SYS_ppoll __syscall_ppoll
7068
#define SYS_utimensat __syscall_utimensat
7169
#define SYS_fallocate __syscall_fallocate
7270
#define SYS_dup3 __syscall_dup3

system/lib/libc/musl/arch/emscripten/syscall_arch.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,6 @@ int __syscall_symlinkat(intptr_t target, int newdirfd, intptr_t linkpath);
8989
int __syscall_readlinkat(int dirfd, intptr_t path, intptr_t buf, size_t bufsize);
9090
int __syscall_fchmodat2(int dirfd, intptr_t path, int mode, int flags);
9191
int __syscall_faccessat(int dirfd, intptr_t path, int amode, int flags);
92-
int __syscall_pselect6(int nfds, intptr_t readfds, intptr_t writefds, intptr_t exceptfds, intptr_t timeout, intptr_t sigmask);
93-
int __syscall_ppoll(intptr_t fds, int nfds, intptr_t timeout, intptr_t sigmask, int size);
9492
int __syscall_utimensat(int dirfd, intptr_t path, intptr_t times, int flags);
9593
int __syscall_fallocate(int fd, int mode, off_t offset, off_t len);
9694
int __syscall_dup3(int fd, int suggestfd, int flags);

system/lib/libc/musl/src/select/ppoll.c

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

1010
int ppoll(struct pollfd *fds, nfds_t n, const struct timespec *to, const sigset_t *mask)
1111
{
12+
#ifdef __EMSCRIPTEN__
13+
// Emscripten does not support true async signals so we just implement ppoll
14+
// in terms of poll here in userspace.
15+
int timeout = (to == NULL) ? -1 : (to->tv_sec * 1000 + to->tv_nsec / 1000000);
16+
sigset_t origmask;
17+
pthread_sigmask(SIG_SETMASK, mask, &origmask);
18+
int rtn = poll(fds, n, timeout);
19+
pthread_sigmask(SIG_SETMASK, &origmask, NULL);
20+
return rtn;
21+
#else
1222
time_t s = to ? to->tv_sec : 0;
1323
long ns = to ? to->tv_nsec : 0;
1424
#ifdef SYS_ppoll_time64
@@ -23,4 +33,5 @@ int ppoll(struct pollfd *fds, nfds_t n, const struct timespec *to, const sigset_
2333
#endif
2434
return syscall_cp(SYS_ppoll, fds, n,
2535
to ? ((long[]){s, ns}) : 0, mask, _NSIG/8);
36+
#endif
2637
}

system/lib/libc/musl/src/select/pselect.c

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

1010
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)
1111
{
12+
#ifdef __EMSCRIPTEN__
13+
// Emscripten does not support true async signals so we just implement pselect
14+
// in terms of select here in userspace.
15+
struct timeval tv_timeout;
16+
if (ts) {
17+
tv_timeout.tv_sec = ts->tv_sec;
18+
tv_timeout.tv_usec = ts->tv_nsec / 1000;
19+
}
20+
sigset_t origmask;
21+
pthread_sigmask(SIG_SETMASK, mask, &origmask);
22+
int rtn = select(n, rfds, wfds, efds, ts ? &tv_timeout : NULL);
23+
pthread_sigmask(SIG_SETMASK, &origmask, NULL);
24+
return rtn;
25+
#else
1226
syscall_arg_t data[2] = { (uintptr_t)mask, _NSIG/8 };
1327
time_t s = ts ? ts->tv_sec : 0;
1428
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
2337
#endif
2438
return syscall_cp(SYS_pselect6, n, rfds, wfds, efds,
2539
ts ? ((long[]){s, ns}) : 0, data);
40+
#endif
2641
}

test/codesize/test_codesize_hello_dylink_all.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,9 +1889,7 @@
18891889
"__syscall_munlockall",
18901890
"__syscall_munmap",
18911891
"__syscall_pause",
1892-
"__syscall_ppoll",
18931892
"__syscall_prlimit64",
1894-
"__syscall_pselect6",
18951893
"__syscall_recvmmsg",
18961894
"__syscall_sendmmsg",
18971895
"__syscall_setdomainname",
@@ -3751,13 +3749,13 @@
37513749
"$__syscall_msync",
37523750
"$__syscall_munmap",
37533751
"$__syscall_pause",
3754-
"$__syscall_ppoll",
37553752
"$__syscall_prlimit64",
3756-
"$__syscall_pselect6",
3753+
"$__syscall_recvmmsg",
37573754
"$__syscall_setdomainname",
37583755
"$__syscall_setpgid",
37593756
"$__syscall_setpriority",
37603757
"$__syscall_setsockopt",
3758+
"$__syscall_shutdown",
37613759
"$__syscall_sync",
37623760
"$__syscall_umask",
37633761
"$__syscall_uname",

test/core/test_ppoll_blocking.c

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
* Copyright 2025 The Emscripten Authors. All rights reserved.
3+
* Emscripten is available under two separate licenses, the MIT license and the
4+
* University of Illinois/NCSA Open Source License. Both these licenses can be
5+
* found in the LICENSE file.
6+
*/
7+
8+
// Duplicate of test_poll_blocking.c using ppoll() instead of poll()
9+
10+
#include <poll.h>
11+
#include <time.h>
12+
#include <assert.h>
13+
#include <stdio.h>
14+
#include <unistd.h>
15+
#include <pthread.h>
16+
#include <string.h>
17+
#include <sys/time.h>
18+
19+
#define TIMEOUT_MS 300
20+
#define TIMEOUT_NS (TIMEOUT_MS * 1000000)
21+
22+
// It is possible for the node timers (such as setTimeout or Atomics.wait) to wake up
23+
// slightly earlier than requested. Because we measure times accurately using
24+
// clock_gettime, we give tests a 5 milliseconds error margin to avoid flaky timeouts.
25+
#define TIMEOUT_MARGIN_MS 5
26+
27+
void sleep_ms(int ms) {
28+
usleep(ms * 1000);
29+
}
30+
31+
int64_t timespec_delta_ms(struct timespec* begin, struct timespec* end) {
32+
int64_t delta_sec = end->tv_sec - begin->tv_sec;
33+
int64_t delta_nsec = end->tv_nsec - begin->tv_nsec;
34+
35+
assert(delta_sec >= 0);
36+
assert(delta_nsec > -1000000000 && delta_nsec < 1000000000);
37+
38+
int64_t delta_ms = (delta_sec * 1000) + (delta_nsec / 1000000);
39+
assert(delta_ms >= 0);
40+
return delta_ms;
41+
}
42+
43+
// Check if timeout works without fds
44+
void test_timeout_without_fds() {
45+
printf("test_timeout_without_fds\n");
46+
struct timespec begin, end;
47+
48+
clock_gettime(CLOCK_MONOTONIC, &begin);
49+
struct timespec timeout;
50+
timeout.tv_sec = 0;
51+
timeout.tv_nsec = TIMEOUT_NS;
52+
assert(ppoll(NULL, 0, &timeout, NULL) == 0);
53+
clock_gettime(CLOCK_MONOTONIC, &end);
54+
55+
int64_t duration = timespec_delta_ms(&begin, &end);
56+
printf(" -> duration: %lld ms\n", duration);
57+
assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS);
58+
}
59+
60+
// Check if timeout works with fds without events
61+
void test_timeout_with_fds_without_events() {
62+
printf("test_timeout_with_fds_without_events\n");
63+
struct timespec begin, end;
64+
int pipe_a[2];
65+
66+
assert(pipe(pipe_a) == 0);
67+
68+
clock_gettime(CLOCK_MONOTONIC, &begin);
69+
struct pollfd fds = {pipe_a[0], 0, 0};
70+
struct timespec timeout;
71+
timeout.tv_sec = 0;
72+
timeout.tv_nsec = TIMEOUT_NS;
73+
assert(ppoll(&fds, 1, &timeout, NULL) == 0);
74+
clock_gettime(CLOCK_MONOTONIC, &end);
75+
76+
int64_t duration = timespec_delta_ms(&begin, &end);
77+
printf(" -> duration: %lld ms\n", duration);
78+
assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS);
79+
80+
close(pipe_a[0]); close(pipe_a[1]);
81+
}
82+
83+
int pipe_shared[2];
84+
85+
void *write_after_sleep(void * arg) {
86+
const char *t = "test\n";
87+
88+
sleep_ms(TIMEOUT_MS);
89+
write(pipe_shared[1], t, strlen(t));
90+
91+
return NULL;
92+
}
93+
94+
// Check if ppoll can unblock on an event
95+
void test_unblock_ppoll() {
96+
printf("test_unblock_ppoll\n");
97+
struct timespec begin, end;
98+
pthread_t tid;
99+
int pipe_a[2];
100+
101+
assert(pipe(pipe_a) == 0);
102+
assert(pipe(pipe_shared) == 0);
103+
104+
struct pollfd fds[2] = {
105+
{pipe_a[0], POLLIN, 0},
106+
{pipe_shared[0], POLLIN, 0},
107+
};
108+
clock_gettime(CLOCK_MONOTONIC, &begin);
109+
assert(pthread_create(&tid, NULL, write_after_sleep, NULL) == 0);
110+
assert(ppoll(fds, 2, NULL, NULL) == 1);
111+
clock_gettime(CLOCK_MONOTONIC, &end);
112+
assert(fds[1].revents & POLLIN);
113+
114+
int64_t duration = timespec_delta_ms(&begin, &end);
115+
printf(" -> duration: %lld ms\n", duration);
116+
assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS);
117+
118+
pthread_join(tid, NULL);
119+
120+
close(pipe_a[0]); close(pipe_a[1]);
121+
close(pipe_shared[0]); close(pipe_shared[1]);
122+
}
123+
124+
int threads_running = 0;
125+
pthread_mutex_t running_lock = PTHREAD_MUTEX_INITIALIZER;
126+
pthread_cond_t running_cv = PTHREAD_COND_INITIALIZER;
127+
128+
void *do_ppoll_in_thread(void * arg) {
129+
struct timespec begin, end;
130+
131+
clock_gettime(CLOCK_MONOTONIC, &begin);
132+
struct pollfd fds = {pipe_shared[0], POLLIN, 0};
133+
pthread_mutex_lock(&running_lock);
134+
threads_running++;
135+
pthread_cond_signal(&running_cv);
136+
pthread_mutex_unlock(&running_lock);
137+
struct timespec timeout;
138+
timeout.tv_sec = 4;
139+
timeout.tv_nsec = 0;
140+
assert(ppoll(&fds, 1, &timeout, NULL) == 1);
141+
clock_gettime(CLOCK_MONOTONIC, &end);
142+
assert(fds.revents & POLLIN);
143+
144+
int64_t duration = timespec_delta_ms(&begin, &end);
145+
printf(" -> duration: %lld ms\n", duration);
146+
assert((duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS) && (duration < 4000));
147+
148+
return NULL;
149+
}
150+
151+
// Check if ppoll works in threads
152+
void test_ppoll_in_threads() {
153+
printf("test_ppoll_in_threads\n");
154+
pthread_t tid1, tid2;
155+
const char *t = "test\n";
156+
157+
assert(pipe(pipe_shared) == 0);
158+
159+
assert(pthread_create(&tid1, NULL, do_ppoll_in_thread, NULL) == 0);
160+
assert(pthread_create(&tid2, NULL, do_ppoll_in_thread, NULL) == 0);
161+
pthread_mutex_lock(&running_lock);
162+
while (threads_running != 2) {
163+
pthread_cond_wait(&running_cv, &running_lock);
164+
}
165+
pthread_mutex_unlock(&running_lock);
166+
167+
sleep_ms(2 * TIMEOUT_MS);
168+
write(pipe_shared[1], t, strlen(t));
169+
170+
pthread_join(tid1, NULL);
171+
pthread_join(tid2, NULL);
172+
173+
close(pipe_shared[0]); close(pipe_shared[1]);
174+
}
175+
176+
// Check if ppoll works with ready fds
177+
void test_ready_fds() {
178+
printf("test_ready_fds\n");
179+
struct timespec zero_timeout;
180+
zero_timeout.tv_sec = 0;
181+
zero_timeout.tv_nsec = 0;
182+
fd_set readfds;
183+
const char *t = "test\n";
184+
int pipe_c[2];
185+
int pipe_d[2];
186+
187+
assert(pipe(pipe_c) == 0);
188+
assert(pipe(pipe_d) == 0);
189+
190+
write(pipe_c[1], t, strlen(t));
191+
write(pipe_d[1], t, strlen(t));
192+
193+
struct pollfd fds[2] = {
194+
{pipe_c[0], POLLIN, 0},
195+
{pipe_d[0], POLLIN, 0},
196+
};
197+
198+
assert(ppoll(fds, 2, &zero_timeout, NULL) == 2);
199+
assert(fds[0].revents & POLLIN);
200+
assert(fds[1].revents & POLLIN);
201+
202+
fds[0].revents = 0;
203+
fds[1].revents = 0;
204+
205+
assert(ppoll(fds, 2, &zero_timeout, NULL) == 2);
206+
assert(fds[0].revents & POLLIN);
207+
assert(fds[1].revents & POLLIN);
208+
209+
close(pipe_c[0]); close(pipe_c[1]);
210+
close(pipe_d[0]); close(pipe_d[1]);
211+
}
212+
213+
int main() {
214+
test_ppoll_in_threads();
215+
test_timeout_without_fds();
216+
test_timeout_with_fds_without_events();
217+
test_unblock_ppoll();
218+
test_ready_fds();
219+
printf("done\n");
220+
return 0;
221+
}

0 commit comments

Comments
 (0)