diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-01 14:41:09 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-01 14:41:09 +0000 |
| commit | 8e0d97f7957e570c2e26d79e66efe0cc064cee4b (patch) | |
| tree | 9a1dee0f994171bb217de852d9d659408df384f4 /src | |
| parent | a1d867ecbcfc9e218408d0a11863a450243a01f9 (diff) | |
pool: log a connection census, not just the broadcast count
"broadcast to N miner(s)" counts the authorized sockets that were sent a
job. It says nothing about the rest of the connection table, so a pool
holding a pile of half-open sessions and one real miner reads the same as
a pool with N miners. #19 was opened on exactly that confusion: ~55
connections served per job while ~2 outside miners actually submitted
shares.
Log the breakdown every 60s instead: connected, subscribed (completed
mining.subscribe), authorized, active (authorized and seen within one
payout-rotate cycle), and never-auth-idle (past the grace period without
authorizing). The pool category is on by default, so this reaches a
production log without /debug, and the burst-collapse window is 3s, so a
60s cadence is never the duplicate this tree has swallowed before.
The census also reports how close the single select() loop is to running
out of room, which is not the same question on both platforms. On POSIX
FD_SETSIZE bounds the value of any descriptor handed to FD_SET, so the
highest fd we hold is what matters; on Windows an fd_set is a counted
array of SOCKET handles whose values are arbitrary, so the count is what
matters. FD_SETSIZE comes from the platform headers either way -- this
file must not define it, because redefining it after <sys/select.h> or
winsock2.h changes nothing at all.
Diffstat (limited to 'src')
| -rw-r--r-- | src/rpc.cpp | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/src/rpc.cpp b/src/rpc.cpp index 7a6b6a4..4ff09b9 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp | |||
| @@ -2474,6 +2474,7 @@ static const double VARDIFF_STALL_CUT = 4.0; // divide difficulty by thi | |||
| 2474 | struct Miner { | 2474 | struct Miner { |
| 2475 | btf_socket_t fd; | 2475 | btf_socket_t fd; |
| 2476 | bool authorised = false; | 2476 | bool authorised = false; |
| 2477 | bool subscribed = false; // completed mining.subscribe (#19 census) | ||
| 2477 | std::string address; | 2478 | std::string address; |
| 2478 | std::string worker; | 2479 | std::string worker; |
| 2479 | std::string readBuf; | 2480 | std::string readBuf; |
| @@ -2582,6 +2583,20 @@ static bool SendLine(btf_socket_t fd, const json& j) | |||
| 2582 | static const size_t MAX_STRATUM_LINE = 64 * 1024; | 2583 | static const size_t MAX_STRATUM_LINE = 64 * 1024; |
| 2583 | static const size_t MAX_POOL_MINERS = 1000; | 2584 | static const size_t MAX_POOL_MINERS = 1000; |
| 2584 | 2585 | ||
| 2586 | // #19 instrumentation: the "broadcast to N miner(s)" line counts connected | ||
| 2587 | // sockets, which says nothing about how many are actually mining. These split | ||
| 2588 | // the connection census into connected / subscribed / authorized / recently | ||
| 2589 | // active, so an operator can tell a real workforce from a pile of half-open | ||
| 2590 | // sessions at a glance. | ||
| 2591 | static const int64 POOL_CENSUS_SECS = 60; // how often to log the breakdown | ||
| 2592 | static const int64 POOL_ACTIVE_WINDOW = 300; // a miner is "active" if it | ||
| 2593 | // authorized and was seen within | ||
| 2594 | // this window (one payout-rotate | ||
| 2595 | // cycle) | ||
| 2596 | static const int64 POOL_UNAUTH_GRACE_SECS = 60; // a connection still not | ||
| 2597 | // authorized after this long is | ||
| 2598 | // idle, not merely starting up | ||
| 2599 | |||
| 2585 | // Non-blocking recv: pull whatever bytes are available now into buf, then | 2600 | // Non-blocking recv: pull whatever bytes are available now into buf, then |
| 2586 | // return one complete line if one is ready. Three outcomes: | 2601 | // return one complete line if one is ready. Three outcomes: |
| 2587 | // true -- line is filled; buf may still contain more lines | 2602 | // true -- line is filled; buf may still contain more lines |
| @@ -2690,6 +2705,65 @@ static void BroadcastJob(bool clean) | |||
| 2690 | } | 2705 | } |
| 2691 | 2706 | ||
| 2692 | // --------------------------------------------------------------------------- | 2707 | // --------------------------------------------------------------------------- |
| 2708 | // #19: connection census. "broadcast to N miner(s)" only counts authorized | ||
| 2709 | // sockets that got a job; it says nothing about the rest of the connection | ||
| 2710 | // table. This logs the full breakdown so an operator can tell a real workforce | ||
| 2711 | // apart from half-open / never-authorized / idle sessions, and flags when the | ||
| 2712 | // single select() loop is running out of room (issue #26). | ||
| 2713 | // | ||
| 2714 | // "Out of room" is not the same question on both platforms, and FD_SETSIZE | ||
| 2715 | // answers a different one on each. On POSIX it bounds the *value* of any | ||
| 2716 | // descriptor handed to FD_SET, so what matters is the highest fd we hold. On | ||
| 2717 | // Windows an fd_set is a counted array of SOCKET handles whose numeric values | ||
| 2718 | // are arbitrary, so what matters is how many we hold, not how large they are. | ||
| 2719 | // FD_SETSIZE comes from <sys/select.h> or winsock2.h; this file must not define | ||
| 2720 | // it, because redefining it after those headers changes nothing. | ||
| 2721 | // --------------------------------------------------------------------------- | ||
| 2722 | static void LogPoolConnectionCensus() | ||
| 2723 | { | ||
| 2724 | int connected = 0, subscribed = 0, authorized = 0, active = 0, neverAuthIdle = 0; | ||
| 2725 | int64 now = GetTime(); | ||
| 2726 | #ifndef _WIN32 | ||
| 2727 | int maxfd = 0; | ||
| 2728 | #endif | ||
| 2729 | for (Miner* m : gMiners) { | ||
| 2730 | connected++; | ||
| 2731 | #ifndef _WIN32 | ||
| 2732 | if ((int)m->fd > maxfd) maxfd = (int)m->fd; | ||
| 2733 | #endif | ||
| 2734 | if (m->subscribed) subscribed++; | ||
| 2735 | if (m->authorised) { | ||
| 2736 | authorized++; | ||
| 2737 | if (m->lastSeen > 0 && now - m->lastSeen <= POOL_ACTIVE_WINDOW) | ||
| 2738 | active++; | ||
| 2739 | } else if (now - m->connectTime >= POOL_UNAUTH_GRACE_SECS) { | ||
| 2740 | neverAuthIdle++; // past the grace period and still not authorized | ||
| 2741 | } | ||
| 2742 | } | ||
| 2743 | |||
| 2744 | #ifndef _WIN32 | ||
| 2745 | int headroom = (int)FD_SETSIZE - maxfd; | ||
| 2746 | LogPrint("pool", "[pool] census: connected=%d subscribed=%d authorized=%d" | ||
| 2747 | " active(last %llds)=%d never-auth-idle=%d" | ||
| 2748 | " (cap=%zu, maxfd=%d, FD_SETSIZE=%d)\n", | ||
| 2749 | connected, subscribed, authorized, (long long)POOL_ACTIVE_WINDOW, | ||
| 2750 | active, neverAuthIdle, MAX_POOL_MINERS, maxfd, (int)FD_SETSIZE); | ||
| 2751 | #else | ||
| 2752 | int headroom = (int)FD_SETSIZE - connected; | ||
| 2753 | LogPrint("pool", "[pool] census: connected=%d subscribed=%d authorized=%d" | ||
| 2754 | " active(last %llds)=%d never-auth-idle=%d" | ||
| 2755 | " (cap=%zu, FD_SETSIZE=%d sockets)\n", | ||
| 2756 | connected, subscribed, authorized, (long long)POOL_ACTIVE_WINDOW, | ||
| 2757 | active, neverAuthIdle, MAX_POOL_MINERS, (int)FD_SETSIZE); | ||
| 2758 | #endif | ||
| 2759 | if (headroom <= 16) | ||
| 2760 | LogPrint("pool", "[pool] WARNING: %d slots left before FD_SETSIZE=%d --" | ||
| 2761 | " the select() loop is about to walk into undefined behaviour" | ||
| 2762 | " (#26). Refuse new miners or rebuild with poll/epoll.\n", | ||
| 2763 | headroom, (int)FD_SETSIZE); | ||
| 2764 | } | ||
| 2765 | |||
| 2766 | // --------------------------------------------------------------------------- | ||
| 2693 | // Stratum message handler -- called for each complete line from a miner | 2767 | // Stratum message handler -- called for each complete line from a miner |
| 2694 | // Returns false if the miner should be disconnected immediately. | 2768 | // Returns false if the miner should be disconnected immediately. |
| 2695 | // --------------------------------------------------------------------------- | 2769 | // --------------------------------------------------------------------------- |
| @@ -2757,6 +2831,7 @@ static bool HandleLine(Miner* m, const std::string& rawLine, | |||
| 2757 | // ------------------------------------------------------------------ | 2831 | // ------------------------------------------------------------------ |
| 2758 | if (method == "mining.subscribe") { | 2832 | if (method == "mining.subscribe") { |
| 2759 | std::string sid = ToHex(&m->fd, 4); | 2833 | std::string sid = ToHex(&m->fd, 4); |
| 2834 | m->subscribed = true; | ||
| 2760 | LogPrint("worker", "[worker->pool] subscribe from fd=%d sid=%s\n", | 2835 | LogPrint("worker", "[worker->pool] subscribe from fd=%d sid=%s\n", |
| 2761 | (int)m->fd, sid.c_str()); | 2836 | (int)m->fd, sid.c_str()); |
| 2762 | reply(json::array({json::array({json::array({"mining.notify",sid})}),sid,4})); | 2837 | reply(json::array({json::array({json::array({"mining.notify",sid})}),sid,4})); |
| @@ -4593,6 +4668,7 @@ static void PoolEventLoop() | |||
| 4593 | int lastHeight = -1; | 4668 | int lastHeight = -1; |
| 4594 | int64 lastRefresh = GetTime(); | 4669 | int64 lastRefresh = GetTime(); |
| 4595 | int64 lastStatsRefresh = 0; | 4670 | int64 lastStatsRefresh = 0; |
| 4671 | int64 lastCensus = 0; // #19 connection census throttle | ||
| 4596 | int blocksFoundThisSession = 0; | 4672 | int blocksFoundThisSession = 0; |
| 4597 | int64 noJobSince = 0; // 0 = have a job | 4673 | int64 noJobSince = 0; // 0 = have a job |
| 4598 | bool warnedNoJob = false; | 4674 | bool warnedNoJob = false; |
| @@ -4810,6 +4886,14 @@ static void PoolEventLoop() | |||
| 4810 | RefreshStats(blocksFoundThisSession, roundShareTotal); | 4886 | RefreshStats(blocksFoundThisSession, roundShareTotal); |
| 4811 | lastStatsRefresh = GetTime(); | 4887 | lastStatsRefresh = GetTime(); |
| 4812 | } | 4888 | } |
| 4889 | |||
| 4890 | // 6. #19: log the connection census periodically. Cheap (one pass over | ||
| 4891 | // gMiners) and it is the single number an operator reads to judge pool | ||
| 4892 | // health, so it should not be an overstatement. | ||
| 4893 | if (GetTime() - lastCensus >= POOL_CENSUS_SECS) { | ||
| 4894 | LogPoolConnectionCensus(); | ||
| 4895 | lastCensus = GetTime(); | ||
| 4896 | } | ||
| 4813 | } | 4897 | } |
| 4814 | 4898 | ||
| 4815 | for (Miner* m : gMiners) { sock_close(m->fd); delete m; } | 4899 | for (Miner* m : gMiners) { sock_close(m->fd); delete m; } |