Skip to content

Commit f8567ea

Browse files
committed
select: enable to handle timeout
This commit enables the select syscall to handle timeout with multiple event sources when it is called from a worker. When a thread worker calls __syscall_newselect, it blocks using emscripten_proxy_sync_with_ctx. When __syscall_newselect is called with zero timeout, it is unblocked immediately by calling emscripten_proxy_finish before returning. When it is called with non-zero timeout, emscripten_proxy_finish is invoked either by the underlying stream implementation (where an event occurs) or by a setTimeout callback when the tiemout expires. In proxying.c, several wrapper functions for proxying-related APIs are added to allow the JS implementation of newselect to use them. Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
1 parent c23574b commit f8567ea

File tree

8 files changed

+135
-3
lines changed

8 files changed

+135
-3
lines changed

src/lib/libsigs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ sigs = {
381381
_mmap_js__sig: 'ipiiijpp',
382382
_msync_js__sig: 'ippiiij',
383383
_munmap_js__sig: 'ippiiij',
384+
_newselect_js__sig: 'ippipppj',
384385
_setitimer_js__sig: 'iid',
385386
_timegm_js__sig: 'jp',
386387
_tzset_js__sig: 'vpppp',

src/lib/libsyscall.js

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -600,13 +600,43 @@ var SyscallsLibrary = {
600600
return 0;
601601
},
602602
__syscall__newselect__i53abi: true,
603-
__syscall__newselect__deps: ['$parseSelectFDSet'],
603+
__syscall__newselect__proxy: 'none',
604+
__syscall__newselect__deps: ['$doNewselect',
605+
#if PTHREADS
606+
'_emscripten_proxy_newselect',
607+
#endif
608+
],
604609
__syscall__newselect: (nfds, readfds, writefds, exceptfds, timeoutInMillis) => {
610+
#if PTHREADS
611+
if (ENVIRONMENT_IS_PTHREAD) {
612+
return __emscripten_proxy_newselect(nfds,
613+
{{{ to64('readfds') }}},
614+
{{{ to64('writefds') }}},
615+
{{{ to64('exceptfds') }}},
616+
{{{ splitI64('timeoutInMillis') }}});
617+
}
618+
#endif
619+
return doNewselect(null, null, nfds, readfds, writefds, exceptfds, timeoutInMillis);
620+
},
621+
_newselect_js__i53abi: true,
622+
_newselect_js__proxy: 'none',
623+
_newselect_js__deps: ['$doNewselect'],
624+
_newselect_js: (ctx, arg, nfds, readfds, writefds, exceptfds, timeoutInMillis) => {
625+
return doNewselect(ctx, arg, nfds, readfds, writefds, exceptfds, timeoutInMillis);
626+
},
627+
$doNewselect__proxy: 'none',
628+
$doNewselect__deps: ['$parseSelectFDSet',
629+
#if PTHREADS
630+
'_emscripten_proxy_newselect_finish',
631+
#endif
632+
],
633+
$doNewselect: (ctx, arg, nfds, readfds, writefds, exceptfds, timeoutInMillis) => {
605634
// readfds are supported,
606635
// writefds checks socket open status
607636
// exceptfds are supported, although on web, such exceptional conditions never arise in web sockets
608637
// and so the exceptfds list will always return empty.
609-
// timeout is supported, although on SOCKFS and PIPEFS these are ignored and always treated as 0 - fully async
638+
// timeout is supported, although on SOCKFS these are ignored and always treated as 0 - fully async
639+
// and PIPEFS supports timeout only when the select is called from a worker.
610640
#if ASSERTIONS
611641
assert(nfds <= 64, 'nfds must be less than or equal to 64'); // fd sets have 64 bits // TODO: this could be 1024 based on current musl headers
612642
#endif
@@ -618,6 +648,36 @@ var SyscallsLibrary = {
618648

619649
var check = (fd, low, high, val) => fd < 32 ? (low & val) : (high & val);
620650

651+
#if PTHREADS
652+
var makeNotifyCallback = null;
653+
if (ctx) {
654+
// Enable event handlers only when the select call is proxied from a worker.
655+
var cleanupFuncs = [];
656+
var notifyDone = false;
657+
makeNotifyCallback = (fd) => {
658+
var cb = (flags) => {
659+
if (notifyDone) {
660+
return;
661+
}
662+
if (fd >= 0) {
663+
fdSet.setFlags(fd, flags);
664+
}
665+
notifyDone = true;
666+
cleanupFuncs.forEach(cb => cb());
667+
fdSet.commit();
668+
__emscripten_proxy_newselect_finish({{{ to64('ctx') }}}, {{{ to64('arg') }}}, fdSet.getTotal());
669+
}
670+
cb.registerCleanupFunc = (f) => {
671+
if (f != null) cleanupFuncs.push(f);
672+
}
673+
return cb;
674+
}
675+
if (timeoutInMillis > 0) {
676+
setTimeout(() => makeNotifyCallback(-1)(0), timeoutInMillis);
677+
}
678+
}
679+
#endif
680+
621681
for (var fd = 0; fd < nfds; fd++) {
622682
var mask = 1 << (fd % 32);
623683
if (!(check(fd, allLow, allHigh, mask))) {
@@ -629,7 +689,14 @@ var SyscallsLibrary = {
629689
var flags = SYSCALLS.DEFAULT_POLLMASK;
630690

631691
if (stream.stream_ops.poll) {
632-
flags = stream.stream_ops.poll(stream, timeoutInMillis);
692+
flags = (() => {
693+
#if PTHREADS
694+
if (makeNotifyCallback != null) {
695+
return stream.stream_ops.poll(stream, timeoutInMillis, timeoutInMillis != 0 ? makeNotifyCallback(fd) : null);
696+
}
697+
#endif
698+
return stream.stream_ops.poll(stream, timeoutInMillis);
699+
})();
633700
} else {
634701
#if ASSERTIONS
635702
if (timeoutInMillis != 0) warnOnce('non-zero select() timeout not supported: ' + timeoutInMillis)
@@ -639,6 +706,14 @@ var SyscallsLibrary = {
639706
fdSet.setFlags(fd, flags);
640707
}
641708

709+
#if PTHREADS
710+
if (makeNotifyCallback != null) {
711+
if ((fdSet.getTotal() > 0) || (timeoutInMillis == 0) ) {
712+
makeNotifyCallback(-1)(0);
713+
}
714+
return 0;
715+
}
716+
#endif
642717

643718
fdSet.commit();
644719

system/lib/libc/emscripten_internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ EmscriptenDeviceOrientationEvent* _emscripten_get_last_deviceorientation_event()
151151
EmscriptenDeviceMotionEvent* _emscripten_get_last_devicemotion_event();
152152
EmscriptenMouseEvent* _emscripten_get_last_mouse_event();
153153

154+
int _newselect_js(void* ctx, void* arg, int n, void *rfds, void *wfds, void *efds, int64_t timeout);
155+
154156
#ifdef __cplusplus
155157
}
156158
#endif

system/lib/libc/proxying_select.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
#include <assert.h>
9+
#include <emscripten/proxying.h>
10+
#include <emscripten/threading.h>
11+
12+
#include "emscripten_internal.h"
13+
14+
typedef struct proxied_select_t {
15+
int n;
16+
void *rfds;
17+
void *wfds;
18+
void *efds;
19+
int64_t timeout;
20+
int result;
21+
} proxied_select_t;
22+
23+
static void call_newselect(em_proxying_ctx* ctx, void* arg) {
24+
proxied_select_t* t = arg;
25+
_newselect_js(ctx, arg, t->n, t->rfds, t->wfds, t->efds, t->timeout);
26+
}
27+
28+
void _emscripten_proxy_newselect_finish(em_proxying_ctx* ctx, void* arg, int ret) {
29+
proxied_select_t* t = arg;
30+
t->result = ret;
31+
emscripten_proxy_finish(ctx);
32+
}
33+
34+
int _emscripten_proxy_newselect(int n, void *rfds, void *wfds, void *efds, int64_t timeout) {
35+
em_proxying_queue* q = emscripten_proxy_get_system_queue();
36+
pthread_t target = emscripten_main_runtime_thread_id();
37+
proxied_select_t t = {.n = n, .rfds = rfds, .wfds = wfds, .efds = efds, .timeout = timeout};
38+
if (!emscripten_proxy_sync_with_ctx(q, target, call_newselect, &t)) {
39+
assert(false && "emscripten_proxy_sync failed");
40+
return -1;
41+
}
42+
return t.result;
43+
}

system/lib/standalone/standalone.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ weak int _munmap_js(
6666
return -ENOSYS;
6767
}
6868

69+
weak int _newselect_js(void* ctx, void* arg, int n, void *rfds, void *wfds, void *efds, int64_t timeout) {
70+
return -ENOSYS;
71+
}
72+
6973
// open(), etc. - we just support the standard streams, with no
7074
// corner case error checking; everything else is not permitted.
7175
// TODO: full file support for WASI, or an option for it

system/lib/wasmfs/syscalls.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,4 +1848,8 @@ int __syscall__newselect(int nfds,
18481848
return count;
18491849
}
18501850

1851+
int _newselect_js(void* ctx, void* arg, int n, void *rfds, void *wfds, void *efds, int64_t timeout) {
1852+
return -ENOSYS;
1853+
}
1854+
18511855
} // extern "C"

tools/emscripten.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,8 @@ def create_pointer_conversion_wrappers(metadata):
12021202
'_emscripten_dlsync_self_async': '_p',
12031203
'_emscripten_proxy_dlsync_async': '_pp',
12041204
'_emscripten_wasm_worker_initialize': '_p_',
1205+
'_emscripten_proxy_newselect': '__ppp_',
1206+
'_emscripten_proxy_newselect_finish': '_pp_',
12051207
'_wasmfs_rename': '_pp',
12061208
'_wasmfs_readlink': '_pp',
12071209
'_wasmfs_truncate': '_p_',

tools/system_libs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,7 @@ def get_files(self):
13671367
'sigtimedwait.c',
13681368
'wasi-helpers.c',
13691369
'system.c',
1370+
'proxying_select.c',
13701371
])
13711372

13721373
if settings.RELOCATABLE or settings.MAIN_MODULE:

0 commit comments

Comments
 (0)