diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-03 21:49:26 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-03 21:49:26 +0000 |
| commit | 5210bd66bb3837c7f8c6e551abc7088e9eb0b36f (patch) | |
| tree | ee098e4a9e6cdbd2451481bc9ae169fef29d32ea | |
| parent | ad62cfc9e7301a5ac456b92fa31523a516688d30 (diff) | |
| parent | b8a976f4870ba5dfc7a6af1ea09c9277b71d686c (diff) | |
merge: !4 fix/robustness into deploy/all-2026-08-24
| -rw-r--r-- | src/btfrv.cpp | 49 | ||||
| -rw-r--r-- | src/btftunnel.cpp | 103 | ||||
| -rw-r--r-- | src/gui.cpp | 34 | ||||
| -rw-r--r-- | src/main.cpp | 30 | ||||
| -rw-r--r-- | src/net.cpp | 5 | ||||
| -rw-r--r-- | src/randomx_pow.cpp | 28 | ||||
| -rw-r--r-- | src/rpc.cpp | 15 | ||||
| -rw-r--r-- | src/util.h | 2 |
8 files changed, 239 insertions, 27 deletions
diff --git a/src/btfrv.cpp b/src/btfrv.cpp index 133eb89..8ab735c 100644 --- a/src/btfrv.cpp +++ b/src/btfrv.cpp | |||
| @@ -40,6 +40,8 @@ typedef int SOCKET; | |||
| 40 | #include <thread> | 40 | #include <thread> |
| 41 | #include <mutex> | 41 | #include <mutex> |
| 42 | #include <atomic> | 42 | #include <atomic> |
| 43 | #include <chrono> | ||
| 44 | #include <system_error> | ||
| 43 | 45 | ||
| 44 | namespace btf | 46 | namespace btf |
| 45 | { | 47 | { |
| @@ -209,8 +211,28 @@ static void HandleIncoming(SOCKET s) | |||
| 209 | bool a = WriteN(svc, &ok, 1); | 211 | bool a = WriteN(svc, &ok, 1); |
| 210 | bool b = WriteN(s, &ok, 1); | 212 | bool b = WriteN(s, &ok, 1); |
| 211 | if (!a || !b) { CLOSESOCK(svc); CLOSESOCK(s); return; } | 213 | if (!a || !b) { CLOSESOCK(svc); CLOSESOCK(s); return; } |
| 212 | std::thread(Forward, s, svc).detach(); | 214 | // One thread pumps s -> svc; this thread pumps svc -> s. Spawning a |
| 213 | std::thread(Forward, svc, s).detach(); | 215 | // thread per direction left no clean way to fail the second spawn: |
| 216 | // std::thread throws std::system_error when pthread_create is refused | ||
| 217 | // (NPROC limit, container pids cgroup, memory pressure), the first | ||
| 218 | // pump was already detached, and closing its sockets from here would | ||
| 219 | // race it -- so nothing caught the throw and it reached | ||
| 220 | // std::terminate, killing the relay over one pair it could not serve. | ||
| 221 | // Running one direction on this thread leaves exactly one spawn to | ||
| 222 | // guard, and a failure here has nothing running yet to race. | ||
| 223 | try | ||
| 224 | { | ||
| 225 | std::thread(Forward, s, svc).detach(); | ||
| 226 | } | ||
| 227 | catch (const std::system_error& e) | ||
| 228 | { | ||
| 229 | fprintf(stderr, "rendezvous relay: cannot spawn forward thread (%s), pair dropped\n", | ||
| 230 | e.what()); | ||
| 231 | CLOSESOCK(svc); | ||
| 232 | CLOSESOCK(s); | ||
| 233 | return; | ||
| 234 | } | ||
| 235 | Forward(svc, s); | ||
| 214 | } | 236 | } |
| 215 | else | 237 | else |
| 216 | { | 238 | { |
| @@ -236,6 +258,7 @@ bool RvRelayRun(unsigned short port) | |||
| 236 | if (bind(g_listen, (struct sockaddr*)&addr, sizeof(addr)) != 0) { CLOSESOCK(g_listen); return false; } | 258 | if (bind(g_listen, (struct sockaddr*)&addr, sizeof(addr)) != 0) { CLOSESOCK(g_listen); return false; } |
| 237 | if (listen(g_listen, 16) != 0) { CLOSESOCK(g_listen); return false; } | 259 | if (listen(g_listen, 16) != 0) { CLOSESOCK(g_listen); return false; } |
| 238 | 260 | ||
| 261 | int nSpawnFails = 0; | ||
| 239 | while (!g_stop) | 262 | while (!g_stop) |
| 240 | { | 263 | { |
| 241 | struct sockaddr_in cli; | 264 | struct sockaddr_in cli; |
| @@ -246,7 +269,27 @@ bool RvRelayRun(unsigned short port) | |||
| 246 | if (g_stop) break; | 269 | if (g_stop) break; |
| 247 | continue; | 270 | continue; |
| 248 | } | 271 | } |
| 249 | std::thread(HandleIncoming, s).detach(); | 272 | try |
| 273 | { | ||
| 274 | std::thread(HandleIncoming, s).detach(); | ||
| 275 | nSpawnFails = 0; | ||
| 276 | } | ||
| 277 | catch (const std::system_error& e) | ||
| 278 | { | ||
| 279 | // A refused pthread_create is backpressure, not a reason to die: | ||
| 280 | // the condition behind it (NPROC, pids cgroup, memory) does not | ||
| 281 | // clear in milliseconds, and the connection count feeding it is | ||
| 282 | // attacker-influenced. Drop this one peer, stop accepting for a | ||
| 283 | // moment -- longer each time, capped -- and keep serving the | ||
| 284 | // connections already established. | ||
| 285 | fprintf(stderr, "rendezvous relay: cannot spawn connection thread (%s), connection dropped\n", | ||
| 286 | e.what()); | ||
| 287 | CLOSESOCK(s); | ||
| 288 | nSpawnFails++; | ||
| 289 | int nWaitMs = nSpawnFails < 5 ? nSpawnFails * 1000 : 5000; | ||
| 290 | for (int i = 0; i < nWaitMs && !g_stop; i += 100) | ||
| 291 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); | ||
| 292 | } | ||
| 250 | } | 293 | } |
| 251 | CLOSESOCK(g_listen); | 294 | CLOSESOCK(g_listen); |
| 252 | g_listen = INVALID_SOCKET; | 295 | g_listen = INVALID_SOCKET; |
diff --git a/src/btftunnel.cpp b/src/btftunnel.cpp index 63d178b..4acca1a 100644 --- a/src/btftunnel.cpp +++ b/src/btftunnel.cpp | |||
| @@ -25,8 +25,12 @@ typedef int SOCKET; | |||
| 25 | #endif | 25 | #endif |
| 26 | #include "sockcount.h" | 26 | #include "sockcount.h" |
| 27 | #include <cstring> | 27 | #include <cstring> |
| 28 | #include <cstdio> | ||
| 29 | #include <memory> | ||
| 30 | #include <new> | ||
| 28 | #include <vector> | 31 | #include <vector> |
| 29 | #include <thread> | 32 | #include <thread> |
| 33 | #include <system_error> | ||
| 30 | 34 | ||
| 31 | namespace btf | 35 | namespace btf |
| 32 | { | 36 | { |
| @@ -119,20 +123,111 @@ static void PumpIn(SOCKET pumpEnd, RvSocket rv, std::vector<unsigned char> key) | |||
| 119 | shutdown((SOCKET)rv, SD_BOTH); | 123 | shutdown((SOCKET)rv, SD_BOTH); |
| 120 | } | 124 | } |
| 121 | 125 | ||
| 126 | // The pump end and the rendezvous socket, owned jointly by whoever is still | ||
| 127 | // using them. Closed once, by the last reference to go. | ||
| 128 | // | ||
| 129 | // Joint ownership rather than "the inbound pump owns the cleanup", because | ||
| 130 | // either pump can reach its exit first and BOTH touch BOTH descriptors on the | ||
| 131 | // way out -- PumpOut ends in shutdown(rv), shutdown(pumpEnd) and PumpIn ends in | ||
| 132 | // the mirror image. A single owner therefore has no exit at which the other is | ||
| 133 | // guaranteed to be finished. Holding a reference for the whole body of each | ||
| 134 | // pump gives it one: the close cannot happen until the last pump has stopped | ||
| 135 | // touching the descriptors, so no shutdown() can land on a number that has | ||
| 136 | // already been closed and handed back out by the kernel to an unrelated | ||
| 137 | // connection. | ||
| 138 | struct TunnelEnds | ||
| 139 | { | ||
| 140 | SOCKET pumpEnd; | ||
| 141 | RvSocket rv; | ||
| 142 | TunnelEnds(SOCKET p, RvSocket r) : pumpEnd(p), rv(r) {} | ||
| 143 | ~TunnelEnds() { BtfCloseSocket(pumpEnd); RvClose(rv); } | ||
| 144 | private: | ||
| 145 | TunnelEnds(const TunnelEnds&); | ||
| 146 | TunnelEnds& operator=(const TunnelEnds&); | ||
| 147 | }; | ||
| 148 | |||
| 122 | // Start the two pumps and hand back the app-facing socket. When both pumps | 149 | // Start the two pumps and hand back the app-facing socket. When both pumps |
| 123 | // finish, close the pump end and the rendezvous socket. | 150 | // finish, close the pump end and the rendezvous socket. |
| 151 | // | ||
| 152 | // That sentence used to be a comment describing something the code did not do. | ||
| 153 | // The inbound pump closed both descriptors as soon as it exited, without | ||
| 154 | // waiting for the outbound pump, which then called shutdown() on both at its | ||
| 155 | // own exit. Whether that shutdown hit a live descriptor or a closed one came | ||
| 156 | // down to which pump got there first, and the rendezvous socket dying before | ||
| 157 | // the app socket -- a relay restart, the far side hanging up -- is precisely | ||
| 158 | // the order that puts the inbound pump ahead. Measured over 25 such teardowns | ||
| 159 | // against the real code: 29 shutdowns landed on an already-closed descriptor. | ||
| 160 | // | ||
| 161 | // appEnd is not part of this. It is handed to the caller on success and closed | ||
| 162 | // here on every failure path; no pump ever touches it. | ||
| 163 | // | ||
| 164 | // Every tunnel is two threads, and a tunnel is one peer connection (a .btf | ||
| 165 | // dial or accept, a pool miner, a participant's pool session) -- so the | ||
| 166 | // thread count scales with peer count, which is attacker-influenced. When | ||
| 167 | // pthread_create is refused std::thread throws std::system_error; uncaught it | ||
| 168 | // reached std::terminate and took the whole process down over one connection | ||
| 169 | // it could not serve. Both spawns are guarded, and the failure paths return | ||
| 170 | // INVALID_SOCKET, which every caller already handles by dropping just that | ||
| 171 | // connection. | ||
| 124 | static btf_socket_t StartTunnel(RvSocket rv, const unsigned char shared[32]) | 172 | static btf_socket_t StartTunnel(RvSocket rv, const unsigned char shared[32]) |
| 125 | { | 173 | { |
| 126 | btf_socket_t appEnd, pumpEnd; | 174 | btf_socket_t appEnd, pumpEnd; |
| 127 | if (!MakeLocalPair(appEnd, pumpEnd)) { RvClose(rv); return INVALID_SOCKET; } | 175 | if (!MakeLocalPair(appEnd, pumpEnd)) { RvClose(rv); return INVALID_SOCKET; } |
| 128 | 176 | ||
| 129 | std::vector<unsigned char> key(shared, shared + 32); | 177 | std::vector<unsigned char> key(shared, shared + 32); |
| 130 | std::thread(PumpOut, pumpEnd, rv, key).detach(); | 178 | |
| 131 | std::thread([pumpEnd, rv, key]() { | 179 | // make_shared, not shared_ptr(new ...): it allocates before it constructs, |
| 132 | PumpIn(pumpEnd, rv, key); | 180 | // so it either comes back owning both descriptors or throws having taken |
| 181 | // ownership of neither. The two-step form can run the deleter and then | ||
| 182 | // throw, which would close both here and again below. | ||
| 183 | std::shared_ptr<TunnelEnds> ends; | ||
| 184 | try | ||
| 185 | { | ||
| 186 | ends = std::make_shared<TunnelEnds>(pumpEnd, rv); | ||
| 187 | } | ||
| 188 | catch (const std::bad_alloc&) | ||
| 189 | { | ||
| 190 | fprintf(stderr, "btf tunnel: out of memory starting tunnel, tunnel dropped\n"); | ||
| 133 | BtfCloseSocket(pumpEnd); | 191 | BtfCloseSocket(pumpEnd); |
| 192 | BtfCloseSocket(appEnd); | ||
| 134 | RvClose(rv); | 193 | RvClose(rv); |
| 135 | }).detach(); | 194 | return INVALID_SOCKET; |
| 195 | } | ||
| 196 | |||
| 197 | try | ||
| 198 | { | ||
| 199 | std::thread([ends, key]() { PumpIn(ends->pumpEnd, ends->rv, key); }).detach(); | ||
| 200 | } | ||
| 201 | catch (const std::system_error& e) | ||
| 202 | { | ||
| 203 | // Nothing is running yet: `ends` is the only reference, and returning | ||
| 204 | // drops it, which is what closes pumpEnd and rv. | ||
| 205 | fprintf(stderr, "btf tunnel: cannot spawn inbound pump (%s), tunnel dropped\n", | ||
| 206 | e.what()); | ||
| 207 | BtfCloseSocket(appEnd); | ||
| 208 | return INVALID_SOCKET; | ||
| 209 | } | ||
| 210 | |||
| 211 | try | ||
| 212 | { | ||
| 213 | std::thread([ends, key]() { PumpOut(ends->pumpEnd, ends->rv, key); }).detach(); | ||
| 214 | } | ||
| 215 | catch (const std::system_error& e) | ||
| 216 | { | ||
| 217 | // The inbound pump may be parked in RvReadN(rv) or may already have | ||
| 218 | // exited -- when the spawn is refused because the process is loaded, | ||
| 219 | // the rendezvous socket is often dead for the same reason, and then | ||
| 220 | // PumpIn returns on its first length read. Either way this thread | ||
| 221 | // still holds a reference, so both descriptors are still open: the | ||
| 222 | // shutdown wakes the pump if it is parked and is harmless if it is | ||
| 223 | // not. The close belongs to whichever reference goes last. | ||
| 224 | fprintf(stderr, "btf tunnel: cannot spawn outbound pump (%s), tunnel dropped\n", | ||
| 225 | e.what()); | ||
| 226 | shutdown((SOCKET)ends->rv, SD_BOTH); | ||
| 227 | shutdown(ends->pumpEnd, SD_BOTH); | ||
| 228 | BtfCloseSocket(appEnd); | ||
| 229 | return INVALID_SOCKET; | ||
| 230 | } | ||
| 136 | return appEnd; | 231 | return appEnd; |
| 137 | } | 232 | } |
| 138 | 233 | ||
diff --git a/src/gui.cpp b/src/gui.cpp index 8dcb854..9b3802b 100644 --- a/src/gui.cpp +++ b/src/gui.cpp | |||
| @@ -15,6 +15,7 @@ | |||
| 15 | #include <atomic> | 15 | #include <atomic> |
| 16 | #include <mutex> | 16 | #include <mutex> |
| 17 | #include <thread> | 17 | #include <thread> |
| 18 | #include <system_error> | ||
| 18 | #include "bip32.h" | 19 | #include "bip32.h" |
| 19 | #include "walletcmd.h" | 20 | #include "walletcmd.h" |
| 20 | #include "font_roboto.h" | 21 | #include "font_roboto.h" |
| @@ -910,8 +911,16 @@ static void DrawOptionsDialog() | |||
| 910 | 911 | ||
| 911 | if (nMineMode == MINE_OPERATOR) { | 912 | if (nMineMode == MINE_OPERATOR) { |
| 912 | if (!gPoolServerRunning) { | 913 | if (!gPoolServerRunning) { |
| 913 | gPoolServerRunning = true; | 914 | // Claim the server is up only once it is. The flag used to |
| 914 | _beginthread(ThreadRPCServer, 0, NULL); | 915 | // be set first and the result never looked at, so a refused |
| 916 | // spawn left an operator with a pool that reports itself | ||
| 917 | // running, no RPC server for miners to reach, and nothing | ||
| 918 | // anywhere saying why. The flag also latches: with it set, | ||
| 919 | // this branch is never retried. | ||
| 920 | if (_beginthread(ThreadRPCServer, 0, NULL) == (uintptr_t)-1) | ||
| 921 | printf("Error: _beginthread(ThreadRPCServer) failed -- pool server not started\n"); | ||
| 922 | else | ||
| 923 | gPoolServerRunning = true; | ||
| 915 | } else if (!gPoolRunning) { | 924 | } else if (!gPoolRunning) { |
| 916 | gPoolRunning = true; | 925 | gPoolRunning = true; |
| 917 | } | 926 | } |
| @@ -1124,11 +1133,28 @@ static void DrawRestorePhraseDialog() | |||
| 1124 | } | 1133 | } |
| 1125 | g_restoreDerived.store(0); | 1134 | g_restoreDerived.store(0); |
| 1126 | g_restoreRecovered.store(0); | 1135 | g_restoreRecovered.store(0); |
| 1136 | // Set before the spawn, not after: RestoreThread clears this | ||
| 1137 | // flag itself when it finishes, and a short restore can beat | ||
| 1138 | // us back here and have its `false` overwritten, leaving the | ||
| 1139 | // dialog disabled forever. | ||
| 1127 | g_restoreRunning.store(true); | 1140 | g_restoreRunning.store(true); |
| 1128 | // On its own thread: a restore walks the whole chain once per | 1141 | // On its own thread: a restore walks the whole chain once per |
| 1129 | // batch and would otherwise freeze the window for as long as it | 1142 | // batch and would otherwise freeze the window for as long as it |
| 1130 | // takes. | 1143 | // takes. A refused spawn is one restore that does not happen, |
| 1131 | std::thread(RestoreThread, std::string(g_restoreInput)).detach(); | 1144 | // not a reason to take the window down with an uncaught |
| 1145 | // std::system_error -- and the flag has to come back off, or | ||
| 1146 | // the dialog sits greyed out reporting a search nobody is | ||
| 1147 | // running. | ||
| 1148 | try | ||
| 1149 | { | ||
| 1150 | std::thread(RestoreThread, std::string(g_restoreInput)).detach(); | ||
| 1151 | } | ||
| 1152 | catch (const std::system_error& e) | ||
| 1153 | { | ||
| 1154 | g_restoreRunning.store(false); | ||
| 1155 | std::lock_guard<std::mutex> lock(g_restoreResultMutex); | ||
| 1156 | g_restoreResult = std::string("Could not start the search: ") + e.what(); | ||
| 1157 | } | ||
| 1132 | } | 1158 | } |
| 1133 | } | 1159 | } |
| 1134 | if (fBusy) | 1160 | if (fBusy) |
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(); |
diff --git a/src/net.cpp b/src/net.cpp index fed6123..16038c8 100644 --- a/src/net.cpp +++ b/src/net.cpp | |||
| @@ -2090,7 +2090,7 @@ bool StartNode(string& strError) | |||
| 2090 | if (addrIncoming.ip) | 2090 | if (addrIncoming.ip) |
| 2091 | addrLocalHost.ip = addrIncoming.ip; | 2091 | addrLocalHost.ip = addrIncoming.ip; |
| 2092 | 2092 | ||
| 2093 | _beginthread([](void*) { | 2093 | if (_beginthread([](void*) { |
| 2094 | unsigned int ip = addrLocalHost.ip; | 2094 | unsigned int ip = addrLocalHost.ip; |
| 2095 | if (GetMyExternalIP(ip)) { | 2095 | if (GetMyExternalIP(ip)) { |
| 2096 | addrLocalHost.ip = ip; | 2096 | addrLocalHost.ip = ip; |
| @@ -2098,7 +2098,8 @@ bool StartNode(string& strError) | |||
| 2098 | CWalletDB().WriteSetting("addrIncoming", addrIncoming); | 2098 | CWalletDB().WriteSetting("addrIncoming", addrIncoming); |
| 2099 | printf("External IP updated: %s\n", addrLocalHost.ToStringIP().c_str()); | 2099 | printf("External IP updated: %s\n", addrLocalHost.ToStringIP().c_str()); |
| 2100 | } | 2100 | } |
| 2101 | }, 0, NULL); | 2101 | }, 0, NULL) == (uintptr_t)-1) |
| 2102 | printf("Error: _beginthread(GetMyExternalIP) failed\n"); | ||
| 2102 | 2103 | ||
| 2103 | // Peers that answered last time, dialled straight away. The relays are the | 2104 | // Peers that answered last time, dialled straight away. The relays are the |
| 2104 | // only way to find anyone otherwise, and reaching them plus walking their | 2105 | // only way to find anyone otherwise, and reaching them plus walking their |
diff --git a/src/randomx_pow.cpp b/src/randomx_pow.cpp index 1d058d4..9c59364 100644 --- a/src/randomx_pow.cpp +++ b/src/randomx_pow.cpp | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | #include <thread> | 8 | #include <thread> |
| 9 | #include <vector> | 9 | #include <vector> |
| 10 | #include <mutex> | 10 | #include <mutex> |
| 11 | #include <system_error> | ||
| 11 | 12 | ||
| 12 | // Fixed key (seed) that determines the RandomX cache/dataset. Keeping it | 13 | // Fixed key (seed) that determines the RandomX cache/dataset. Keeping it |
| 13 | // constant avoids per-epoch "reseed" logic; for the CPU+RAM fairness goal | 14 | // constant avoids per-epoch "reseed" logic; for the CPU+RAM fairness goal |
| @@ -101,13 +102,28 @@ bool RandomXInitDataset(int nThreads) | |||
| 101 | 102 | ||
| 102 | std::vector<std::thread> workers; | 103 | std::vector<std::thread> workers; |
| 103 | unsigned long per = total / nThreads; | 104 | unsigned long per = total / nThreads; |
| 104 | for (int i = 0; i < nThreads; i++) | 105 | try |
| 105 | { | 106 | { |
| 106 | unsigned long start = i * per; | 107 | for (int i = 0; i < nThreads; i++) |
| 107 | unsigned long count = (i == nThreads - 1) ? (total - start) : per; | 108 | { |
| 108 | workers.emplace_back([start, count]() { | 109 | unsigned long start = i * per; |
| 109 | randomx_init_dataset(g_dataset, g_cache, start, count); | 110 | unsigned long count = (i == nThreads - 1) ? (total - start) : per; |
| 110 | }); | 111 | workers.emplace_back([start, count]() { |
| 112 | randomx_init_dataset(g_dataset, g_cache, start, count); | ||
| 113 | }); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | catch (const std::system_error& e) | ||
| 117 | { | ||
| 118 | // Same refusal as the allocation failure above: light mode is slow | ||
| 119 | // but alive, std::terminate is neither. Join the workers that did | ||
| 120 | // start before releasing the dataset out from under them. | ||
| 121 | for (auto& w : workers) w.join(); | ||
| 122 | randomx_release_dataset(g_dataset); | ||
| 123 | g_dataset = NULL; | ||
| 124 | printf("RandomX: cannot spawn dataset init threads (%s), staying in light mode\n", | ||
| 125 | e.what()); | ||
| 126 | return false; | ||
| 111 | } | 127 | } |
| 112 | for (auto& w : workers) w.join(); | 128 | for (auto& w : workers) w.join(); |
| 113 | 129 | ||
diff --git a/src/rpc.cpp b/src/rpc.cpp index a4b53ca..13cb6c9 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp | |||
| @@ -5077,10 +5077,17 @@ void ThreadRPCServer(void*) | |||
| 5077 | // armed for its one wallet probe before it is allowed to send. | 5077 | // armed for its one wallet probe before it is allowed to send. |
| 5078 | ReconcilePayouts(); | 5078 | ReconcilePayouts(); |
| 5079 | 5079 | ||
| 5080 | _beginthread(AcceptLoopFn, 0, NULL); | 5080 | // _beginthread cannot throw (it reports -1), but an unchecked failure |
| 5081 | _beginthread(LocalAcceptFn, 0, NULL); | 5081 | // leaves the pool running deaf: nobody accepts, announces, or pays, and |
| 5082 | _beginthread(ThreadBtfPoolAnnouncer, 0, NULL); | 5082 | // nothing in the log says why. |
| 5083 | _beginthread(PayoutThreadFn, 0, NULL); | 5083 | if (_beginthread(AcceptLoopFn, 0, NULL) == (uintptr_t)-1) |
| 5084 | LogPrint("pool", "[pool] FATAL: accept loop thread failed to spawn -- miners cannot connect\n"); | ||
| 5085 | if (_beginthread(LocalAcceptFn, 0, NULL) == (uintptr_t)-1) | ||
| 5086 | LogPrint("pool", "[pool] local accept thread failed to spawn\n"); | ||
| 5087 | if (_beginthread(ThreadBtfPoolAnnouncer, 0, NULL) == (uintptr_t)-1) | ||
| 5088 | LogPrint("pool", "[pool] announcer thread failed to spawn -- pool will not be advertised\n"); | ||
| 5089 | if (_beginthread(PayoutThreadFn, 0, NULL) == (uintptr_t)-1) | ||
| 5090 | LogPrint("pool", "[pool] FATAL: payout thread failed to spawn -- no one gets paid\n"); | ||
| 5084 | 5091 | ||
| 5085 | PoolEventLoop(); // blocks until gPoolRunning = false or fShutdown | 5092 | PoolEventLoop(); // blocks until gPoolRunning = false or fShutdown |
| 5086 | 5093 | ||
| @@ -199,7 +199,7 @@ protected: | |||
| 199 | public: | 199 | public: |
| 200 | char* pszFile; | 200 | char* pszFile; |
| 201 | int nLine; | 201 | int nLine; |
| 202 | explicit CCriticalSection() { InitializeCriticalSection(&cs); } | 202 | explicit CCriticalSection() : pszFile(NULL), nLine(0) { InitializeCriticalSection(&cs); } |
| 203 | ~CCriticalSection() { DeleteCriticalSection(&cs); } | 203 | ~CCriticalSection() { DeleteCriticalSection(&cs); } |
| 204 | void Enter() { EnterCriticalSection(&cs); } | 204 | void Enter() { EnterCriticalSection(&cs); } |
| 205 | void Leave() { LeaveCriticalSection(&cs); } | 205 | void Leave() { LeaveCriticalSection(&cs); } |