upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBitflash <admin@bitflash.network>2026-08-12 10:31:37 -0300
committerBitflash <admin@bitflash.network>2026-08-12 10:31:37 -0300
commitc9bc3f6f97ea9090b2038166055da0283c6dc669 (patch)
tree81b5d44e5193f220b64097f496e314e04caf2db8
parentd0ebe648f333def99fababd65f6b929448616d56 (diff)
parentc8d798807e69ddbe55bfab8a34286ab75b786cb5 (diff)
Merge pull request 'net: replace peer select loop with poll' (#29) from poll-socket-handler into main
-rw-r--r--README.md2
-rw-r--r--src/makefile.mingw10
-rw-r--r--src/net.cpp152
-rw-r--r--src/net.h15
4 files changed, 97 insertions, 82 deletions
diff --git a/README.md b/README.md
index 978aba5..ef4f39b 100644
--- a/README.md
+++ b/README.md
@@ -342,7 +342,7 @@ probes disabled. Use `-tor=HOST:PORT` when Tor listens somewhere else.
342### When something looks wrong 342### When something looks wrong
343 343
344**Menu > Diagnostics**, and the same report in `debug.log` every ten minutes: 344**Menu > Diagnostics**, and the same report in `debug.log` every ten minutes:
345peers held against peers `select()` is actually watching, blocks received and how 345peers held against peers the socket loop is actually watching, blocks received and how
346many arrived without a parent, the proof-of-work mode with the live miner thread 346many arrived without a parent, the proof-of-work mode with the live miner thread
347count, sockets by the part of the program that opened them, and per peer how long 347count, sockets by the part of the program that opened them, and per peer how long
348since the last message each way. There is a copy button — if you open an issue, 348since 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 28e925c..4e36a5c 100644
--- a/src/makefile.mingw
+++ b/src/makefile.mingw
@@ -14,14 +14,14 @@ 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 \
18<<<<<<< HEAD
19 -DWIN32 -DNOPCH \
20 -DSECP256K1_STATIC \
21=======
23 -DWIN32 -DNOPCH -DFD_SETSIZE=1024 \ 22 -DWIN32 -DNOPCH -DFD_SETSIZE=1024 \
24 -DSECP256K1_STATIC -DBITFLASH_NODE_BUILD \ 23 -DSECP256K1_STATIC -DBITFLASH_NODE_BUILD \
24>>>>>>> origin/main
25 -I$(DEPS)/include -I$(RANDOMX)/src \ 25 -I$(DEPS)/include -I$(RANDOMX)/src \
26 -I$(DEPS)/include/nlohmann \ 26 -I$(DEPS)/include/nlohmann \
27 -IC:/msys64/ucrt64/include \ 27 -IC:/msys64/ucrt64/include \
diff --git a/src/net.cpp b/src/net.cpp
index fe9964e..c24547e 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"
@@ -23,6 +25,20 @@ void ThreadReconnectCachedBtfPeers(void* parg);
23void ThreadMessageHandler2(void* parg); 25void ThreadMessageHandler2(void* parg);
24void ThreadSocketHandler2(void* parg); 26void ThreadSocketHandler2(void* parg);
25 27
28#ifdef _WIN32
29typedef WSAPOLLFD BtfPollFd;
30static int BtfPoll(BtfPollFd* pfd, unsigned int nfd, int nTimeoutMs)
31{
32 return WSAPoll(pfd, nfd, nTimeoutMs);
33}
34#else
35typedef struct pollfd BtfPollFd;
36static int BtfPoll(BtfPollFd* pfd, unsigned int nfd, int nTimeoutMs)
37{
38 return poll(pfd, (nfds_t)nfd, nTimeoutMs);
39}
40#endif
41
26 42
27 43
28 44
@@ -275,8 +291,8 @@ string GetDiagnosticsText()
275 291
276 // The number that would have made the deafness obvious. Anything held but 292 // The number that would have made the deafness obvious. Anything held but
277 // not watched is a socket this node will never read again. 293 // not watched is a socket this node will never read again.
278 str += strprintf(" peers watched %d of %d that select() can hold%s\n", 294 str += strprintf(" peers watched %d of %d held by poll%s\n",
279 nPeersWatched, (int)FD_SETSIZE - 1, 295 nPeersWatched, nHeld,
280 nHeld > nPeersWatched ? " <-- NOT ALL PEERS ARE BEING READ" : ""); 296 nHeld > nPeersWatched ? " <-- NOT ALL PEERS ARE BEING READ" : "");
281 297
282 if (nBlocksReceived > 0) 298 if (nBlocksReceived > 0)
@@ -1202,7 +1218,7 @@ void CNode::Disconnect()
1202 1218
1203 // Invalidate before anything else can look at it. This used to close the 1219 // Invalidate before anything else can look at it. This used to close the
1204 // socket and leave the handle number sitting in the object, so ~CNode 1220 // socket and leave the handle number sitting in the object, so ~CNode
1205 // closed it a second time and the select loop kept arming it in the 1221 // closed it a second time and the socket loop kept arming it in the
1206 // meantime. See the note on ~CNode in net.h. 1222 // meantime. See the note on ~CNode in net.h.
1207 BtfCloseSocket(hSocket); 1223 BtfCloseSocket(hSocket);
1208 hSocket = INVALID_SOCKET; 1224 hSocket = INVALID_SOCKET;
@@ -1363,52 +1379,49 @@ void ThreadSocketHandler2(void* parg)
1363 // 1379 //
1364 // Find which sockets have data to receive 1380 // Find which sockets have data to receive
1365 // 1381 //
1366 struct timeval timeout; 1382 static const int nPollTimeoutMs = 50; // frequency to poll pnode->vSend
1367 timeout.tv_sec = 0; 1383
1368 timeout.tv_usec = 50000; // frequency to poll pnode->vSend 1384 struct NodePollResult
1369 1385 {
1370 fd_set fdsetRecv; 1386 CNode* pnode;
1371 fd_set fdsetSend; 1387 SOCKET hSocket;
1372 FD_ZERO(&fdsetRecv); 1388 bool fRecv;
1373 FD_ZERO(&fdsetSend); 1389 bool fSend;
1374 SOCKET hSocketMax = 0; 1390 bool fInvalid;
1375 FD_SET(hListenSocket, &fdsetRecv); 1391 NodePollResult(CNode* pnodeIn, SOCKET hSocketIn)
1376 hSocketMax = max(hSocketMax, hListenSocket); 1392 : pnode(pnodeIn), hSocket(hSocketIn), fRecv(false), fSend(false), fInvalid(false) {}
1377 // FD_SET drops silently once the set is full, so count what goes in 1393 };
1378 // and say so. A peer that never makes it into the set is never read, 1394
1379 // and that is indistinguishable from a peer with nothing to say -- 1395 vector<BtfPollFd> vPoll;
1380 // the failure this whole path used to hide. 1396 vector<NodePollResult> vPollNodes;
1381 unsigned int nWatched = 1; // the listen socket 1397
1398 BtfPollFd listenPoll;
1399 memset(&listenPoll, 0, sizeof(listenPoll));
1400 listenPoll.fd = hListenSocket;
1401 listenPoll.events = POLLIN;
1402 vPoll.push_back(listenPoll);
1403
1382 CRITICAL_BLOCK(cs_vNodes) 1404 CRITICAL_BLOCK(cs_vNodes)
1383 { 1405 {
1384 foreach(CNode* pnode, vNodes) 1406 foreach(CNode* pnode, vNodes)
1385 { 1407 {
1386 if (nWatched >= FD_SETSIZE)
1387 break;
1388 // A disconnected node stays in vNodes until its references go. 1408 // A disconnected node stays in vNodes until its references go.
1389 // Arming its closed handle asks select() to watch a number the 1409 // Do not watch a closed handle number: the OS may already have
1390 // operating system may already have given to somebody else. 1410 // handed it to a different connection.
1391 if (pnode->hSocket == INVALID_SOCKET) 1411 if (pnode->hSocket == INVALID_SOCKET)
1392 continue; 1412 continue;
1393 FD_SET(pnode->hSocket, &fdsetRecv); 1413
1394 nWatched++; 1414 BtfPollFd pfd;
1395 hSocketMax = max(hSocketMax, pnode->hSocket); 1415 memset(&pfd, 0, sizeof(pfd));
1416 pfd.fd = pnode->hSocket;
1417 pfd.events = POLLIN;
1396 TRY_CRITICAL_BLOCK(pnode->cs_vSend) 1418 TRY_CRITICAL_BLOCK(pnode->cs_vSend)
1397 if (!pnode->vSend.empty()) 1419 if (!pnode->vSend.empty())
1398 FD_SET(pnode->hSocket, &fdsetSend); 1420 pfd.events |= POLLOUT;
1399 } 1421 vPoll.push_back(pfd);
1400 if (vNodes.size() + 1 > (size_t)FD_SETSIZE) 1422 vPollNodes.push_back(NodePollResult(pnode, pnode->hSocket));
1401 {
1402 static int64 nLastWarned = 0;
1403 if (GetTime() - nLastWarned > 60)
1404 {
1405 nLastWarned = GetTime();
1406 printf("WARNING: %d peers but select() can only watch %d -- %d are not being read\n",
1407 (int)vNodes.size(), (int)FD_SETSIZE - 1,
1408 (int)vNodes.size() - (int)FD_SETSIZE + 1);
1409 }
1410 } 1423 }
1411 nPeersWatched = (int)nWatched - 1; // less the listen socket 1424 nPeersWatched = (int)vPollNodes.size();
1412 } 1425 }
1413 1426
1414 // Say the state of the node out loud now and then, so a log pulled off 1427 // Say the state of the node out loud now and then, so a log pulled off
@@ -1429,32 +1442,17 @@ void ThreadSocketHandler2(void* parg)
1429 } 1442 }
1430 1443
1431 vfThreadRunning[0] = false; 1444 vfThreadRunning[0] = false;
1432 int nSelect = select(hSocketMax + 1, &fdsetRecv, &fdsetSend, NULL, &timeout); 1445 int nPoll = BtfPoll(&vPoll[0], (unsigned int)vPoll.size(), nPollTimeoutMs);
1433 vfThreadRunning[0] = true; 1446 vfThreadRunning[0] = true;
1434 CheckForShutdown(0); 1447 CheckForShutdown(0);
1435 if (nSelect == SOCKET_ERROR) 1448 if (nPoll == SOCKET_ERROR)
1436 { 1449 {
1437 // This used to answer a failed select() by marking every
1438 // descriptor from 0 to hSocketMax ready, which is code written for
1439 // POSIX, where a descriptor is a small integer. A Windows SOCKET is
1440 // a kernel handle in the thousands, so the loop iterated over
1441 // numbers that are not sockets and told the rest of the function
1442 // that every peer had data. One bad handle in the set makes
1443 // select() fail on every pass, so a single closed socket put the
1444 // whole thread into that state permanently.
1445 //
1446 // Find the bad handle instead and let it be dropped.
1447 int nErr = WSAGetLastError(); 1450 int nErr = WSAGetLastError();
1448 LogPrint("net", "select failed: %d\n", nErr); 1451 LogPrint("net", "poll failed: %d\n", nErr);
1449 FD_ZERO(&fdsetRecv);
1450 FD_ZERO(&fdsetSend);
1451 CRITICAL_BLOCK(cs_vNodes) 1452 CRITICAL_BLOCK(cs_vNodes)
1452 { 1453 {
1453 foreach(CNode* pnode, vNodes) 1454 foreach(CNode* pnode, vNodes)
1454 { 1455 {
1455 // Already disconnected: its handle is gone on purpose, and
1456 // reporting it here would name the wrong node for a select()
1457 // failure it did not cause.
1458 if (pnode->hSocket == INVALID_SOCKET) 1456 if (pnode->hSocket == INVALID_SOCKET)
1459 continue; 1457 continue;
1460 int nType = 0; 1458 int nType = 0;
@@ -1471,7 +1469,17 @@ void ThreadSocketHandler2(void* parg)
1471 } 1469 }
1472 } 1470 }
1473 } 1471 }
1474 Sleep(timeout.tv_usec/1000); 1472 Sleep(nPollTimeoutMs);
1473 }
1474 else
1475 {
1476 for (size_t i = 0; i < vPollNodes.size(); i++)
1477 {
1478 short revents = vPoll[i + 1].revents; // poll[0] is the listener
1479 vPollNodes[i].fRecv = (revents & (POLLIN | POLLERR | POLLHUP)) != 0;
1480 vPollNodes[i].fSend = (revents & POLLOUT) != 0;
1481 vPollNodes[i].fInvalid = (revents & POLLNVAL) != 0;
1482 }
1475 } 1483 }
1476 RandAddSeed(); 1484 RandAddSeed();
1477 1485
@@ -1487,7 +1495,7 @@ void ThreadSocketHandler2(void* parg)
1487 // 1495 //
1488 // Accept new connections 1496 // Accept new connections
1489 // 1497 //
1490 if (FD_ISSET(hListenSocket, &fdsetRecv)) 1498 if (nPoll != SOCKET_ERROR && (vPoll[0].revents & POLLIN))
1491 { 1499 {
1492 struct sockaddr_in sockaddr; 1500 struct sockaddr_in sockaddr;
1493#ifdef _WIN32 1501#ifdef _WIN32
@@ -1506,7 +1514,7 @@ void ThreadSocketHandler2(void* parg)
1506 { 1514 {
1507 // Refuse rather than accept a connection this node cannot 1515 // Refuse rather than accept a connection this node cannot
1508 // watch. Without this the listen socket kept taking peers -- 1516 // watch. Without this the listen socket kept taking peers --
1509 // it is always in the select set -- while everything past the 1517 // it is always watched -- while everything past the
1510 // limit sat open and unread, which fed on itself: the deafer 1518 // limit sat open and unread, which fed on itself: the deafer
1511 // the node got, the more connections it collected. 1519 // the node got, the more connections it collected.
1512 unsigned int nNodes = 0; 1520 unsigned int nNodes = 0;
@@ -1533,18 +1541,28 @@ void ThreadSocketHandler2(void* parg)
1533 // 1541 //
1534 // Service each socket 1542 // Service each socket
1535 // 1543 //
1536 vector<CNode*> vNodesCopy; 1544 for (size_t i = 0; i < vPollNodes.size(); i++)
1537 CRITICAL_BLOCK(cs_vNodes)
1538 vNodesCopy = vNodes;
1539 foreach(CNode* pnode, vNodesCopy)
1540 { 1545 {
1541 CheckForShutdown(0); 1546 CheckForShutdown(0);
1542 SOCKET hSocket = pnode->hSocket; 1547 NodePollResult& poll = vPollNodes[i];
1548 CNode* pnode = poll.pnode;
1549 SOCKET hSocket = poll.hSocket;
1550
1551 if (hSocket == INVALID_SOCKET || pnode->hSocket != hSocket)
1552 continue;
1553
1554 if (poll.fInvalid)
1555 {
1556 LogPrint("net", "poll reported invalid socket for %s\n",
1557 pnode->addr.ToString().c_str());
1558 pnode->fDisconnect = true;
1559 continue;
1560 }
1543 1561
1544 // 1562 //
1545 // Receive 1563 // Receive
1546 // 1564 //
1547 if (FD_ISSET(hSocket, &fdsetRecv)) 1565 if (poll.fRecv)
1548 { 1566 {
1549 TRY_CRITICAL_BLOCK(pnode->cs_vRecv) 1567 TRY_CRITICAL_BLOCK(pnode->cs_vRecv)
1550 { 1568 {
@@ -1584,7 +1602,7 @@ void ThreadSocketHandler2(void* parg)
1584 // 1602 //
1585 // Send 1603 // Send
1586 // 1604 //
1587 if (FD_ISSET(hSocket, &fdsetSend)) 1605 if (poll.fSend)
1588 { 1606 {
1589 TRY_CRITICAL_BLOCK(pnode->cs_vSend) 1607 TRY_CRITICAL_BLOCK(pnode->cs_vSend)
1590 { 1608 {
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