Skip to content

Commit 7539a75

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 1bb9651 commit 7539a75

File tree

8 files changed

+139
-3
lines changed

8 files changed

+139
-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: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -600,15 +600,49 @@ 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: ['_newselect_js',
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 __newselect_js({{{ to64('0') }}},
620+
{{{ to64('0') }}},
621+
nfds,
622+
{{{ to64('readfds') }}},
623+
{{{ to64('writefds') }}},
624+
{{{ to64('exceptfds') }}},
625+
{{{ splitI64('timeoutInMillis') }}});
626+
},
627+
_newselect_js__i53abi: true,
628+
_newselect_js__proxy: 'none',
629+
_newselect_js__deps: ['$parseSelectFDSet',
630+
#if PTHREADS
631+
'_emscripten_proxy_newselect_finish',
632+
#endif
633+
],
634+
_newselect_js: (ctx, arg, nfds, readfds, writefds, exceptfds, timeoutInMillis) => {
605635
// readfds are supported,
606636
// writefds checks socket open status
607637
// exceptfds are supported, although on web, such exceptional conditions never arise in web sockets
608638
// 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
639+
// timeout is supported, although on SOCKFS these are ignored and always treated as 0 - fully async
640+
// and PIPEFS supports timeout only when the select is called from a worker.
610641
#if ASSERTIONS
611642
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
643+
#if PTHREADS
644+
assert(!ENVIRONMENT_IS_PTHREAD, '_newselect_js must be called in the main thread');
645+
#endif
612646
#endif
613647

614648
var fdSet = parseSelectFDSet(readfds, writefds, exceptfds);
@@ -618,6 +652,36 @@ var SyscallsLibrary = {
618652

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

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

631695
if (stream.stream_ops.poll) {
632-
flags = stream.stream_ops.poll(stream, timeoutInMillis);
696+
flags = (() => {
697+
#if PTHREADS
698+
if (makeNotifyCallback != null) {
699+
return stream.stream_ops.poll(stream, timeoutInMillis, timeoutInMillis != 0 ? makeNotifyCallback(fd) : null);
700+
}
701+
#endif
702+
return stream.stream_ops.poll(stream, timeoutInMillis);
703+
})();
633704
} else {
634705
#if ASSERTIONS
635706
if (timeoutInMillis != 0) warnOnce('non-zero select() timeout not supported: ' + timeoutInMillis)
@@ -639,6 +710,14 @@ var SyscallsLibrary = {
639710
fdSet.setFlags(fd, flags);
640711
}
641712

713+
#if PTHREADS
714+
if (makeNotifyCallback != null) {
715+
if ((fdSet.getTotal() > 0) || (timeoutInMillis == 0) ) {
716+
makeNotifyCallback(-1)(0);
717+
}
718+
return 0;
719+
}
720+
#endif
642721

643722
fdSet.commit();
644723

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)