Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/lib/libsigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ sigs = {
__syscall_openat__sig: 'iipip',
__syscall_pipe2__sig: 'ipi',
__syscall_poll__sig: 'ipii',
__syscall_poll_nonblocking__sig: 'ipi',
__syscall_readlinkat__sig: 'iippp',
__syscall_recvfrom__sig: 'iippipp',
__syscall_recvmsg__sig: 'iipiiii',
Expand Down
55 changes: 36 additions & 19 deletions src/lib/libsyscall.js
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ var SyscallsLibrary = {
},
__syscall_poll__proxy: 'sync',
__syscall_poll__async: 'auto',
__syscall_poll__deps: ['$doPoll'],
__syscall_poll: (fds, nfds, timeout) => {
#if PTHREADS || ASYNCIFY
#if PTHREADS
Expand Down Expand Up @@ -622,9 +623,31 @@ var SyscallsLibrary = {
}, timeout);
cleanupFuncs.push(() => clearTimeout(t));
}
// A zero timeout never registers notifications: the derivation alone
// answers, matching the non-blocking probe.
var count = doPoll(fds, nfds, timeout, makeNotifyCallback);
if (count || !timeout) {
asyncPollComplete(count);
}
return promise;
}
#endif

var count = doPoll(fds, nfds, 0, undefined);
#if ASSERTIONS
if (!count && timeout != 0) warnOnce('non-zero poll() timeout not supported: ' + timeout)
#endif
return count;
},
// The shared readiness derivation: one pass over the pollfds, writing
// revents and returning the ready count. With a nonzero `timeout`, a
// readiness notification is also registered on each stream by the same
// `stream_ops.poll` call that derives it, so there is no window between
// registration and derivation; a zero `timeout` means the caller will not
// wait, so no notification is registered (the plain probe).
$doPoll__internal: true,
$doPoll__deps: ['$FS'],
$doPoll: (fds, nfds, timeout, makeNotifyCallback) => {
var count = 0;
for (var i = 0; i < nfds; i++) {
var pollfd = fds + {{{ C_STRUCTS.pollfd.__size__ }}} * i;
Expand All @@ -634,12 +657,9 @@ var SyscallsLibrary = {
var stream = FS.getStream(fd);
if (stream) {
if (stream.stream_ops.poll) {
#if PTHREADS || ASYNCIFY
if (isAsyncContext && timeout) {
flags = stream.stream_ops.poll(stream, timeout, makeNotifyCallback(stream, pollfd));
} else
#endif
flags = stream.stream_ops.poll(stream, -1);
flags = timeout
? stream.stream_ops.poll(stream, timeout, makeNotifyCallback(stream, pollfd))
: stream.stream_ops.poll(stream, -1);
} else {
flags = {{{ cDefs.POLLIN | cDefs.POLLOUT }}};
}
Expand All @@ -648,21 +668,18 @@ var SyscallsLibrary = {
if (flags) count++;
{{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'flags', 'i16') }}};
Comment thread
sbc100 marked this conversation as resolved.
}

#if PTHREADS || ASYNCIFY
if (isAsyncContext) {
if (count || !timeout) {
asyncPollComplete(count);
}
return promise;
}
#endif

#if ASSERTIONS
if (!count && timeout != 0) warnOnce('non-zero poll() timeout not supported: ' + timeout)
#endif
return count;
},
// libc routes zero-timeout poll() calls here: the same synchronous
// readiness derivation as __syscall_poll, but as a plain import that never
// suspends, so probes stay callable from any context (under JSPI,
// __syscall_poll is a suspending import and traps when called from a stack
// that wasn't entered through a promising export).
__syscall_poll_nonblocking__proxy: 'sync',
__syscall_poll_nonblocking__deps: ['$doPoll'],
__syscall_poll_nonblocking: (fds, nfds) => {
return doPoll(fds, nfds, 0, undefined);
},
__syscall_getcwd__deps: ['$lengthBytesUTF8', '$stringToUTF8'],
__syscall_getcwd: (buf, size) => {
if (size === 0) return -{{{ cDefs.EINVAL }}};
Expand Down
1 change: 1 addition & 0 deletions system/include/emscripten/syscalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ int __syscall_mlockall(int flags);
int __syscall_munlockall(void);
int __syscall_mremap(intptr_t old_addr, size_t old_size, size_t new_size, int flags, intptr_t new_addr);
int __syscall_poll(intptr_t fds, int nfds, int timeout);
int __syscall_poll_nonblocking(intptr_t fds, int nfds);
int __syscall_getcwd(intptr_t buf, size_t size);
intptr_t __syscall_mmap2(intptr_t addr, size_t len, int prot, int flags, int fd, off_t offset);
int __syscall_truncate64(intptr_t path, off_t length);
Expand Down
10 changes: 10 additions & 0 deletions system/lib/libc/musl/src/select/poll.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@

int poll(struct pollfd *fds, nfds_t n, int timeout)
{
#ifdef __EMSCRIPTEN__
// A zero timeout is an instantaneous probe: route it through a plain
// import that never suspends. Under JSPI, __syscall_poll is a suspending
// import and so may only be called from a stack entered through a
// promising export — a requirement a readiness probe must not carry
// (e.g. probes from event-loop callbacks).
if (timeout == 0) {
return __syscall_ret(__syscall_poll_nonblocking((intptr_t)fds, n));
Comment thread
guybedford marked this conversation as resolved.
}
#endif
#ifdef SYS_poll
return syscall_cp(SYS_poll, fds, n, timeout);
#else
Expand Down
4 changes: 4 additions & 0 deletions system/lib/standalone/standalone.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ weak int __syscall_poll(intptr_t fds, int nfds, int timeout) {
return -ENOSYS;
}

weak int __syscall_poll_nonblocking(intptr_t fds, int nfds) {
return -ENOSYS;
}

// open(), etc. - we just support the standard streams, with no
// corner case error checking; everything else is not permitted.
// TODO: full file support for WASI, or an option for it
Expand Down
6 changes: 6 additions & 0 deletions system/lib/wasmfs/syscalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1436,6 +1436,12 @@ int __syscall_poll(intptr_t fds_, int nfds, int timeout) {
return nonzero;
}

// libc routes zero-timeout poll() calls here (see musl's poll.c). WasmFS's
// __syscall_poll never blocks, so the zero-timeout probe is the same call.
int __syscall_poll_nonblocking(intptr_t fds, int nfds) {
return __syscall_poll(fds, nfds, 0);
}

int __syscall_fallocate(int fd, int mode, off_t offset, off_t len) {
assert(mode == 0); // TODO, but other modes were never supported in the old FS

Expand Down
8 changes: 5 additions & 3 deletions test/codesize/test_codesize_hello_dylink_all.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"a.out.js": 268089,
"a.out.nodebug.wasm": 587586,
"total": 855675,
"a.out.js": 268274,
"a.out.nodebug.wasm": 587640,
"total": 855914,
"sent": [
"IMG_Init",
"IMG_Load",
Expand Down Expand Up @@ -248,6 +248,7 @@
"__syscall_openat",
"__syscall_pipe2",
"__syscall_poll",
"__syscall_poll_nonblocking",
"__syscall_readlinkat",
"__syscall_recvfrom",
"__syscall_recvmsg",
Expand Down Expand Up @@ -1766,6 +1767,7 @@
"env.__syscall_openat",
"env.__syscall_pipe2",
"env.__syscall_poll",
"env.__syscall_poll_nonblocking",
"env.__syscall_readlinkat",
"env.__syscall_recvfrom",
"env.__syscall_recvmsg",
Expand Down
15 changes: 15 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -13161,6 +13161,21 @@ def test_emscripten_set_timeout(self):
def test_emscripten_set_timeout_loop(self):
self.do_runf('emscripten_set_timeout_loop.c', args=['-pthread', '-sPROXY_TO_PTHREAD'])

@parameterized({
'': ([],),
'asyncify': (['-sASYNCIFY'],),
'jspi': (['-sJSPI'],),
'pthreads': (['-pthread', '-sPROXY_TO_PTHREAD'],),
})
def test_poll_nonblocking(self, args):
if '-sJSPI' in args:
self.require_jspi()
if '-pthread' in args:
self.require_pthreads()
self.do_runf('test_poll_nonblocking.c',
'poll probe ok from main\npoll probe ok from callback\n',
cflags=args)

# Verify that we are able to successfully compile a script when the Windows 7
# and Python workaround env. vars are enabled.
# See https://bugs.python.org/issue34780
Expand Down
36 changes: 36 additions & 0 deletions test/test_poll_nonblocking.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <assert.h>
Comment thread
guybedford marked this conversation as resolved.
#include <emscripten/emscripten.h>
#include <emscripten/eventloop.h>
#include <poll.h>
#include <stdio.h>
#include <unistd.h>

// A zero-timeout poll() is a non-suspending probe, so it must work from any
// context. The property under test with -sJSPI: it stays callable from an
// event-loop callback, where a suspending call would trap (Suspending
// imports require a stack entered via a promising export).

static void probe(const char* where) {
struct pollfd pfd;
pfd.fd = STDOUT_FILENO;
pfd.events = POLLOUT;
pfd.revents = 0;
int n = poll(&pfd, 1, 0);
assert(n == 1);
assert(pfd.revents & POLLOUT);
printf("poll probe ok from %s\n", where);
}

static void on_timeout(void* user_data) {
probe("callback");
// A true shutdown even where the runtime is kept alive (e.g. worker
// threads under PROXY_TO_PTHREAD).
emscripten_force_exit(0);
}

int main(void) {
probe("main");
emscripten_set_timeout(on_timeout, 0, NULL);
emscripten_exit_with_live_runtime();
return 99;
}
Loading