Add a stale/never-active session reaper and raise the listen backlog
TCPServer accepted a connection and never revisited it again once it stopped generating epoll events - a connection that completes the TCP handshake and then goes permanently silent (an incomplete TLS handshake, a port scanner/prober) sat in sessions forever, one TCPSession + one fd each. sweepStaleSessions() now drops any session that has gone 30+ seconds without a single byte of real data ever passing through onDataReceived(), run each time a new connection arrives rather than needing a dedicated timer. hasReceivedData latches permanently true on a sessions first real byte, so an ordinary connected-but-briefly-idle client is never at risk. Also raised listen()s backlog from 20 to 128, so a burst of connection attempts has more headroom before the kernel starts dropping SYNs. Verified live on zephora.barant.com: a silent connection held open was reaped within the grace window while a real concurrent connection was left untouched, with the service remaining healthy throughout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d058322cc6
commit
6483729de3
4 files changed
+103
-1
No files matched your search
+41
-1
@@ -3,6 +3,8 @@
|
||||
#include "Exception.h"
|
||||
#include "Log.h"
|
||||
#include "TCPSession.h"
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
|
||||
namespace core {
|
||||
|
||||
@@ -30,7 +32,14 @@ namespace core {
|
||||
if (bind(getDescriptor(), address.getPointer(), address.addressLength) < 0)
|
||||
throw coreutils::Exception("Error on bind to socket: " + std::to_string(errno));
|
||||
|
||||
if (listen(getDescriptor(), 20) < 0)
|
||||
// Was 20 - small enough that a burst of connection attempts
|
||||
// arriving faster than this process calls accept() (a port scan,
|
||||
// or just several real clients reconnecting around the same
|
||||
// moment) can fill the kernel's own backlog before the
|
||||
// application layer gets a chance to drain it, silently dropping
|
||||
// the overflow SYNs rather than queuing them. 128 gives real
|
||||
// bursts meaningfully more headroom; still bounded, not unlimited.
|
||||
if (listen(getDescriptor(), 128) < 0)
|
||||
throw coreutils::Exception("Error on listen to socket");
|
||||
|
||||
// See isKnownListeningSocket()'s own doc comment - registers this
|
||||
@@ -49,6 +58,10 @@ namespace core {
|
||||
|
||||
void TCPServer::connectionRequest() {
|
||||
coreutils::Log(coreutils::LOG_DEBUG_2) << "Connection request is being received on socket " << getDescriptor() << ".";
|
||||
// Outside the lock.lock()/unlock() below, not nested inside it -
|
||||
// sweepStaleSessions() takes the same `lock` itself (see its own
|
||||
// comment), and this mutex isn't recursive.
|
||||
sweepStaleSessions();
|
||||
lock.lock();
|
||||
TCPSession *session = accept();
|
||||
// The shared_ptr constructed here IS the session's real, strong
|
||||
@@ -80,6 +93,33 @@ namespace core {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
void TCPServer::sweepStaleSessions() {
|
||||
// 30 seconds is generous for completing a TCP handshake plus a
|
||||
// TLS handshake plus sending a first real message over any real
|
||||
// network - deliberately not aimed at catching anything borderline,
|
||||
// only connections that never progressed at all.
|
||||
static const std::chrono::seconds GRACE_PERIOD(30);
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
// Collect under `lock` (sessions is mutated elsewhere under the
|
||||
// same mutex - see its own doc comment), then drop the lock before
|
||||
// actually removing anything. removeFromSessionList() takes `lock`
|
||||
// itself for each call; calling it while still holding the guard
|
||||
// below would deadlock on this non-recursive mutex.
|
||||
std::vector<TCPSession *> stale;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(lock);
|
||||
for (auto &s : sessions)
|
||||
if (!s->hasReceivedData && (now - s->acceptedAt) > GRACE_PERIOD)
|
||||
stale.push_back(s.get());
|
||||
}
|
||||
|
||||
for (auto *s : stale) {
|
||||
coreutils::Log(coreutils::LOG_DEBUG_2) << "Reaping stale, never-active session " << s->getDescriptor() << ".";
|
||||
removeFromSessionList(s);
|
||||
}
|
||||
}
|
||||
|
||||
void TCPServer::receiveData(coreutils::ZString &buffer) {
|
||||
// See this override's own doc comment in TCPServer.h - the
|
||||
// listening socket always uses the plain read()/ENOTCONN detection
|
||||
|
||||
Reference in new issue
Block a user