From 7031ee39d7f6faf45d1cf858ab3be43668e80566 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Sun, 8 Jun 2025 16:34:44 +0200 Subject: [PATCH 1/2] Use poll() instead of select() in js_os_poll It's not safe to add file descriptors > FD_SETSIZE (usually 1,024) to a fd_set, it writes beyond the buffer. Switch to poll(2). Fixes: https://github.com/quickjs-ng/quickjs/issues/1096 --- quickjs-libc.c | 94 +++++++++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 39 deletions(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index 1c49ecb55..80dbfd229 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -66,6 +66,7 @@ #include #include #include +#include #include #endif @@ -2633,11 +2634,10 @@ static int js_os_poll(JSContext *ctx) { JSRuntime *rt = JS_GetRuntime(ctx); JSThreadState *ts = js_get_thread_state(rt); - int ret, fd_max, min_delay; - fd_set rfds, wfds; + int r, w, ret, nfds, min_delay; JSOSRWHandler *rh; struct list_head *el; - struct timeval tv, *tvp; + struct pollfd *pfd, *pfds, pfds_local[64]; /* only check signals in the main thread */ if (!ts->recv_pipe && @@ -2663,23 +2663,32 @@ static int js_os_poll(JSContext *ctx) if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->port_list)) return -1; /* no more events */ - tvp = NULL; - if (min_delay >= 0) { - tv.tv_sec = min_delay / 1000; - tv.tv_usec = (min_delay % 1000) * 1000; - tvp = &tv; + nfds = 0; + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + nfds += (!JS_IsNull(rh->rw_func[0]) || !JS_IsNull(rh->rw_func[1])); + } + +#ifdef USE_WORKER + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + nfds += !JS_IsNull(port->on_message_func); + } +#endif // USE_WORKER + + pfd = pfds = pfds_local; + if (nfds > (int)countof(pfds_local)) { + pfd = pfds = js_malloc(ctx, nfds * sizeof(*pfd)); + if (!pfd) + return -1; } - FD_ZERO(&rfds); - FD_ZERO(&wfds); - fd_max = -1; list_for_each(el, &ts->os_rw_handlers) { rh = list_entry(el, JSOSRWHandler, link); - fd_max = max_int(fd_max, rh->fd); - if (!JS_IsNull(rh->rw_func[0])) - FD_SET(rh->fd, &rfds); - if (!JS_IsNull(rh->rw_func[1])) - FD_SET(rh->fd, &wfds); + r = POLLIN * !JS_IsNull(rh->rw_func[0]); + w = POLLOUT * !JS_IsNull(rh->rw_func[1]); + if (r || w) + *pfd++ = (struct pollfd){rh->fd, r|w, 0}; } #ifdef USE_WORKER @@ -2687,43 +2696,50 @@ static int js_os_poll(JSContext *ctx) JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); if (!JS_IsNull(port->on_message_func)) { JSWorkerMessagePipe *ps = port->recv_pipe; - fd_max = max_int(fd_max, ps->waker.read_fd); - FD_SET(ps->waker.read_fd, &rfds); + *pfd++ = (struct pollfd){ps->waker.read_fd, POLLIN, 0}; } } #endif // USE_WORKER - ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp); - if (ret > 0) { - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - if (!JS_IsNull(rh->rw_func[0]) && - FD_ISSET(rh->fd, &rfds)) { - return call_handler(ctx, rh->rw_func[0]); + // FIXME(bnoordhuis) the loop below is quadratic in theory but + // linear-ish in practice because we bail out on the first hit, + // i.e., it's probably good enough for now + ret = 0; + nfds = poll(pfds, nfds, min_delay); + for (pfd = pfds; nfds-- > 0; pfd++) { + rh = find_rh(ts, pfd->fd); + if (rh) { + r = (POLLERR|POLLHUP|POLLNVAL|POLLIN) * !JS_IsNull(rh->rw_func[0]); + w = (POLLERR|POLLHUP|POLLNVAL|POLLOUT) * !JS_IsNull(rh->rw_func[1]); + if (r & pfd->revents) { + ret = call_handler(ctx, rh->rw_func[0]); + goto done; /* must stop because the list may have been modified */ } - if (!JS_IsNull(rh->rw_func[1]) && - FD_ISSET(rh->fd, &wfds)) { - return call_handler(ctx, rh->rw_func[1]); + if (w & pfd->revents) { + ret = call_handler(ctx, rh->rw_func[1]); + goto done; /* must stop because the list may have been modified */ } - } + } else { #ifdef USE_WORKER - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - if (!JS_IsNull(port->on_message_func)) { - JSWorkerMessagePipe *ps = port->recv_pipe; - if (FD_ISSET(ps->waker.read_fd, &rfds)) { - if (handle_posted_message(rt, ctx, port)) - goto done; + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (!JS_IsNull(port->on_message_func)) { + JSWorkerMessagePipe *ps = port->recv_pipe; + if (pfd->fd == ps->waker.read_fd) { + if (handle_posted_message(rt, ctx, port)) + goto done; + } } } - } #endif // USE_WORKER + } } - goto done; // silence unused label warning done: - return 0; + if (pfds != pfds_local) + js_free(ctx, pfds); + return ret; } #endif // defined(_WIN32) From 98d8f38180eb9240dec6bd161d0cfd0657b9230b Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Sun, 8 Jun 2025 18:25:39 +0200 Subject: [PATCH 2/2] squash! fix wasi maybe? --- quickjs-libc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index 80dbfd229..281e8fbb6 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -61,12 +61,12 @@ #define chdir _chdir #else #include +#include #if !defined(__wasi__) #include #include #include #include -#include #include #endif