diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/btfrv.cpp | 49 | ||||
| -rw-r--r-- | src/btftunnel.cpp | 52 | ||||
| -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 |
6 files changed, 157 insertions, 22 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..2b3f818 100644 --- a/src/btftunnel.cpp +++ b/src/btftunnel.cpp | |||
| @@ -25,8 +25,10 @@ typedef int SOCKET; | |||
| 25 | #endif | 25 | #endif |
| 26 | #include "sockcount.h" | 26 | #include "sockcount.h" |
| 27 | #include <cstring> | 27 | #include <cstring> |
| 28 | #include <cstdio> | ||
| 28 | #include <vector> | 29 | #include <vector> |
| 29 | #include <thread> | 30 | #include <thread> |
| 31 | #include <system_error> | ||
| 30 | 32 | ||
| 31 | namespace btf | 33 | namespace btf |
| 32 | { | 34 | { |
| @@ -121,18 +123,60 @@ static void PumpIn(SOCKET pumpEnd, RvSocket rv, std::vector<unsigned char> key) | |||
| 121 | 123 | ||
| 122 | // Start the two pumps and hand back the app-facing socket. When both pumps | 124 | // Start the two pumps and hand back the app-facing socket. When both pumps |
| 123 | // finish, close the pump end and the rendezvous socket. | 125 | // finish, close the pump end and the rendezvous socket. |
| 126 | // | ||
| 127 | // Every tunnel is two threads, and a tunnel is one peer connection (a .btf | ||
| 128 | // dial or accept, a pool miner, a participant's pool session) -- so the | ||
| 129 | // thread count scales with peer count, which is attacker-influenced. When | ||
| 130 | // pthread_create is refused std::thread throws std::system_error; uncaught it | ||
| 131 | // reached std::terminate and took the whole process down over one connection | ||
| 132 | // it could not serve. Both spawns are guarded, and the failure paths return | ||
| 133 | // INVALID_SOCKET, which every caller already handles by dropping just that | ||
| 134 | // connection. | ||
| 124 | static btf_socket_t StartTunnel(RvSocket rv, const unsigned char shared[32]) | 135 | static btf_socket_t StartTunnel(RvSocket rv, const unsigned char shared[32]) |
| 125 | { | 136 | { |
| 126 | btf_socket_t appEnd, pumpEnd; | 137 | btf_socket_t appEnd, pumpEnd; |
| 127 | if (!MakeLocalPair(appEnd, pumpEnd)) { RvClose(rv); return INVALID_SOCKET; } | 138 | if (!MakeLocalPair(appEnd, pumpEnd)) { RvClose(rv); return INVALID_SOCKET; } |
| 128 | 139 | ||
| 129 | std::vector<unsigned char> key(shared, shared + 32); | 140 | std::vector<unsigned char> key(shared, shared + 32); |
| 130 | std::thread(PumpOut, pumpEnd, rv, key).detach(); | 141 | |
| 131 | std::thread([pumpEnd, rv, key]() { | 142 | // The inbound pump starts first because it owns the cleanup: it closes |
| 132 | PumpIn(pumpEnd, rv, key); | 143 | // pumpEnd and rv when it exits. That ownership is what makes the second |
| 144 | // spawn's failure path below race-free. | ||
| 145 | try | ||
| 146 | { | ||
| 147 | std::thread([pumpEnd, rv, key]() { | ||
| 148 | PumpIn(pumpEnd, rv, key); | ||
| 149 | BtfCloseSocket(pumpEnd); | ||
| 150 | RvClose(rv); | ||
| 151 | }).detach(); | ||
| 152 | } | ||
| 153 | catch (const std::system_error& e) | ||
| 154 | { | ||
| 155 | // Nothing is running yet, so closing everything here is safe. | ||
| 156 | fprintf(stderr, "btf tunnel: cannot spawn inbound pump (%s), tunnel dropped\n", | ||
| 157 | e.what()); | ||
| 133 | BtfCloseSocket(pumpEnd); | 158 | BtfCloseSocket(pumpEnd); |
| 159 | BtfCloseSocket(appEnd); | ||
| 134 | RvClose(rv); | 160 | RvClose(rv); |
| 135 | }).detach(); | 161 | return INVALID_SOCKET; |
| 162 | } | ||
| 163 | |||
| 164 | try | ||
| 165 | { | ||
| 166 | std::thread(PumpOut, pumpEnd, rv, key).detach(); | ||
| 167 | } | ||
| 168 | catch (const std::system_error& e) | ||
| 169 | { | ||
| 170 | // The inbound pump is parked in RvReadN(rv). shutdown() wakes it, and | ||
| 171 | // it closes pumpEnd and rv itself -- closing them here instead would | ||
| 172 | // race exactly that, and a reused descriptor is how an unrelated | ||
| 173 | // socket gets closed. | ||
| 174 | fprintf(stderr, "btf tunnel: cannot spawn outbound pump (%s), tunnel dropped\n", | ||
| 175 | e.what()); | ||
| 176 | shutdown((SOCKET)rv, SD_BOTH); | ||
| 177 | BtfCloseSocket(appEnd); | ||
| 178 | return INVALID_SOCKET; | ||
| 179 | } | ||
| 136 | return appEnd; | 180 | return appEnd; |
| 137 | } | 181 | } |
| 138 | 182 | ||
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 | ||