upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-02 07:09:45 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-02 07:09:45 +0000
commit0b566b4869b9e86c90162befd87f9882b6dd143c (patch)
tree978c6a3d0ecf729fa72ab38468280d195843996a /src/main.cpp
parenta44c9402c20493f0964a1296dd38d313216fbd4d (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.cpp30
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();