diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-01 14:41:33 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-01 14:41:33 +0000 |
| commit | 12752bc0f9441ef9f929ee652eeb5d92b9dda4a5 (patch) | |
| tree | 8d37b2cd119078c9e1e84327211b213a6b4b0af6 | |
| parent | 8e0d97f7957e570c2e26d79e66efe0cc064cee4b (diff) | |
pool: refuse a miner the select() loop cannot represent
The operator event loop still builds an fd_set by hand (#26), and FD_SET
on a descriptor the set cannot represent is undefined -- it writes past
the bitmap. MAX_POOL_MINERS caps the miner list at 1000, but that is a
count, and a count does not bound descriptor values: a Miner is removed
only on clean disconnect, so half-open sessions hold their fds while the
OS keeps issuing higher ones.
Refuse the connection instead, and refuse on the limit that actually
applies. On POSIX that is the descriptor value against FD_SETSIZE. On
Windows an fd_set is a counted array of SOCKET handles whose values are
arbitrary -- often large from the first accept -- so comparing a handle
against FD_SETSIZE there would reject nearly every miner; the bound is
how many sockets are held.
This is a behaviour change: a pool at the limit now turns miners away
with a log line rather than corrupting memory quietly. The real fix is
poll/epoll, which #26 tracks.
| -rw-r--r-- | src/rpc.cpp | 25 |
1 files changed, 25 insertions, 0 deletions
diff --git a/src/rpc.cpp b/src/rpc.cpp index 4ff09b9..a4b53ca 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp | |||
| @@ -4697,6 +4697,31 @@ static void PoolEventLoop() | |||
| 4697 | sock_close(fd); | 4697 | sock_close(fd); |
| 4698 | continue; | 4698 | continue; |
| 4699 | } | 4699 | } |
| 4700 | // #26: select() + FD_SET goes undefined once we exceed what an | ||
| 4701 | // fd_set can carry, and MAX_POOL_MINERS (1000) alone does not | ||
| 4702 | // prevent it -- a Miner is only removed on clean disconnect, so | ||
| 4703 | // half-open zombies accumulate and the OS can hand out fds past | ||
| 4704 | // the limit long before the count reaches 1000. Refuse rather | ||
| 4705 | // than corrupt the fd_set. Which limit applies is platform | ||
| 4706 | // specific: POSIX bounds the descriptor value, Windows the | ||
| 4707 | // number of SOCKET handles an fd_set can hold. | ||
| 4708 | #ifndef _WIN32 | ||
| 4709 | if ((int)fd >= (int)FD_SETSIZE) { | ||
| 4710 | LogPrint("worker", "[pool] refusing miner fd=%d -- at/over" | ||
| 4711 | " FD_SETSIZE=%d, select() would be undefined (#26)\n", | ||
| 4712 | (int)fd, (int)FD_SETSIZE); | ||
| 4713 | sock_close(fd); | ||
| 4714 | continue; | ||
| 4715 | } | ||
| 4716 | #else | ||
| 4717 | if (gMiners.size() >= (size_t)FD_SETSIZE) { | ||
| 4718 | LogPrint("worker", "[pool] refusing miner -- %zu sockets held," | ||
| 4719 | " an fd_set carries at most FD_SETSIZE=%d (#26)\n", | ||
| 4720 | gMiners.size(), (int)FD_SETSIZE); | ||
| 4721 | sock_close(fd); | ||
| 4722 | continue; | ||
| 4723 | } | ||
| 4724 | #endif | ||
| 4700 | gMiners.push_back(new Miner(fd)); | 4725 | gMiners.push_back(new Miner(fd)); |
| 4701 | LogPrint("worker", "[pool] miner fd=%d entered event loop" | 4726 | LogPrint("worker", "[pool] miner fd=%d entered event loop" |
| 4702 | " -- awaiting subscribe/authorize\n", (int)fd); | 4727 | " -- awaiting subscribe/authorize\n", (int)fd); |