diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-08-13 17:29:10 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-08-19 22:48:27 +0000 |
| commit | 88d30ade28d6836e64d0157da57896bdb320e8a2 (patch) | |
| tree | 9fd87eca1ec870aeb4b09f4387df71486e67c20b | |
| parent | 5490d6682be52a3de5388086b01fc84bea195d61 (diff) | |
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)
| -rw-r--r-- | src/main.cpp | 12 | ||||
| -rw-r--r-- | src/main_gui.cpp | 2 | ||||
| -rw-r--r-- | src/net.cpp | 2 | ||||
| -rw-r--r-- | src/util.h | 35 |
4 files changed, 45 insertions, 6 deletions
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() | |||
| 164 | { | 164 | { |
| 165 | if (nMinerThreads > 0) | 165 | if (nMinerThreads > 0) |
| 166 | return nMinerThreads; | 166 | return nMinerThreads; |
| 167 | unsigned int nCores = std::thread::hardware_concurrency(); | 167 | unsigned int nCores = AvailableCoreCount(); |
| 168 | if (nCores <= 1) | 168 | if (nCores <= 1) |
| 169 | return 1; | 169 | return 1; |
| 170 | return (int)(nCores - 1); | 170 | return (int)(nCores - 1); |
| @@ -3827,7 +3827,11 @@ connected: | |||
| 3827 | // ------------------------------------------------------------------ | 3827 | // ------------------------------------------------------------------ |
| 3828 | if (!RandomXFastReady()) { | 3828 | if (!RandomXFastReady()) { |
| 3829 | SetParticipantMiningStatus("building RandomX dataset (~2GB, one-time)"); | 3829 | SetParticipantMiningStatus("building RandomX dataset (~2GB, one-time)"); |
| 3830 | unsigned int nThreads = std::thread::hardware_concurrency(); | 3830 | // Fill with the cores we are permitted to use, not the ones the host |
| 3831 | // happens to have. More threads than the cpuset allows does not build | ||
| 3832 | // the dataset any faster -- the CPU budget is the same either way -- | ||
| 3833 | // it just oversubscribes, and this runs alongside the pool node. | ||
| 3834 | unsigned int nThreads = AvailableCoreCount(); | ||
| 3831 | RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); | 3835 | RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); |
| 3832 | } | 3836 | } |
| 3833 | LogPrint("worker", "[worker] RandomX dataset ready (%s)\n", | 3837 | LogPrint("worker", "[worker] RandomX dataset ready (%s)\n", |
| @@ -3866,7 +3870,7 @@ connected: | |||
| 3866 | // publish jobs to the threads, submit the shares they find. | 3870 | // publish jobs to the threads, submit the shares they find. |
| 3867 | // ------------------------------------------------------------------ | 3871 | // ------------------------------------------------------------------ |
| 3868 | int nWorkers = nHashThreads > 0 ? nHashThreads | 3872 | int nWorkers = nHashThreads > 0 ? nHashThreads |
| 3869 | : (int)std::thread::hardware_concurrency(); | 3873 | : (int)AvailableCoreCount(); |
| 3870 | if (nHashThreads <= 0) { | 3874 | if (nHashThreads <= 0) { |
| 3871 | if (nWorkers < 2) nWorkers = 2; | 3875 | if (nWorkers < 2) nWorkers = 2; |
| 3872 | nWorkers -= 1; // leave one core for node + I/O threads | 3876 | nWorkers -= 1; // leave one core for node + I/O threads |
| @@ -4177,7 +4181,7 @@ bool BitcoinMiner(int nThreadId) | |||
| 4177 | // Build the fast (~2 GB dataset) RandomX mode once, up front, so we're | 4181 | // Build the fast (~2 GB dataset) RandomX mode once, up front, so we're |
| 4178 | // not stuck mining in the much slower light/cache-only mode forever. | 4182 | // not stuck mining in the much slower light/cache-only mode forever. |
| 4179 | if (!RandomXFastReady()) { | 4183 | if (!RandomXFastReady()) { |
| 4180 | unsigned int nThreads = std::thread::hardware_concurrency(); | 4184 | unsigned int nThreads = AvailableCoreCount(); |
| 4181 | RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); | 4185 | RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); |
| 4182 | } | 4186 | } |
| 4183 | 4187 | ||
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[]) | |||
| 202 | if (!strProcLimit.empty()) | 202 | if (!strProcLimit.empty()) |
| 203 | { | 203 | { |
| 204 | int n = atoi(strProcLimit.c_str()); | 204 | int n = atoi(strProcLimit.c_str()); |
| 205 | unsigned int nCores = std::thread::hardware_concurrency(); | 205 | unsigned int nCores = AvailableCoreCount(); |
| 206 | if (n < 0) | 206 | if (n < 0) |
| 207 | fprintf(stderr, "Ignoring /genproclimit=%s: not a thread count\n", | 207 | fprintf(stderr, "Ignoring /genproclimit=%s: not a thread count\n", |
| 208 | strProcLimit.c_str()); | 208 | 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() | |||
| 1910 | // question anyone asks. | 1910 | // question anyone asks. |
| 1911 | printf("Mining with %d thread(s) of %u core(s) -- about %d MB " | 1911 | printf("Mining with %d thread(s) of %u core(s) -- about %d MB " |
| 1912 | "(~2080 MB shared RandomX dataset + ~2 MB per thread)\n", | 1912 | "(~2080 MB shared RandomX dataset + ~2 MB per thread)\n", |
| 1913 | nStarted, std::thread::hardware_concurrency(), 2080 + 2 * nStarted); | 1913 | nStarted, AvailableCoreCount(), 2080 + 2 * nStarted); |
| 1914 | return nStarted; | 1914 | return nStarted; |
| 1915 | } | 1915 | } |
| 1916 | 1916 | ||
| @@ -8,6 +8,13 @@ | |||
| 8 | #include <cxxabi.h> | 8 | #include <cxxabi.h> |
| 9 | #endif | 9 | #endif |
| 10 | 10 | ||
| 11 | // For AvailableCoreCount() below: sched_getaffinity on Linux, and | ||
| 12 | // hardware_concurrency as the fallback everywhere else. | ||
| 13 | #include <thread> | ||
| 14 | #ifdef __linux__ | ||
| 15 | #include <sched.h> | ||
| 16 | #endif | ||
| 17 | |||
| 11 | #if defined(_MSC_VER) || defined(__BORLANDC__) | 18 | #if defined(_MSC_VER) || defined(__BORLANDC__) |
| 12 | typedef __int64 int64; | 19 | typedef __int64 int64; |
| 13 | typedef unsigned __int64 uint64; | 20 | typedef unsigned __int64 uint64; |
| @@ -144,6 +151,34 @@ int64 GetTime(); | |||
| 144 | int64 GetAdjustedTime(); | 151 | int64 GetAdjustedTime(); |
| 145 | void AddTimeData(unsigned int ip, int64 nTime); | 152 | void AddTimeData(unsigned int ip, int64 nTime); |
| 146 | 153 | ||
| 154 | // Cores this process is actually allowed to run on. | ||
| 155 | // | ||
| 156 | // std::thread::hardware_concurrency() reports the MACHINE's core count, which | ||
| 157 | // is not the same number inside a container. With --cpuset-cpus 0-5 on a | ||
| 158 | // 12-core host it still answers 12, so every caller that sized a thread pool | ||
| 159 | // from it oversubscribed by 2x and then wondered why the box was thrashing: | ||
| 160 | // the pool node started 11 miner threads on 6 permitted CPUs, and a burst | ||
| 161 | // miner asked for 2 cores filled the ~2 GB RandomX dataset with 12 threads. | ||
| 162 | // | ||
| 163 | // sched_getaffinity reports what the cpuset permits, which is the number every | ||
| 164 | // one of those callers meant. Falls back to hardware_concurrency where the | ||
| 165 | // call is unavailable, and never returns 0 -- callers divide by this. | ||
| 166 | inline unsigned int AvailableCoreCount() | ||
| 167 | { | ||
| 168 | #ifdef __linux__ | ||
| 169 | cpu_set_t cpuset; | ||
| 170 | CPU_ZERO(&cpuset); | ||
| 171 | if (sched_getaffinity(0, sizeof(cpuset), &cpuset) == 0) | ||
| 172 | { | ||
| 173 | int nAllowed = CPU_COUNT(&cpuset); | ||
| 174 | if (nAllowed > 0) | ||
| 175 | return (unsigned int)nAllowed; | ||
| 176 | } | ||
| 177 | #endif | ||
| 178 | unsigned int nCores = std::thread::hardware_concurrency(); | ||
| 179 | return nCores > 0 ? nCores : 1; | ||
| 180 | } | ||
| 181 | |||
| 147 | 182 | ||
| 148 | 183 | ||
| 149 | 184 | ||