From eaa0da24d24be57707077e8d0463a89815c4de4d Mon Sep 17 00:00:00 2001 From: Chris Speciale Date: Fri, 22 Aug 2025 10:03:54 -0400 Subject: [PATCH 1/2] [fix] Socket: Fix stale read/write indexes in `_hx_std_socket_poll_events` on error/empty sets Previously, `_hx_std_socket_poll_events` could exit early on `select()/poll()` errors without resetting the ready index arrays. This left stale values in `p->ridx/p->widx`, causing incorrect results when the socket set was empty or when sockets were removed. In Haxe this manifested as phantom entries (e.g. index "1" still marked ready after deregistration, or `poll([])` returning `[null]`). This change ensures `p->ridx[0]` and `p->widx[0]` are always initialized to `-1` at function entry, and remain consistent even if `select()/poll()` returns an error. With this fix, empty socket sets no longer yield spurious ready indexes, and poll() on an empty registry correctly returns `[]` without mutating the input array. --- src/hx/libs/std/Socket.cpp | 104 ++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/src/hx/libs/std/Socket.cpp b/src/hx/libs/std/Socket.cpp index fe77cdc46..258dd754d 100644 --- a/src/hx/libs/std/Socket.cpp +++ b/src/hx/libs/std/Socket.cpp @@ -1224,62 +1224,70 @@ Array _hx_std_socket_poll_prepare( Dynamic pdata, Array rsocks Update the read/write flags arrays that were created with [socket_poll_prepare]. **/ -void _hx_std_socket_poll_events( Dynamic pdata, double timeout ) +void _hx_std_socket_poll_events(Dynamic pdata, double timeout) { - polldata *p = val_poll(pdata); - - #ifdef NEKO_WINDOWS - memcpy(p->outr,p->fdr,FDSIZE(p->fdr->fd_count)); - memcpy(p->outw,p->fdw,FDSIZE(p->fdw->fd_count)); + polldata *p = val_poll(pdata); - struct timeval t; - struct timeval *tt = init_timeval(timeout,&t); + // Clear at entry so any early return leaves a clean -1 sentinel. + p->ridx[0] = -1; + p->widx[0] = -1; - hx::EnterGCFreeZone(); - if( select(0/* Ignored */, p->fdr->fd_count ? p->outr : 0, p->fdw->fd_count ?p->outw : 0,NULL,tt) == SOCKET_ERROR ) - { - hx::ExitGCFreeZone(); - return; - } - hx::ExitGCFreeZone(); +#ifdef NEKO_WINDOWS + memcpy(p->outr, p->fdr, FDSIZE(p->fdr->fd_count)); + memcpy(p->outw, p->fdw, FDSIZE(p->fdw->fd_count)); - int k = 0; - for(int i=0;ifdr->fd_count;i++) - if( FD_ISSET(p->fdr->fd_array[i],p->outr) ) - p->ridx[k++] = i; - p->ridx[k] = -1; + struct timeval t; + struct timeval *tt = init_timeval(timeout, &t); - k = 0; - for(int i=0;ifdw->fd_count;i++) - if( FD_ISSET(p->fdw->fd_array[i],p->outw) ) - p->widx[k++] = i; - p->widx[k] = -1; + hx::EnterGCFreeZone(); + if (select(0, p->fdr->fd_count ? p->outr : 0, + p->fdw->fd_count ? p->outw : 0, + NULL, tt) == SOCKET_ERROR) + { + hx::ExitGCFreeZone(); + // ridx/widx already set to -1 at entry + return; + } + hx::ExitGCFreeZone(); - #else + int k = 0; + for (int i=0; ifdr->fd_count; i++) + if (FD_ISSET(p->fdr->fd_array[i], p->outr)) + p->ridx[k++] = i; + p->ridx[k] = -1; - int tot = p->rcount + p->wcount; - hx::EnterGCFreeZone(); - POSIX_LABEL(poll_events_again); - if( poll(p->fds,tot,(int)(timeout * 1000)) < 0 ) - { - HANDLE_EINTR(poll_events_again); - hx::ExitGCFreeZone(); - return; - } - hx::ExitGCFreeZone(); + k = 0; + for (int i=0; ifdw->fd_count; i++) + if (FD_ISSET(p->fdw->fd_array[i], p->outw)) + p->widx[k++] = i; + p->widx[k] = -1; - int k = 0; - int i = 0; - for(i=0;ircount;i++) - if( p->fds[i].revents & (POLLIN|POLLHUP) ) - p->ridx[k++] = i; - p->ridx[k] = -1; - k = 0; - for(;ifds[i].revents & (POLLOUT|POLLHUP) ) - p->widx[k++] = i - p->rcount; - p->widx[k] = -1; - #endif +#else + int tot = p->rcount + p->wcount; + hx::EnterGCFreeZone(); +poll_events_again: + if (poll(p->fds, tot, (int)(timeout * 1000)) < 0) + { + if (errno == EINTR) goto poll_events_again; + hx::ExitGCFreeZone(); + // ridx/widx already set to -1 at entry + return; + } + hx::ExitGCFreeZone(); + + int k = 0; + int i = 0; + for (i=0; ircount; i++) + if (p->fds[i].revents & (POLLIN | POLLHUP)) + p->ridx[k++] = i; + p->ridx[k] = -1; + + k = 0; + for (; ifds[i].revents & (POLLOUT | POLLHUP)) + p->widx[k++] = i - p->rcount; + p->widx[k] = -1; +#endif } From 42fb3c6f9d985042b1eca8f0be3f601e8c565b2e Mon Sep 17 00:00:00 2001 From: Chris Speciale Date: Fri, 22 Aug 2025 10:53:38 -0400 Subject: [PATCH 2/2] [fix] Socket: Surface close/error reliably in _hx_std_socket_poll_events Windows: pass/handle exceptfds in select() and on SOCKET_ERROR surface current read fds for cleanup. POSIX: include POLLERR|POLLNVAL in readiness. Ensures peer FIN/RST/error is observed by default once-per-frame events(0) loops without scanning or keepalives as it should. --- src/hx/libs/std/Socket.cpp | 132 ++++++++++++++++++++++--------------- 1 file changed, 80 insertions(+), 52 deletions(-) diff --git a/src/hx/libs/std/Socket.cpp b/src/hx/libs/std/Socket.cpp index 258dd754d..eafce592b 100644 --- a/src/hx/libs/std/Socket.cpp +++ b/src/hx/libs/std/Socket.cpp @@ -1226,71 +1226,99 @@ Array _hx_std_socket_poll_prepare( Dynamic pdata, Array rsocks **/ void _hx_std_socket_poll_events(Dynamic pdata, double timeout) { - polldata *p = val_poll(pdata); + polldata *p = val_poll(pdata); - // Clear at entry so any early return leaves a clean -1 sentinel. - p->ridx[0] = -1; - p->widx[0] = -1; + p->ridx[0] = -1; + p->widx[0] = -1; #ifdef NEKO_WINDOWS - memcpy(p->outr, p->fdr, FDSIZE(p->fdr->fd_count)); - memcpy(p->outw, p->fdw, FDSIZE(p->fdw->fd_count)); + memcpy(p->outr, p->fdr, FDSIZE(p->fdr->fd_count)); + memcpy(p->outw, p->fdw, FDSIZE(p->fdw->fd_count)); - struct timeval t; - struct timeval *tt = init_timeval(timeout, &t); + fd_set oute; + FD_ZERO(&oute); + if (p->fdr->fd_count) + { + for (u_int i = 0; i < p->fdr->fd_count; ++i) + FD_SET(p->fdr->fd_array[i], &oute); + } - hx::EnterGCFreeZone(); - if (select(0, p->fdr->fd_count ? p->outr : 0, - p->fdw->fd_count ? p->outw : 0, - NULL, tt) == SOCKET_ERROR) - { - hx::ExitGCFreeZone(); - // ridx/widx already set to -1 at entry - return; - } - hx::ExitGCFreeZone(); + struct timeval t; + struct timeval *tt = init_timeval(timeout, &t); - int k = 0; - for (int i=0; ifdr->fd_count; i++) - if (FD_ISSET(p->fdr->fd_array[i], p->outr)) - p->ridx[k++] = i; - p->ridx[k] = -1; + hx::EnterGCFreeZone(); + int sel = select( + 0, + p->fdr->fd_count ? p->outr : 0, + p->fdw->fd_count ? p->outw : 0, + p->fdr->fd_count ? &oute : 0, + tt); + if (sel == SOCKET_ERROR) + { + hx::ExitGCFreeZone(); - k = 0; - for (int i=0; ifdw->fd_count; i++) - if (FD_ISSET(p->fdw->fd_array[i], p->outw)) - p->widx[k++] = i; - p->widx[k] = -1; + int k = 0; + for (u_int i = 0; i < p->fdr->fd_count; ++i) + p->ridx[k++] = i; + p->ridx[k] = -1; + + return; + } + hx::ExitGCFreeZone(); + + int k = 0; + for (u_int i = 0; i < p->fdr->fd_count; ++i) + { + SOCKET fd = p->fdr->fd_array[i]; + if (FD_ISSET(fd, p->outr) || FD_ISSET(fd, &oute)) + p->ridx[k++] = i; + } + p->ridx[k] = -1; + + k = 0; + for (u_int i = 0; i < p->fdw->fd_count; ++i) + { + if (FD_ISSET(p->fdw->fd_array[i], p->outw)) + p->widx[k++] = i; + } + p->widx[k] = -1; #else - int tot = p->rcount + p->wcount; - hx::EnterGCFreeZone(); + int tot = p->rcount + p->wcount; + + hx::EnterGCFreeZone(); poll_events_again: - if (poll(p->fds, tot, (int)(timeout * 1000)) < 0) - { - if (errno == EINTR) goto poll_events_again; - hx::ExitGCFreeZone(); - // ridx/widx already set to -1 at entry - return; - } - hx::ExitGCFreeZone(); - - int k = 0; - int i = 0; - for (i=0; ircount; i++) - if (p->fds[i].revents & (POLLIN | POLLHUP)) - p->ridx[k++] = i; - p->ridx[k] = -1; - - k = 0; - for (; ifds[i].revents & (POLLOUT | POLLHUP)) - p->widx[k++] = i - p->rcount; - p->widx[k] = -1; + if (poll(p->fds, tot, (int)(timeout * 1000)) < 0) + { + if (errno == EINTR) + goto poll_events_again; + hx::ExitGCFreeZone(); + + return; + } + hx::ExitGCFreeZone(); + + int k = 0; + int i = 0; + + for (i = 0; i < p->rcount; ++i) + { + if (p->fds[i].revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)) + p->ridx[k++] = i; + } + p->ridx[k] = -1; + + k = 0; + + for (; i < tot; ++i) + { + if (p->fds[i].revents & (POLLOUT | POLLHUP | POLLERR | POLLNVAL)) + p->widx[k++] = i - p->rcount; + } + p->widx[k] = -1; #endif } - /** socket_poll : 'socket array -> 'poll -> timeout:float -> 'socket array