diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-08-12 16:33:35 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-08-19 22:48:27 +0000 |
| commit | db34011b507fdacbb4511d2ae0847e4b0a8b76de (patch) | |
| tree | 30998698ba1aa2ff70afa5102ae0f4a1e03a624f | |
| parent | 6401992a1a89dc2e7ec670b6979b0d2095049e49 (diff) | |
net: one dead descriptor must not cost every peer its turn
From the first of two wedges captured on the pool. It is a different
proximate fault from the second one, and this is the part that turns a
single bad socket into a node-wide outage.
A failed select() zeroes both descriptor sets, so no peer is serviced on
that pass. That is correct as far as it goes. What was missing is that
nothing stopped the offending descriptor coming straight back:
- the invalid socket was only marked fDisconnect, and a node is not
removed from vNodes until it has drained or DISCONNECT_DRAIN_SECS has
elapsed -- and it cannot drain, because its socket is gone;
- the select set was rebuilt from vNodes with no check, so the same dead
descriptor went back in on the very next pass and failed select()
again.
Measured in the capture: 1,027 consecutive 'its socket is no longer valid'
passes across 61 seconds -- the full drain timeout -- one bad socket, and
for the whole of it the node read nothing from anybody. The peer that
caused it had 57 bytes unsent, so it took the slow path out.
Now the descriptor is forgotten as soon as getsockopt says it is not ours:
select() succeeds again on the next pass and every other peer is served
normally while the dead node ages out as before.
Deliberately forgotten rather than closed. getsockopt has just said this
descriptor is no longer ours; calling close() on it is precisely how an
unrelated socket gets closed instead, which is the fault fixed in e742d0b.
(cherry picked from commit f14c513b4e39d6203b73584f6dc01acbb5a9546e)
| -rw-r--r-- | src/net.cpp | 24 |
1 files changed, 23 insertions, 1 deletions
diff --git a/src/net.cpp b/src/net.cpp index 964097b..77f9acf 100644 --- a/src/net.cpp +++ b/src/net.cpp | |||
| @@ -1471,11 +1471,29 @@ void ThreadSocketHandler2(void* parg) | |||
| 1471 | #else | 1471 | #else |
| 1472 | socklen_t nTypeLen = sizeof(nType); | 1472 | socklen_t nTypeLen = sizeof(nType); |
| 1473 | #endif | 1473 | #endif |
| 1474 | if (getsockopt(pnode->hSocket, SOL_SOCKET, SO_TYPE, (char*)&nType, &nTypeLen) != 0) | 1474 | if (pnode->hSocket != INVALID_SOCKET && |
| 1475 | getsockopt(pnode->hSocket, SOL_SOCKET, SO_TYPE, (char*)&nType, &nTypeLen) != 0) | ||
| 1475 | { | 1476 | { |
| 1476 | printf("dropping node %s: its socket is no longer valid\n", | 1477 | printf("dropping node %s: its socket is no longer valid\n", |
| 1477 | pnode->addr.ToString().c_str()); | 1478 | pnode->addr.ToString().c_str()); |
| 1478 | pnode->fDisconnect = true; | 1479 | pnode->fDisconnect = true; |
| 1480 | |||
| 1481 | // Stop referring to it, or it goes back into the next | ||
| 1482 | // select() set and fails it again. | ||
| 1483 | // | ||
| 1484 | // Marking fDisconnect is not enough: a node is only | ||
| 1485 | // removed from vNodes once it has drained or once | ||
| 1486 | // DISCONNECT_DRAIN_SECS has passed, so one dead | ||
| 1487 | // descriptor kept failing select() for a full minute -- | ||
| 1488 | // and a failed select() zeroes both sets above, which | ||
| 1489 | // means NO peer is read on that pass. Measured on a | ||
| 1490 | // wedged pool: 1,027 consecutive passes over 61 | ||
| 1491 | // seconds, one bad socket, the entire node deaf. | ||
| 1492 | // | ||
| 1493 | // Not closed, only forgotten. getsockopt just told us | ||
| 1494 | // this descriptor is not ours any more; closing it now | ||
| 1495 | // is how an unrelated socket gets closed instead. | ||
| 1496 | pnode->hSocket = INVALID_SOCKET; | ||
| 1479 | } | 1497 | } |
| 1480 | } | 1498 | } |
| 1481 | } | 1499 | } |
| @@ -1548,6 +1566,10 @@ void ThreadSocketHandler2(void* parg) | |||
| 1548 | { | 1566 | { |
| 1549 | CheckForShutdown(0); | 1567 | CheckForShutdown(0); |
| 1550 | SOCKET hSocket = pnode->hSocket; | 1568 | SOCKET hSocket = pnode->hSocket; |
| 1569 | // Forgotten above because it was already invalid. FD_ISSET on it | ||
| 1570 | // is meaningless and recv()/send() would be operating on -1. | ||
| 1571 | if (hSocket == INVALID_SOCKET) | ||
| 1572 | continue; | ||
| 1551 | 1573 | ||
| 1552 | // | 1574 | // |
| 1553 | // Receive | 1575 | // Receive |