Skip to content

Commit f0849ad

Browse files
authored
Use poll() instead of select() in js_os_poll (#1097)
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: #1096
1 parent 0469489 commit f0849ad

File tree

1 file changed

+55
-39
lines changed

1 file changed

+55
-39
lines changed

quickjs-libc.c

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
#define chdir _chdir
6262
#else
6363
#include <sys/ioctl.h>
64+
#include <poll.h>
6465
#if !defined(__wasi__)
6566
#include <dlfcn.h>
6667
#include <termios.h>
@@ -2633,11 +2634,10 @@ static int js_os_poll(JSContext *ctx)
26332634
{
26342635
JSRuntime *rt = JS_GetRuntime(ctx);
26352636
JSThreadState *ts = js_get_thread_state(rt);
2636-
int ret, fd_max, min_delay;
2637-
fd_set rfds, wfds;
2637+
int r, w, ret, nfds, min_delay;
26382638
JSOSRWHandler *rh;
26392639
struct list_head *el;
2640-
struct timeval tv, *tvp;
2640+
struct pollfd *pfd, *pfds, pfds_local[64];
26412641

26422642
/* only check signals in the main thread */
26432643
if (!ts->recv_pipe &&
@@ -2663,67 +2663,83 @@ static int js_os_poll(JSContext *ctx)
26632663
if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->port_list))
26642664
return -1; /* no more events */
26652665

2666-
tvp = NULL;
2667-
if (min_delay >= 0) {
2668-
tv.tv_sec = min_delay / 1000;
2669-
tv.tv_usec = (min_delay % 1000) * 1000;
2670-
tvp = &tv;
2666+
nfds = 0;
2667+
list_for_each(el, &ts->os_rw_handlers) {
2668+
rh = list_entry(el, JSOSRWHandler, link);
2669+
nfds += (!JS_IsNull(rh->rw_func[0]) || !JS_IsNull(rh->rw_func[1]));
2670+
}
2671+
2672+
#ifdef USE_WORKER
2673+
list_for_each(el, &ts->port_list) {
2674+
JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link);
2675+
nfds += !JS_IsNull(port->on_message_func);
2676+
}
2677+
#endif // USE_WORKER
2678+
2679+
pfd = pfds = pfds_local;
2680+
if (nfds > (int)countof(pfds_local)) {
2681+
pfd = pfds = js_malloc(ctx, nfds * sizeof(*pfd));
2682+
if (!pfd)
2683+
return -1;
26712684
}
26722685

2673-
FD_ZERO(&rfds);
2674-
FD_ZERO(&wfds);
2675-
fd_max = -1;
26762686
list_for_each(el, &ts->os_rw_handlers) {
26772687
rh = list_entry(el, JSOSRWHandler, link);
2678-
fd_max = max_int(fd_max, rh->fd);
2679-
if (!JS_IsNull(rh->rw_func[0]))
2680-
FD_SET(rh->fd, &rfds);
2681-
if (!JS_IsNull(rh->rw_func[1]))
2682-
FD_SET(rh->fd, &wfds);
2688+
r = POLLIN * !JS_IsNull(rh->rw_func[0]);
2689+
w = POLLOUT * !JS_IsNull(rh->rw_func[1]);
2690+
if (r || w)
2691+
*pfd++ = (struct pollfd){rh->fd, r|w, 0};
26832692
}
26842693

26852694
#ifdef USE_WORKER
26862695
list_for_each(el, &ts->port_list) {
26872696
JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link);
26882697
if (!JS_IsNull(port->on_message_func)) {
26892698
JSWorkerMessagePipe *ps = port->recv_pipe;
2690-
fd_max = max_int(fd_max, ps->waker.read_fd);
2691-
FD_SET(ps->waker.read_fd, &rfds);
2699+
*pfd++ = (struct pollfd){ps->waker.read_fd, POLLIN, 0};
26922700
}
26932701
}
26942702
#endif // USE_WORKER
26952703

2696-
ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp);
2697-
if (ret > 0) {
2698-
list_for_each(el, &ts->os_rw_handlers) {
2699-
rh = list_entry(el, JSOSRWHandler, link);
2700-
if (!JS_IsNull(rh->rw_func[0]) &&
2701-
FD_ISSET(rh->fd, &rfds)) {
2702-
return call_handler(ctx, rh->rw_func[0]);
2704+
// FIXME(bnoordhuis) the loop below is quadratic in theory but
2705+
// linear-ish in practice because we bail out on the first hit,
2706+
// i.e., it's probably good enough for now
2707+
ret = 0;
2708+
nfds = poll(pfds, nfds, min_delay);
2709+
for (pfd = pfds; nfds-- > 0; pfd++) {
2710+
rh = find_rh(ts, pfd->fd);
2711+
if (rh) {
2712+
r = (POLLERR|POLLHUP|POLLNVAL|POLLIN) * !JS_IsNull(rh->rw_func[0]);
2713+
w = (POLLERR|POLLHUP|POLLNVAL|POLLOUT) * !JS_IsNull(rh->rw_func[1]);
2714+
if (r & pfd->revents) {
2715+
ret = call_handler(ctx, rh->rw_func[0]);
2716+
goto done;
27032717
/* must stop because the list may have been modified */
27042718
}
2705-
if (!JS_IsNull(rh->rw_func[1]) &&
2706-
FD_ISSET(rh->fd, &wfds)) {
2707-
return call_handler(ctx, rh->rw_func[1]);
2719+
if (w & pfd->revents) {
2720+
ret = call_handler(ctx, rh->rw_func[1]);
2721+
goto done;
27082722
/* must stop because the list may have been modified */
27092723
}
2710-
}
2724+
} else {
27112725
#ifdef USE_WORKER
2712-
list_for_each(el, &ts->port_list) {
2713-
JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link);
2714-
if (!JS_IsNull(port->on_message_func)) {
2715-
JSWorkerMessagePipe *ps = port->recv_pipe;
2716-
if (FD_ISSET(ps->waker.read_fd, &rfds)) {
2717-
if (handle_posted_message(rt, ctx, port))
2718-
goto done;
2726+
list_for_each(el, &ts->port_list) {
2727+
JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link);
2728+
if (!JS_IsNull(port->on_message_func)) {
2729+
JSWorkerMessagePipe *ps = port->recv_pipe;
2730+
if (pfd->fd == ps->waker.read_fd) {
2731+
if (handle_posted_message(rt, ctx, port))
2732+
goto done;
2733+
}
27192734
}
27202735
}
2721-
}
27222736
#endif // USE_WORKER
2737+
}
27232738
}
2724-
goto done; // silence unused label warning
27252739
done:
2726-
return 0;
2740+
if (pfds != pfds_local)
2741+
js_free(ctx, pfds);
2742+
return ret;
27272743
}
27282744
#endif // defined(_WIN32)
27292745

0 commit comments

Comments
 (0)