diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-02 07:09:45 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-02 07:09:45 +0000 |
| commit | 0b566b4869b9e86c90162befd87f9882b6dd143c (patch) | |
| tree | 978c6a3d0ecf729fa72ab38468280d195843996a /src/main.cpp | |
| parent | a44c9402c20493f0964a1296dd38d313216fbd4d (diff) | |
net: a refused thread spawn drops one peer, not the node
std::thread throws std::system_error when pthread_create is refused
(RLIMIT_NPROC, a container's pids cgroup, memory pressure), and nothing
caught it, so the node died by std::terminate. Threads are spawned per
peer connection, so the pressure that triggers this scales with peer
count, which is attacker-influenced. Observed killing a healthy
participant worker mid-mining.
Every spawn site in the node build, and what a failure now does:
- btftunnel.cpp: both tunnel pumps are guarded. The inbound pump starts
first because it owns cleanup of the pump end and rendezvous socket;
if the second spawn is refused, shutdown() wakes the first out of its
read and it cleans up itself -- closing those descriptors from the
failure path instead would race it onto reused descriptors. Both
paths return INVALID_SOCKET, which every caller already treats as
"this connection failed".
- btfrv.cpp: the relay's accept loop drops the connection it cannot
serve and stops accepting for a moment -- longer after consecutive
failures, capped at 5s -- instead of dying at an arbitrary one. The
per-pair second pump now runs on the handling thread itself, leaving
exactly one spawn to guard and nothing running yet to race.
- main.cpp: a participant that cannot spawn its hash workers stops the
ones that did start, drops the pool connection, and returns to the
reconnect loop, the same way an ordinary disconnect does.
- randomx_pow.cpp: dataset init joins the workers it got, releases the
dataset, and stays in light mode -- the same trade the allocation
failure path above it already makes.
- rpc.cpp, net.cpp: the remaining unchecked _beginthread calls (which
report failure by value, they cannot throw) now say precisely what
did not start. The pool's accept and payout threads failing silently
left the pool running deaf -- nobody accepts, nobody gets paid, and
nothing in the log says why.
Left alone deliberately: the per-relay accept spawns (rpc.cpp) already
check, log and retry on the next poll; startup-critical spawns in
net.cpp keep failing hard; the selftest-only std::thread uses may abort
a selftest run, which is acceptable where dying over one peer is not.
Verified: full node rebuilds clean and links; pool-stratum selftest
passes; a scratch harness under RLIMIT_NPROC=1 confirms _beginthread
reports -1 and std::thread throws system_error, the premises of every
path above.
Diffstat (limited to 'src/main.cpp')
| -rw-r--r-- | src/main.cpp | 30 |
1 files changed, 27 insertions, 3 deletions
diff --git a/src/main.cpp b/src/main.cpp index 7b3e555..d154523 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -14,6 +14,7 @@ | |||
| 14 | #include <atomic> | 14 | #include <atomic> |
| 15 | #include <mutex> | 15 | #include <mutex> |
| 16 | #include <thread> | 16 | #include <thread> |
| 17 | #include <system_error> | ||
| 17 | #include "bip32.h" | 18 | #include "bip32.h" |
| 18 | #include "btfaddr.h" | 19 | #include "btfaddr.h" |
| 19 | #include "btftunnel.h" | 20 | #include "btftunnel.h" |
| @@ -3899,9 +3900,32 @@ connected: | |||
| 3899 | std::vector<ParticipantShareHit> shareHits; | 3900 | std::vector<ParticipantShareHit> shareHits; |
| 3900 | std::mutex shareHitsMtx; | 3901 | std::mutex shareHitsMtx; |
| 3901 | std::vector<std::thread> hashThreads; | 3902 | std::vector<std::thread> hashThreads; |
| 3902 | for (int i = 0; i < nWorkers; i++) | 3903 | try |
| 3903 | hashThreads.emplace_back(ParticipantHashWorker, &jobShared, i, nWorkers, | 3904 | { |
| 3904 | &shareHits, &shareHitsMtx); | 3905 | for (int i = 0; i < nWorkers; i++) |
| 3906 | hashThreads.emplace_back(ParticipantHashWorker, &jobShared, i, nWorkers, | ||
| 3907 | &shareHits, &shareHitsMtx); | ||
| 3908 | } | ||
| 3909 | catch (const std::system_error& e) | ||
| 3910 | { | ||
| 3911 | // A refused pthread_create used to reach std::terminate here and | ||
| 3912 | // kill the worker mid-session -- observed twice on an otherwise | ||
| 3913 | // healthy participant. Stop the workers that did start the same way | ||
| 3914 | // the disconnect path below does, drop this pool connection, and let | ||
| 3915 | // the caller's reconnect loop try again once the pressure clears. | ||
| 3916 | LogPrint("worker", "[worker] cannot spawn hash threads (%s)" | ||
| 3917 | " -- dropping pool connection\n", e.what()); | ||
| 3918 | SetParticipantMiningStatus("hash thread spawn failed"); | ||
| 3919 | { | ||
| 3920 | std::lock_guard<std::mutex> lk(jobShared.mtx); | ||
| 3921 | jobShared.stop = true; | ||
| 3922 | jobShared.haveJob = false; | ||
| 3923 | } | ||
| 3924 | for (std::thread& t : hashThreads) | ||
| 3925 | t.join(); | ||
| 3926 | closesocket(s); | ||
| 3927 | return false; | ||
| 3928 | } | ||
| 3905 | LogPrint("worker", "[worker] spawned %d hash threads on shared RandomX dataset\n", | 3929 | LogPrint("worker", "[worker] spawned %d hash threads on shared RandomX dataset\n", |
| 3906 | nWorkers); | 3930 | nWorkers); |
| 3907 | const uint64 sessionStartHashes = gParticipantHashes.load(); | 3931 | const uint64 sessionStartHashes = gParticipantHashes.load(); |