diff options
| author | Bitflash <btf01@proton.me> | 2026-07-31 17:40:58 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-31 17:40:58 -0300 |
| commit | d5bcd347cd8d156e3a6d4526fd2470741194a64a (patch) | |
| tree | 41e0820126fb7a0f1daebc8130054c64a00b9faf /src/net.cpp | |
| parent | 795e51612bd5b17dbbf8f0cbea64ee4c874b5a33 (diff) | |
net: a node could only ever watch 63 peers (#78)
Diffstat (limited to 'src/net.cpp')
| -rw-r--r-- | src/net.cpp | 96 |
1 files changed, 88 insertions, 8 deletions
diff --git a/src/net.cpp b/src/net.cpp index 37c081f..5ef8316 100644 --- a/src/net.cpp +++ b/src/net.cpp | |||
| @@ -954,8 +954,27 @@ void ThreadSocketHandler2(void* parg) | |||
| 954 | vector<CNode*> vNodesCopy = vNodes; | 954 | vector<CNode*> vNodesCopy = vNodes; |
| 955 | foreach(CNode* pnode, vNodesCopy) | 955 | foreach(CNode* pnode, vNodesCopy) |
| 956 | { | 956 | { |
| 957 | if (pnode->ReadyToDisconnect() && pnode->vRecv.empty() && pnode->vSend.empty()) | 957 | // Wait for the buffers to drain before letting a node go, so a |
| 958 | // last message still gets out -- but not forever. vSend never | ||
| 959 | // drains through a socket whose far end is gone, and a node | ||
| 960 | // stuck that way stayed in vNodes for the life of the process, | ||
| 961 | // holding its socket and counting as a peer. | ||
| 962 | if (!pnode->ReadyToDisconnect()) | ||
| 963 | pnode->nDisconnectSince = 0; | ||
| 964 | else if (pnode->nDisconnectSince == 0) | ||
| 965 | pnode->nDisconnectSince = GetTime(); | ||
| 966 | |||
| 967 | bool fDrained = pnode->vRecv.empty() && pnode->vSend.empty(); | ||
| 968 | bool fStuck = pnode->nDisconnectSince != 0 && | ||
| 969 | GetTime() - pnode->nDisconnectSince > DISCONNECT_DRAIN_SECS; | ||
| 970 | |||
| 971 | if (pnode->ReadyToDisconnect() && (fDrained || fStuck)) | ||
| 958 | { | 972 | { |
| 973 | if (!fDrained) | ||
| 974 | printf("dropping stuck node %s after %d s with %d bytes unsent\n", | ||
| 975 | pnode->addr.ToString().c_str(), | ||
| 976 | (int)(GetTime() - pnode->nDisconnectSince), | ||
| 977 | (int)pnode->vSend.size()); | ||
| 959 | // remove from vNodes | 978 | // remove from vNodes |
| 960 | vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end()); | 979 | vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end()); |
| 961 | pnode->Disconnect(); | 980 | pnode->Disconnect(); |
| @@ -1010,16 +1029,35 @@ void ThreadSocketHandler2(void* parg) | |||
| 1010 | SOCKET hSocketMax = 0; | 1029 | SOCKET hSocketMax = 0; |
| 1011 | FD_SET(hListenSocket, &fdsetRecv); | 1030 | FD_SET(hListenSocket, &fdsetRecv); |
| 1012 | hSocketMax = max(hSocketMax, hListenSocket); | 1031 | hSocketMax = max(hSocketMax, hListenSocket); |
| 1032 | // FD_SET drops silently once the set is full, so count what goes in | ||
| 1033 | // and say so. A peer that never makes it into the set is never read, | ||
| 1034 | // and that is indistinguishable from a peer with nothing to say -- | ||
| 1035 | // the failure this whole path used to hide. | ||
| 1036 | unsigned int nWatched = 1; // the listen socket | ||
| 1013 | CRITICAL_BLOCK(cs_vNodes) | 1037 | CRITICAL_BLOCK(cs_vNodes) |
| 1014 | { | 1038 | { |
| 1015 | foreach(CNode* pnode, vNodes) | 1039 | foreach(CNode* pnode, vNodes) |
| 1016 | { | 1040 | { |
| 1041 | if (nWatched >= FD_SETSIZE) | ||
| 1042 | break; | ||
| 1017 | FD_SET(pnode->hSocket, &fdsetRecv); | 1043 | FD_SET(pnode->hSocket, &fdsetRecv); |
| 1044 | nWatched++; | ||
| 1018 | hSocketMax = max(hSocketMax, pnode->hSocket); | 1045 | hSocketMax = max(hSocketMax, pnode->hSocket); |
| 1019 | TRY_CRITICAL_BLOCK(pnode->cs_vSend) | 1046 | TRY_CRITICAL_BLOCK(pnode->cs_vSend) |
| 1020 | if (!pnode->vSend.empty()) | 1047 | if (!pnode->vSend.empty()) |
| 1021 | FD_SET(pnode->hSocket, &fdsetSend); | 1048 | FD_SET(pnode->hSocket, &fdsetSend); |
| 1022 | } | 1049 | } |
| 1050 | if (vNodes.size() + 1 > (size_t)FD_SETSIZE) | ||
| 1051 | { | ||
| 1052 | static int64 nLastWarned = 0; | ||
| 1053 | if (GetTime() - nLastWarned > 60) | ||
| 1054 | { | ||
| 1055 | nLastWarned = GetTime(); | ||
| 1056 | printf("WARNING: %d peers but select() can only watch %d -- %d are not being read\n", | ||
| 1057 | (int)vNodes.size(), (int)FD_SETSIZE - 1, | ||
| 1058 | (int)vNodes.size() - (int)FD_SETSIZE + 1); | ||
| 1059 | } | ||
| 1060 | } | ||
| 1023 | } | 1061 | } |
| 1024 | 1062 | ||
| 1025 | vfThreadRunning[0] = false; | 1063 | vfThreadRunning[0] = false; |
| @@ -1028,12 +1066,37 @@ void ThreadSocketHandler2(void* parg) | |||
| 1028 | CheckForShutdown(0); | 1066 | CheckForShutdown(0); |
| 1029 | if (nSelect == SOCKET_ERROR) | 1067 | if (nSelect == SOCKET_ERROR) |
| 1030 | { | 1068 | { |
| 1069 | // This used to answer a failed select() by marking every | ||
| 1070 | // descriptor from 0 to hSocketMax ready, which is code written for | ||
| 1071 | // POSIX, where a descriptor is a small integer. A Windows SOCKET is | ||
| 1072 | // a kernel handle in the thousands, so the loop iterated over | ||
| 1073 | // numbers that are not sockets and told the rest of the function | ||
| 1074 | // that every peer had data. One bad handle in the set makes | ||
| 1075 | // select() fail on every pass, so a single closed socket put the | ||
| 1076 | // whole thread into that state permanently. | ||
| 1077 | // | ||
| 1078 | // Find the bad handle instead and let it be dropped. | ||
| 1031 | int nErr = WSAGetLastError(); | 1079 | int nErr = WSAGetLastError(); |
| 1032 | LogPrint("net", "select failed: %d\n", nErr); | 1080 | LogPrint("net", "select failed: %d\n", nErr); |
| 1033 | for (int i = 0; i <= hSocketMax; i++) | 1081 | FD_ZERO(&fdsetRecv); |
| 1082 | FD_ZERO(&fdsetSend); | ||
| 1083 | CRITICAL_BLOCK(cs_vNodes) | ||
| 1034 | { | 1084 | { |
| 1035 | FD_SET(i, &fdsetRecv); | 1085 | foreach(CNode* pnode, vNodes) |
| 1036 | FD_SET(i, &fdsetSend); | 1086 | { |
| 1087 | int nType = 0; | ||
| 1088 | #ifdef _WIN32 | ||
| 1089 | int nTypeLen = sizeof(nType); | ||
| 1090 | #else | ||
| 1091 | socklen_t nTypeLen = sizeof(nType); | ||
| 1092 | #endif | ||
| 1093 | if (getsockopt(pnode->hSocket, SOL_SOCKET, SO_TYPE, (char*)&nType, &nTypeLen) != 0) | ||
| 1094 | { | ||
| 1095 | printf("dropping node %s: its socket is no longer valid\n", | ||
| 1096 | pnode->addr.ToString().c_str()); | ||
| 1097 | pnode->fDisconnect = true; | ||
| 1098 | } | ||
| 1099 | } | ||
| 1037 | } | 1100 | } |
| 1038 | Sleep(timeout.tv_usec/1000); | 1101 | Sleep(timeout.tv_usec/1000); |
| 1039 | } | 1102 | } |
| @@ -1068,11 +1131,28 @@ void ThreadSocketHandler2(void* parg) | |||
| 1068 | } | 1131 | } |
| 1069 | else | 1132 | else |
| 1070 | { | 1133 | { |
| 1071 | LogPrint("net", "accepted connection from %s\n", addr.ToString().c_str()); | 1134 | // Refuse rather than accept a connection this node cannot |
| 1072 | CNode* pnode = new CNode(hSocket, addr, true); | 1135 | // watch. Without this the listen socket kept taking peers -- |
| 1073 | pnode->AddRef(); | 1136 | // it is always in the select set -- while everything past the |
| 1137 | // limit sat open and unread, which fed on itself: the deafer | ||
| 1138 | // the node got, the more connections it collected. | ||
| 1139 | unsigned int nNodes = 0; | ||
| 1074 | CRITICAL_BLOCK(cs_vNodes) | 1140 | CRITICAL_BLOCK(cs_vNodes) |
| 1075 | vNodes.push_back(pnode); | 1141 | nNodes = (unsigned int)vNodes.size(); |
| 1142 | if (nNodes >= MAX_CONNECTIONS) | ||
| 1143 | { | ||
| 1144 | LogPrint("net", "refusing connection from %s, already at %u\n", | ||
| 1145 | addr.ToString().c_str(), nNodes); | ||
| 1146 | closesocket(hSocket); | ||
| 1147 | } | ||
| 1148 | else | ||
| 1149 | { | ||
| 1150 | LogPrint("net", "accepted connection from %s\n", addr.ToString().c_str()); | ||
| 1151 | CNode* pnode = new CNode(hSocket, addr, true); | ||
| 1152 | pnode->AddRef(); | ||
| 1153 | CRITICAL_BLOCK(cs_vNodes) | ||
| 1154 | vNodes.push_back(pnode); | ||
| 1155 | } | ||
| 1076 | } | 1156 | } |
| 1077 | } | 1157 | } |
| 1078 | 1158 | ||