From 88d30ade28d6836e64d0157da57896bdb320e8a2 Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Thu, 13 Aug 2026 17:29:10 +0000 Subject: size thread pools by the cores we are allowed, not the ones the host has std::thread::hardware_concurrency() reports the MACHINE's core count. Inside a container that is the wrong number: with --cpuset-cpus 0-5 on a 12-core host it still answers 12, and every caller that sized a pool from it oversubscribed 2x. Measured on the pool box: - the pool node logged "Mining with 11 thread(s) of 12 core(s)" while confined to 6 CPUs - a burst miner launched with /hashthreads=2 filled the ~2 GB RandomX dataset with 12 threads, alongside the pool node in the same 8 GB container AvailableCoreCount() asks sched_getaffinity what the cpuset actually permits, which is the number all of these meant. It cannot make the dataset build slower: the CPU budget is identical either way, so fewer threads on the same cores is the same work without the thrash. Falls back to hardware_concurrency where the call is unavailable, and never returns 0 because callers subtract from it. Not the cause of the burst miner dying -- that was a corrupt Berkeley DB environment in the worker datadir -- but it is why the failure landed on the dataset build, and it is wrong on its own terms. (cherry picked from commit b3bee16f3eef8982bff1174bd9d0a9c21473732c) --- src/main.cpp | 12 ++++++++---- src/main_gui.cpp | 2 +- src/net.cpp | 2 +- src/util.h | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 11eb11d..62068d2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -164,7 +164,7 @@ int MinerThreadCount() { if (nMinerThreads > 0) return nMinerThreads; - unsigned int nCores = std::thread::hardware_concurrency(); + unsigned int nCores = AvailableCoreCount(); if (nCores <= 1) return 1; return (int)(nCores - 1); @@ -3827,7 +3827,11 @@ connected: // ------------------------------------------------------------------ if (!RandomXFastReady()) { SetParticipantMiningStatus("building RandomX dataset (~2GB, one-time)"); - unsigned int nThreads = std::thread::hardware_concurrency(); + // Fill with the cores we are permitted to use, not the ones the host + // happens to have. More threads than the cpuset allows does not build + // the dataset any faster -- the CPU budget is the same either way -- + // it just oversubscribes, and this runs alongside the pool node. + unsigned int nThreads = AvailableCoreCount(); RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); } LogPrint("worker", "[worker] RandomX dataset ready (%s)\n", @@ -3866,7 +3870,7 @@ connected: // publish jobs to the threads, submit the shares they find. // ------------------------------------------------------------------ int nWorkers = nHashThreads > 0 ? nHashThreads - : (int)std::thread::hardware_concurrency(); + : (int)AvailableCoreCount(); if (nHashThreads <= 0) { if (nWorkers < 2) nWorkers = 2; nWorkers -= 1; // leave one core for node + I/O threads @@ -4177,7 +4181,7 @@ bool BitcoinMiner(int nThreadId) // Build the fast (~2 GB dataset) RandomX mode once, up front, so we're // not stuck mining in the much slower light/cache-only mode forever. if (!RandomXFastReady()) { - unsigned int nThreads = std::thread::hardware_concurrency(); + unsigned int nThreads = AvailableCoreCount(); RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); } diff --git a/src/main_gui.cpp b/src/main_gui.cpp index c20ae46..bee4b95 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -202,7 +202,7 @@ static void ParseStartupArguments(int argc, char* argv[]) if (!strProcLimit.empty()) { int n = atoi(strProcLimit.c_str()); - unsigned int nCores = std::thread::hardware_concurrency(); + unsigned int nCores = AvailableCoreCount(); if (n < 0) fprintf(stderr, "Ignoring /genproclimit=%s: not a thread count\n", strProcLimit.c_str()); diff --git a/src/net.cpp b/src/net.cpp index 77f9acf..72a7fda 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1910,7 +1910,7 @@ int StartMinerThreads() // question anyone asks. printf("Mining with %d thread(s) of %u core(s) -- about %d MB " "(~2080 MB shared RandomX dataset + ~2 MB per thread)\n", - nStarted, std::thread::hardware_concurrency(), 2080 + 2 * nStarted); + nStarted, AvailableCoreCount(), 2080 + 2 * nStarted); return nStarted; } diff --git a/src/util.h b/src/util.h index d95d3ae..d72518b 100644 --- a/src/util.h +++ b/src/util.h @@ -8,6 +8,13 @@ #include #endif +// For AvailableCoreCount() below: sched_getaffinity on Linux, and +// hardware_concurrency as the fallback everywhere else. +#include +#ifdef __linux__ +#include +#endif + #if defined(_MSC_VER) || defined(__BORLANDC__) typedef __int64 int64; typedef unsigned __int64 uint64; @@ -144,6 +151,34 @@ int64 GetTime(); int64 GetAdjustedTime(); void AddTimeData(unsigned int ip, int64 nTime); +// Cores this process is actually allowed to run on. +// +// std::thread::hardware_concurrency() reports the MACHINE's core count, which +// is not the same number inside a container. With --cpuset-cpus 0-5 on a +// 12-core host it still answers 12, so every caller that sized a thread pool +// from it oversubscribed by 2x and then wondered why the box was thrashing: +// the pool node started 11 miner threads on 6 permitted CPUs, and a burst +// miner asked for 2 cores filled the ~2 GB RandomX dataset with 12 threads. +// +// sched_getaffinity reports what the cpuset permits, which is the number every +// one of those callers meant. Falls back to hardware_concurrency where the +// call is unavailable, and never returns 0 -- callers divide by this. +inline unsigned int AvailableCoreCount() +{ +#ifdef __linux__ + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + if (sched_getaffinity(0, sizeof(cpuset), &cpuset) == 0) + { + int nAllowed = CPU_COUNT(&cpuset); + if (nAllowed > 0) + return (unsigned int)nAllowed; + } +#endif + unsigned int nCores = std::thread::hardware_concurrency(); + return nCores > 0 ? nCores : 1; +} + -- cgit v1.2.3