diff options
| author | Bitflash <btf01@proton.me> | 2026-08-01 18:43:25 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-01 18:43:25 -0300 |
| commit | 2ffc50998c30f285f45c5c5d81975bf857885aa8 (patch) | |
| tree | 9fe6be3a0dd7a8f869c88f9c1dd13e75fa8e7b78 /src | |
| parent | 19ecd6ad61b560d92d82ed5a79b5217d64f97327 (diff) | |
diag: say why a close failed, and total what the program thinks it holds (#98)
The 1.2.11 accounting answered 'which site' and stopped. On a node with 3h51m of uptime it reported 58 live sockets while Windows attributed 112 to the process, 47 of them bound and never connected, against 55 closes that had returned an error -- with nothing recording what those errors were. Close failures are now counted per code per site, and the table totals, so the live figure can be compared directly against what the OS attributes to the process. Instrumentation only.
Diffstat (limited to 'src')
| -rw-r--r-- | src/net.cpp | 29 | ||||
| -rw-r--r-- | src/sockcount.h | 23 |
2 files changed, 52 insertions, 0 deletions
diff --git a/src/net.cpp b/src/net.cpp index 7a13257..4fe5f15 100644 --- a/src/net.cpp +++ b/src/net.cpp | |||
| @@ -66,8 +66,12 @@ static string SockAccountingText() | |||
| 66 | 66 | ||
| 67 | string str = "\n sockets by where they were created\n"; | 67 | string str = "\n sockets by where they were created\n"; |
| 68 | str += " site opened closed live close failed\n"; | 68 | str += " site opened closed live close failed\n"; |
| 69 | long long nTotalOpened = 0, nTotalClosed = 0, nTotalFailed = 0; | ||
| 69 | for (int i = 0; i < SOCK_SITES; i++) | 70 | for (int i = 0; i < SOCK_SITES; i++) |
| 70 | { | 71 | { |
| 72 | nTotalOpened += nOpened[i]; | ||
| 73 | nTotalClosed += nClosed[i]; | ||
| 74 | nTotalFailed += nFailed[i]; | ||
| 71 | if (nOpened[i] == 0) | 75 | if (nOpened[i] == 0) |
| 72 | continue; | 76 | continue; |
| 73 | str += strprintf(" %-24s %8lld %8lld %8lld %13lld\n", pszSockSite[i], | 77 | str += strprintf(" %-24s %8lld %8lld %8lld %13lld\n", pszSockSite[i], |
| @@ -75,6 +79,31 @@ static string SockAccountingText() | |||
| 75 | } | 79 | } |
| 76 | str += strprintf(" %-24s %8s %8lld %8s %13s\n", "closed but never tagged", | 80 | str += strprintf(" %-24s %8s %8lld %8s %13s\n", "closed but never tagged", |
| 77 | "-", nUntagged, "-", "-"); | 81 | "-", nUntagged, "-", "-"); |
| 82 | str += strprintf(" %-24s %8lld %8lld %8lld %13lld\n", "TOTAL", | ||
| 83 | nTotalOpened, nTotalClosed, nTotalOpened - nTotalClosed, nTotalFailed); | ||
| 84 | |||
| 85 | // The live total above is what this program believes it is holding. Compare | ||
| 86 | // it against what the operating system attributes to the process: on | ||
| 87 | // Windows, `Get-NetTCPConnection | ? OwningProcess -eq <pid>`. A gap there | ||
| 88 | // is the leak, and the codes below say what the failed closes hit -- 10038 | ||
| 89 | // (WSAENOTSOCK) means the handle was already gone and this close was a | ||
| 90 | // second one, which is a different bug from a descriptor that stayed. | ||
| 91 | if (nTotalFailed > 0) | ||
| 92 | { | ||
| 93 | str += " close failures by code\n"; | ||
| 94 | for (int i = 0; i < SOCK_SITES; i++) | ||
| 95 | { | ||
| 96 | if (nFailed[i] == 0) | ||
| 97 | continue; | ||
| 98 | map<int, long long> mapErr; | ||
| 99 | BtfSockCloseErrors(i, mapErr); | ||
| 100 | string strCodes; | ||
| 101 | for (map<int, long long>::const_iterator mi = mapErr.begin(); mi != mapErr.end(); ++mi) | ||
| 102 | strCodes += strprintf("%s%d x%lld", strCodes.empty() ? "" : ", ", | ||
| 103 | mi->first, mi->second); | ||
| 104 | str += strprintf(" %-24s %s\n", pszSockSite[i], strCodes.c_str()); | ||
| 105 | } | ||
| 106 | } | ||
| 78 | return str; | 107 | return str; |
| 79 | } | 108 | } |
| 80 | 109 | ||
diff --git a/src/sockcount.h b/src/sockcount.h index 19f0827..a42abb9 100644 --- a/src/sockcount.h +++ b/src/sockcount.h | |||
| @@ -23,6 +23,7 @@ | |||
| 23 | #ifndef BITFLASH_SOCKCOUNT_H | 23 | #ifndef BITFLASH_SOCKCOUNT_H |
| 24 | #define BITFLASH_SOCKCOUNT_H | 24 | #define BITFLASH_SOCKCOUNT_H |
| 25 | 25 | ||
| 26 | #include <cerrno> | ||
| 26 | #include <map> | 27 | #include <map> |
| 27 | #include <mutex> | 28 | #include <mutex> |
| 28 | 29 | ||
| @@ -52,6 +53,13 @@ struct BtfSockAccount | |||
| 52 | long long nOpened[SOCK_SITES]; | 53 | long long nOpened[SOCK_SITES]; |
| 53 | long long nClosed[SOCK_SITES]; | 54 | long long nClosed[SOCK_SITES]; |
| 54 | long long nCloseFailed[SOCK_SITES]; | 55 | long long nCloseFailed[SOCK_SITES]; |
| 56 | // Why the close failed, counted per code per site. Measured on a node with | ||
| 57 | // 3h51m of uptime: the program believed it held 58 sockets and Windows | ||
| 58 | // attributed 112 to the process, 47 of them bound to an ephemeral port and | ||
| 59 | // never connected -- against 55 closes that had returned an error. The | ||
| 60 | // shapes match, and the error code is the piece that says whether the | ||
| 61 | // descriptor is still there or was already gone. | ||
| 62 | std::map<int, long long> mapCloseErr[SOCK_SITES]; | ||
| 55 | long long nClosedUntagged; | 63 | long long nClosedUntagged; |
| 56 | BtfSockAccount() : nClosedUntagged(0) | 64 | BtfSockAccount() : nClosedUntagged(0) |
| 57 | { | 65 | { |
| @@ -102,8 +110,10 @@ inline int BtfCloseSocket(SOCKET hSocket) | |||
| 102 | } | 110 | } |
| 103 | #ifdef _WIN32 | 111 | #ifdef _WIN32 |
| 104 | int nRet = ::closesocket(hSocket); | 112 | int nRet = ::closesocket(hSocket); |
| 113 | int nErr = (nRet != 0) ? WSAGetLastError() : 0; | ||
| 105 | #else | 114 | #else |
| 106 | int nRet = ::close(hSocket); | 115 | int nRet = ::close(hSocket); |
| 116 | int nErr = (nRet != 0) ? errno : 0; | ||
| 107 | #endif | 117 | #endif |
| 108 | // A close that fails leaves the descriptor -- and on Windows the ephemeral | 118 | // A close that fails leaves the descriptor -- and on Windows the ephemeral |
| 109 | // port it holds -- in place. Counted because a socket the program believes | 119 | // port it holds -- in place. Counted because a socket the program believes |
| @@ -115,6 +125,7 @@ inline int BtfCloseSocket(SOCKET hSocket) | |||
| 115 | BtfSockAccount& a = BtfSockAccounting(); | 125 | BtfSockAccount& a = BtfSockAccounting(); |
| 116 | std::lock_guard<std::mutex> lock(a.mtx); | 126 | std::lock_guard<std::mutex> lock(a.mtx); |
| 117 | a.nCloseFailed[nSite]++; | 127 | a.nCloseFailed[nSite]++; |
| 128 | a.mapCloseErr[nSite][nErr]++; | ||
| 118 | } | 129 | } |
| 119 | return nRet; | 130 | return nRet; |
| 120 | } | 131 | } |
| @@ -134,4 +145,16 @@ inline void BtfSockSnapshot(long long* pOpened, long long* pClosed, long long* p | |||
| 134 | *pUntagged = a.nClosedUntagged; | 145 | *pUntagged = a.nClosedUntagged; |
| 135 | } | 146 | } |
| 136 | 147 | ||
| 148 | // The close-failure codes for one site, newest counts included. Separate call | ||
| 149 | // because the table above is fixed-width and this is a list of unknown length. | ||
| 150 | inline void BtfSockCloseErrors(int nSite, std::map<int, long long>& mapOut) | ||
| 151 | { | ||
| 152 | mapOut.clear(); | ||
| 153 | if (nSite < 0 || nSite >= SOCK_SITES) | ||
| 154 | return; | ||
| 155 | BtfSockAccount& a = BtfSockAccounting(); | ||
| 156 | std::lock_guard<std::mutex> lock(a.mtx); | ||
| 157 | mapOut = a.mapCloseErr[nSite]; | ||
| 158 | } | ||
| 159 | |||
| 137 | #endif | 160 | #endif |