upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNsable-sh <nsable-sh@noreply.git.bitflash.network>2026-08-11 16:38:49 -0300
committerNsable-sh <nsable-sh@noreply.git.bitflash.network>2026-08-11 16:38:49 -0300
commitc5f6b52c0d1d65f7bf8cd745a7a212bc6b2fe318 (patch)
tree7ad2835cd6cdca8808b202036525d0cde2380607
parenta83fae37099cb102beb5a4c174a4abe6ef3f24a4 (diff)
net: replace peer select loop with poll
-rw-r--r--README.md2
-rw-r--r--src/makefile.mingw7
-rw-r--r--src/net.cpp152
-rw-r--r--src/net.h15
4 files changed, 93 insertions, 83 deletions
diff --git a/README.md b/README.md
index c28b0f1..f7d9468 100644
--- a/README.md
+++ b/README.md
@@ -321,7 +321,7 @@ the same one will refuse to start.
321### When something looks wrong 321### When something looks wrong
322 322
323**Menu > Diagnostics**, and the same report in `debug.log` every ten minutes: 323**Menu > Diagnostics**, and the same report in `debug.log` every ten minutes:
324peers held against peers `select()` is actually watching, blocks received and how 324peers held against peers the socket loop is actually watching, blocks received and how
325many arrived without a parent, the proof-of-work mode with the live miner thread 325many arrived without a parent, the proof-of-work mode with the live miner thread
326count, sockets by the part of the program that opened them, and per peer how long 326count, sockets by the part of the program that opened them, and per peer how long
327since the last message each way. There is a copy button — if you open an issue, 327since the last message each way. There is a copy button — if you open an issue,
diff --git a/src/makefile.mingw b/src/makefile.mingw
index b946e50..678d2e8 100644
--- a/src/makefile.mingw
+++ b/src/makefile.mingw
@@ -14,13 +14,8 @@ WARNFLAGS = -Wformat -Werror=format \
14 -Wno-deprecated-declarations -Wno-write-strings \ 14 -Wno-deprecated-declarations -Wno-write-strings \
15 -Wno-narrowing -Wno-unused-result 15 -Wno-narrowing -Wno-unused-result
16 16
17# FD_SETSIZE has to be defined before winsock2.h and it has to be the same
18# number in every translation unit -- fd_set carries an array of that length,
19# so disagreeing about it means disagreeing about sizeof(fd_set). That is why
20# it lives here as a -D and not as a #define in a header. Windows defaults to
21# 64, which capped this node at 63 watchable peers no matter how many it had.
22CXXFLAGS = -std=gnu++14 -O2 $(WARNFLAGS) -fpermissive -pthread \ 17CXXFLAGS = -std=gnu++14 -O2 $(WARNFLAGS) -fpermissive -pthread \
23 -DWIN32 -DNOPCH -DFD_SETSIZE=1024 \ 18 -DWIN32 -DNOPCH \
24 -DSECP256K1_STATIC \ 19 -DSECP256K1_STATIC \
25 -I$(DEPS)/include -I$(RANDOMX)/src \ 20 -I$(DEPS)/include -I$(RANDOMX)/src \
26 -I$(DEPS)/include/nlohmann \ 21 -I$(DEPS)/include/nlohmann \
diff --git a/src/net.cpp b/src/net.cpp
index b9bb95d..b6b990a 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -13,6 +13,8 @@
13#include <thread> // hardware_concurrency, for the miner thread count 13#include <thread> // hardware_concurrency, for the miner thread count
14#ifdef _WIN32 14#ifdef _WIN32
15#include <winsock2.h> 15#include <winsock2.h>
16#else
17#include <poll.h>
16#endif 18#endif
17#include <openssl/rand.h> 19#include <openssl/rand.h>
18#include "btfaddr.h" 20#include "btfaddr.h"
@@ -22,6 +24,20 @@ void ThreadReconnectCachedBtfPeers(void* parg);
22void ThreadMessageHandler2(void* parg); 24void ThreadMessageHandler2(void* parg);
23void ThreadSocketHandler2(void* parg); 25void ThreadSocketHandler2(void* parg);
24 26
27#ifdef _WIN32
28typedef WSAPOLLFD BtfPollFd;
29static int BtfPoll(BtfPollFd* pfd, unsigned int nfd, int nTimeoutMs)
30{
31 return WSAPoll(pfd, nfd, nTimeoutMs);
32}
33#else
34typedef struct pollfd BtfPollFd;
35static int BtfPoll(BtfPollFd* pfd, unsigned int nfd, int nTimeoutMs)
36{
37 return poll(pfd, (nfds_t)nfd, nTimeoutMs);
38}
39#endif
40
25 41
26 42
27 43
@@ -274,8 +290,8 @@ string GetDiagnosticsText()
274 290
275 // The number that would have made the deafness obvious. Anything held but 291 // The number that would have made the deafness obvious. Anything held but
276 // not watched is a socket this node will never read again. 292 // not watched is a socket this node will never read again.
277 str += strprintf(" peers watched %d of %d that select() can hold%s\n", 293 str += strprintf(" peers watched %d of %d held by poll%s\n",
278 nPeersWatched, (int)FD_SETSIZE - 1, 294 nPeersWatched, nHeld,
279 nHeld > nPeersWatched ? " <-- NOT ALL PEERS ARE BEING READ" : ""); 295 nHeld > nPeersWatched ? " <-- NOT ALL PEERS ARE BEING READ" : "");
280 296
281 if (nBlocksReceived > 0) 297 if (nBlocksReceived > 0)
@@ -1198,7 +1214,7 @@ void CNode::Disconnect()
1198 1214
1199 // Invalidate before anything else can look at it. This used to close the 1215 // Invalidate before anything else can look at it. This used to close the
1200 // socket and leave the handle number sitting in the object, so ~CNode 1216 // socket and leave the handle number sitting in the object, so ~CNode
1201 // closed it a second time and the select loop kept arming it in the 1217 // closed it a second time and the socket loop kept arming it in the
1202 // meantime. See the note on ~CNode in net.h. 1218 // meantime. See the note on ~CNode in net.h.
1203 BtfCloseSocket(hSocket); 1219 BtfCloseSocket(hSocket);
1204 hSocket = INVALID_SOCKET; 1220 hSocket = INVALID_SOCKET;
@@ -1359,52 +1375,49 @@ void ThreadSocketHandler2(void* parg)
1359 // 1375 //
1360 // Find which sockets have data to receive 1376 // Find which sockets have data to receive
1361 // 1377 //
1362 struct timeval timeout; 1378 static const int nPollTimeoutMs = 50; // frequency to poll pnode->vSend
1363 timeout.tv_sec = 0; 1379
1364 timeout.tv_usec = 50000; // frequency to poll pnode->vSend 1380 struct NodePollResult
1365 1381 {
1366 fd_set fdsetRecv; 1382 CNode* pnode;
1367 fd_set fdsetSend; 1383 SOCKET hSocket;
1368 FD_ZERO(&fdsetRecv); 1384 bool fRecv;
1369 FD_ZERO(&fdsetSend); 1385 bool fSend;
1370 SOCKET hSocketMax = 0; 1386 bool fInvalid;
1371 FD_SET(hListenSocket, &fdsetRecv); 1387 NodePollResult(CNode* pnodeIn, SOCKET hSocketIn)
1372 hSocketMax = max(hSocketMax, hListenSocket); 1388 : pnode(pnodeIn), hSocket(hSocketIn), fRecv(false), fSend(false), fInvalid(false) {}
1373 // FD_SET drops silently once the set is full, so count what goes in 1389 };
1374 // and say so. A peer that never makes it into the set is never read, 1390
1375 // and that is indistinguishable from a peer with nothing to say -- 1391 vector<BtfPollFd> vPoll;
1376 // the failure this whole path used to hide. 1392 vector<NodePollResult> vPollNodes;
1377 unsigned int nWatched = 1; // the listen socket 1393
1394 BtfPollFd listenPoll;
1395 memset(&listenPoll, 0, sizeof(listenPoll));
1396 listenPoll.fd = hListenSocket;
1397 listenPoll.events = POLLIN;
1398 vPoll.push_back(listenPoll);
1399
1378 CRITICAL_BLOCK(cs_vNodes) 1400 CRITICAL_BLOCK(cs_vNodes)
1379 { 1401 {
1380 foreach(CNode* pnode, vNodes) 1402 foreach(CNode* pnode, vNodes)
1381 { 1403 {
1382 if (nWatched >= FD_SETSIZE)
1383 break;
1384 // A disconnected node stays in vNodes until its references go. 1404 // A disconnected node stays in vNodes until its references go.
1385 // Arming its closed handle asks select() to watch a number the 1405 // Do not watch a closed handle number: the OS may already have
1386 // operating system may already have given to somebody else. 1406 // handed it to a different connection.
1387 if (pnode->hSocket == INVALID_SOCKET) 1407 if (pnode->hSocket == INVALID_SOCKET)
1388 continue; 1408 continue;
1389 FD_SET(pnode->hSocket, &fdsetRecv); 1409
1390 nWatched++; 1410 BtfPollFd pfd;
1391 hSocketMax = max(hSocketMax, pnode->hSocket); 1411 memset(&pfd, 0, sizeof(pfd));
1412 pfd.fd = pnode->hSocket;
1413 pfd.events = POLLIN;
1392 TRY_CRITICAL_BLOCK(pnode->cs_vSend) 1414 TRY_CRITICAL_BLOCK(pnode->cs_vSend)
1393 if (!pnode->vSend.empty()) 1415 if (!pnode->vSend.empty())
1394 FD_SET(pnode->hSocket, &fdsetSend); 1416 pfd.events |= POLLOUT;
1395 } 1417 vPoll.push_back(pfd);
1396 if (vNodes.size() + 1 > (size_t)FD_SETSIZE) 1418 vPollNodes.push_back(NodePollResult(pnode, pnode->hSocket));
1397 {
1398 static int64 nLastWarned = 0;
1399 if (GetTime() - nLastWarned > 60)
1400 {
1401 nLastWarned = GetTime();
1402 printf("WARNING: %d peers but select() can only watch %d -- %d are not being read\n",
1403 (int)vNodes.size(), (int)FD_SETSIZE - 1,
1404 (int)vNodes.size() - (int)FD_SETSIZE + 1);
1405 }
1406 } 1419 }
1407 nPeersWatched = (int)nWatched - 1; // less the listen socket 1420 nPeersWatched = (int)vPollNodes.size();
1408 } 1421 }
1409 1422
1410 // Say the state of the node out loud now and then, so a log pulled off 1423 // Say the state of the node out loud now and then, so a log pulled off
@@ -1425,32 +1438,17 @@ void ThreadSocketHandler2(void* parg)
1425 } 1438 }
1426 1439
1427 vfThreadRunning[0] = false; 1440 vfThreadRunning[0] = false;
1428 int nSelect = select(hSocketMax + 1, &fdsetRecv, &fdsetSend, NULL, &timeout); 1441 int nPoll = BtfPoll(&vPoll[0], (unsigned int)vPoll.size(), nPollTimeoutMs);
1429 vfThreadRunning[0] = true; 1442 vfThreadRunning[0] = true;
1430 CheckForShutdown(0); 1443 CheckForShutdown(0);
1431 if (nSelect == SOCKET_ERROR) 1444 if (nPoll == SOCKET_ERROR)
1432 { 1445 {
1433 // This used to answer a failed select() by marking every
1434 // descriptor from 0 to hSocketMax ready, which is code written for
1435 // POSIX, where a descriptor is a small integer. A Windows SOCKET is
1436 // a kernel handle in the thousands, so the loop iterated over
1437 // numbers that are not sockets and told the rest of the function
1438 // that every peer had data. One bad handle in the set makes
1439 // select() fail on every pass, so a single closed socket put the
1440 // whole thread into that state permanently.
1441 //
1442 // Find the bad handle instead and let it be dropped.
1443 int nErr = WSAGetLastError(); 1446 int nErr = WSAGetLastError();
1444 LogPrint("net", "select failed: %d\n", nErr); 1447 LogPrint("net", "poll failed: %d\n", nErr);
1445 FD_ZERO(&fdsetRecv);
1446 FD_ZERO(&fdsetSend);
1447 CRITICAL_BLOCK(cs_vNodes) 1448 CRITICAL_BLOCK(cs_vNodes)
1448 { 1449 {
1449 foreach(CNode* pnode, vNodes) 1450 foreach(CNode* pnode, vNodes)
1450 { 1451 {
1451 // Already disconnected: its handle is gone on purpose, and
1452 // reporting it here would name the wrong node for a select()
1453 // failure it did not cause.
1454 if (pnode->hSocket == INVALID_SOCKET) 1452 if (pnode->hSocket == INVALID_SOCKET)
1455 continue; 1453 continue;
1456 int nType = 0; 1454 int nType = 0;
@@ -1467,7 +1465,17 @@ void ThreadSocketHandler2(void* parg)
1467 } 1465 }
1468 } 1466 }
1469 } 1467 }
1470 Sleep(timeout.tv_usec/1000); 1468 Sleep(nPollTimeoutMs);
1469 }
1470 else
1471 {
1472 for (size_t i = 0; i < vPollNodes.size(); i++)
1473 {
1474 short revents = vPoll[i + 1].revents; // poll[0] is the listener
1475 vPollNodes[i].fRecv = (revents & (POLLIN | POLLERR | POLLHUP)) != 0;
1476 vPollNodes[i].fSend = (revents & POLLOUT) != 0;
1477 vPollNodes[i].fInvalid = (revents & POLLNVAL) != 0;
1478 }
1471 } 1479 }
1472 RandAddSeed(); 1480 RandAddSeed();
1473 1481
@@ -1483,7 +1491,7 @@ void ThreadSocketHandler2(void* parg)
1483 // 1491 //
1484 // Accept new connections 1492 // Accept new connections
1485 // 1493 //
1486 if (FD_ISSET(hListenSocket, &fdsetRecv)) 1494 if (nPoll != SOCKET_ERROR && (vPoll[0].revents & POLLIN))
1487 { 1495 {
1488 struct sockaddr_in sockaddr; 1496 struct sockaddr_in sockaddr;
1489#ifdef _WIN32 1497#ifdef _WIN32
@@ -1502,7 +1510,7 @@ void ThreadSocketHandler2(void* parg)
1502 { 1510 {
1503 // Refuse rather than accept a connection this node cannot 1511 // Refuse rather than accept a connection this node cannot
1504 // watch. Without this the listen socket kept taking peers -- 1512 // watch. Without this the listen socket kept taking peers --
1505 // it is always in the select set -- while everything past the 1513 // it is always watched -- while everything past the
1506 // limit sat open and unread, which fed on itself: the deafer 1514 // limit sat open and unread, which fed on itself: the deafer
1507 // the node got, the more connections it collected. 1515 // the node got, the more connections it collected.
1508 unsigned int nNodes = 0; 1516 unsigned int nNodes = 0;
@@ -1529,18 +1537,28 @@ void ThreadSocketHandler2(void* parg)
1529 // 1537 //
1530 // Service each socket 1538 // Service each socket
1531 // 1539 //
1532 vector<CNode*> vNodesCopy; 1540 for (size_t i = 0; i < vPollNodes.size(); i++)
1533 CRITICAL_BLOCK(cs_vNodes)
1534 vNodesCopy = vNodes;
1535 foreach(CNode* pnode, vNodesCopy)
1536 { 1541 {
1537 CheckForShutdown(0); 1542 CheckForShutdown(0);
1538 SOCKET hSocket = pnode->hSocket; 1543 NodePollResult& poll = vPollNodes[i];
1544 CNode* pnode = poll.pnode;
1545 SOCKET hSocket = poll.hSocket;
1546
1547 if (hSocket == INVALID_SOCKET || pnode->hSocket != hSocket)
1548 continue;
1549
1550 if (poll.fInvalid)
1551 {
1552 LogPrint("net", "poll reported invalid socket for %s\n",
1553 pnode->addr.ToString().c_str());
1554 pnode->fDisconnect = true;
1555 continue;
1556 }
1539 1557
1540 // 1558 //
1541 // Receive 1559 // Receive
1542 // 1560 //
1543 if (FD_ISSET(hSocket, &fdsetRecv)) 1561 if (poll.fRecv)
1544 { 1562 {
1545 TRY_CRITICAL_BLOCK(pnode->cs_vRecv) 1563 TRY_CRITICAL_BLOCK(pnode->cs_vRecv)
1546 { 1564 {
@@ -1580,7 +1598,7 @@ void ThreadSocketHandler2(void* parg)
1580 // 1598 //
1581 // Send 1599 // Send
1582 // 1600 //
1583 if (FD_ISSET(hSocket, &fdsetSend)) 1601 if (poll.fSend)
1584 { 1602 {
1585 TRY_CRITICAL_BLOCK(pnode->cs_vSend) 1603 TRY_CRITICAL_BLOCK(pnode->cs_vSend)
1586 { 1604 {
diff --git a/src/net.h b/src/net.h
index bcd0b2a..feecf96 100644
--- a/src/net.h
+++ b/src/net.h
@@ -98,19 +98,16 @@ static const int BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS = 2 * 60;
98 98
99// Ceiling on simultaneous connections. 99// Ceiling on simultaneous connections.
100// 100//
101// ThreadSocketHandler watches every peer through one select(), and select() 101// ThreadSocketHandler watches every peer in one socket loop. This used to be a
102// cannot watch more descriptors than FD_SETSIZE. On Windows FD_SET simply 102// select() loop, which made FD_SETSIZE the real limit and silently starved
103// stops adding once the set is full -- no error, no log -- so every socket 103// sockets past that bitmap. It is now poll()/WSAPoll(), so the ceiling below is
104// past the limit stays open and is never read again. Nothing capped vNodes, 104// a deliberate resource bound rather than a descriptor-set accident.
105// so a node accepted connections it had no way to service, and because new
106// entries go on the end of vNodes it was always the newest connection that
107// starved, including the ones dialed to fix connectivity.
108// 105//
109// Measured on two nodes before this ceiling existed: 277 and 90 CNode objects 106// Measured on two nodes before this ceiling existed: 277 and 90 CNode objects
110// against 93 and 90 live sockets, and a status bar reporting hundreds of 107// against 93 and 90 live sockets, and a status bar reporting hundreds of
111// peers for a node that was really talking to thirty. 108// peers for a node that was really talking to thirty.
112// 109//
113// 125 is Bitcoin's number and sits far below FD_SETSIZE on both platforms. 110// 125 is Bitcoin's number and remains a conservative public-node default.
114static const unsigned int MAX_CONNECTIONS = 125; 111static const unsigned int MAX_CONNECTIONS = 125;
115 112
116// A node marked for disconnect is normally held until its buffers drain, so 113// A node marked for disconnect is normally held until its buffers drain, so
@@ -142,7 +139,7 @@ static const int BTF_BEHIND_WARN_INTERVAL_SECS = 5 * 60;
142// 139//
143// These counters exist so it can. Cheap to keep, and they name the failures 140// These counters exist so it can. Cheap to keep, and they name the failures
144// that actually happened rather than the ones that sound impressive. 141// that actually happened rather than the ones that sound impressive.
145extern int nPeersWatched; // peers in the last select() set 142extern int nPeersWatched; // peers in the last poll() set
146extern int64 nBlocksReceived; // blocks handed to ProcessBlock 143extern int64 nBlocksReceived; // blocks handed to ProcessBlock
147extern int64 nBlocksWithoutParent; // ...of those, how many arrived orphaned 144extern int64 nBlocksWithoutParent; // ...of those, how many arrived orphaned
148 145