Skip to content

Hub event loop hits FD_SETSIZE=1024 Linux limit (select -> poll port) #310

Description

@Aybook

Problem

core/server.lua event loop calls luasocket.select which wraps the OS select() syscall via the bundled luasocket/src/select.c. On Linux, glibc's fd_set is hardcoded to 1024 bits in <sys/select.h> (__FD_SETSIZE). The hub silently caps at ~1000 concurrent connected sockets — connection #1024+ fails with EINVAL and the operator sees timeouts.

ulimit -n N does not help: it raises the per-process FD limit but the fd_set bitmask size is fixed at compile-time inside glibc.

Originally reported upstream as luadch/luadch#1 (2015, still open). Upstream maintainer's plan in 2020: "Eventually I will replace select by poll on unix". Never happened.

Why this isn't a one-line CMake bump

-DFD_SETSIZE=8192 only works on Windows. On Linux, glibc's fd_set typedef has a fixed buffer of 128 longs (1024 bits); calling FD_SET(2048, &set) writes past the struct. Buffer overflow, undefined behavior. The fd-bound guard in luasocket/src/select.c:140 is exactly to prevent that overflow.

Only path on Linux: replace select() with poll() (no bitmask, takes an struct pollfd[] array).

Code surface

File Function Role LoC change
luasocket/src/select.c global_select() Lua-facing luasocket.select — main hub event loop ~80-100
luasocket/src/usocket.c socket_waitfd() Per-socket wait inside connect/send/receive. if (*ps >= FD_SETSIZE) return EINVAL → every operation on fd ≥ 1024 fails, not just the event loop ~25-30
luasocket/src/wsocket.c socket_waitfd() + socket_select() Windows variants, WSAPoll instead of poll ~50-60
luasocket/src/socket.h prototypes +5
luasocket/src/luasocket.c register luasocket.poll +5
core/server.lua luasocket.selectluasocket.poll +3
luasocket/CMakeLists.txt -DLUASOCKET_USE_POLL build flag +10
tests/smoke/ new smoke opening >1024 sockets +60-80

Total: ~200 LoC C + ~10 Lua + ~80 LoC test = ~290 LoC.

Tiers

  1. Linux poll() backend, opt-in via CMake (-DLUASOCKET_USE_POLL=ON, default OFF). 2-3 days.
  2. Windows WSAPoll backend, same flag. WSAPoll has a historic non-blocking connect() reporting bug — workaround via getsockopt(SO_ERROR) after POLLWRNORM. 2-3 days.
  3. Default ON + BUILDING.md update. 1 day.

Realistic total: 6-8 focused workdays, plus 1-2 days buffer for WSAPoll quirks.

Behaviour-difference risks

  • poll() distinguishes POLLHUP / POLLERR / POLLIN; select() doesn't. Current code does not inspect why a socket is readable — just calls receive() and lets that report the error. Map POLLHUP|POLLERR|POLLNVAL → readable in the wrapper to preserve behaviour 1:1.
  • Smoke test needs a synthetic >1024-socket load (pairs via socketpair() or many localhost connects). Today's smoke does not exercise this regime.

Out of scope

  • epoll() on Linux for O(1) event delivery (vs poll's O(N) array scan). Worth doing if hub regularly serves 5000+ concurrent users; not before.
  • libevent / libev / cqueues replacement of luasocket. Too invasive, this issue is the minimal fix.
  • Replacing luadch with uhub for mass-hub use cases (upstream blasti's 2015 suggestion). Out of project scope.

Upstream maintenance

luasocket releases are slow (3.0 in 2013, 3.1 in 2022). Re-porting the poll diff at each upstream bump: ~1-2h.

When this is worth doing

Today, the hubs we know of (Sopor / similar) run <500 concurrent users — the limit is theoretical. This issue is for when:

  • a luadch operator wants to host >800 concurrent users, OR
  • we want to advertise luadch as suitable for public mass-hubs.

Until then, later label.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Fields

No fields configured for Feature.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions