From 43f40569d4df0b2d1a9b2b7e09c71c49de46fa8b Mon Sep 17 00:00:00 2001 From: Vladimir Dubrovin <3proxy@3proxy.ru> Date: Sun, 25 Oct 2026 01:00:48 +0300 Subject: [PATCH] Sockets closed without SO_LINGER Client, outgoing and FTP data sockets, and the inetd socket of a standalone service, were given SO_LINGER, the client's for the long string timeout (60 seconds by default), the outgoing one's for the LINGER timeout. It kept nothing from being lost: without it the system sends what is queued after the close all the same, and the time running out reset the connection, losing what was still queued. All it did was make close() wait for the peer to acknowledge the data, holding the connection's thread and maxconn slot meanwhile, and, for clients served on an event loop, every client of the loop: with one loop it was idle a quarter of the time in a benchmark of many short exchanges. The LINGER timeout keeps its use: how long a half-closed connection waits for the rest of its data, and how long httpsrv reads what is left of a request before closing. Co-Authored-By: Claude Opus 5.5 --- man/3proxy.cfg.5 | 8 +++++--- src/common.c | 7 ------- src/ftppr.c | 12 ------------ src/mainclient.c | 3 +-- src/maintcp.c | 16 +++++----------- src/proxymain.c | 4 ---- 6 files changed, 11 insertions(+), 39 deletions(-) diff --git a/man/3proxy.cfg.5 b/man/3proxy.cfg.5 index dee85e2..5e49164 100644 --- a/man/3proxy.cfg.5 +++ b/man/3proxy.cfg.5 @@ -526,9 +526,11 @@ can use %A as produced archive name and %F as filename. \fBCHAIN\fR timeout for reading data from chained connection .br \fBLINGER\fR timeout to deliver buffered data after one of the sides has closed -its sending side, also used as SO_LINGER value on outgoing connections. Too small -value may cause the tail of the data to be lost on slow connections, too large one -delays release of the sockets. +its sending side, and for httpsrv to read what is left of a request before it +closes the connection. Too small value may cause the tail of the data to be +lost on slow connections, too large one delays release of the sockets. +Sockets are closed without SO_LINGER: the system sends what is queued after +the close, the connection's thread and slot freed at once. .br default timeouts 1 5 30 60 180 1800 15 60 15 5 5 .br diff --git a/src/common.c b/src/common.c index a1f2fd0..666af55 100644 --- a/src/common.c +++ b/src/common.c @@ -789,9 +789,6 @@ int doconnect(struct clientparam * param){ if(param->srv->so._getpeername(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsr, &size)==-1) {return (14);} } else { -#ifndef _WIN32 - struct linger lg = {1,param->srv->timeouts[LINGER_TO]}; -#endif int tries = RANGETRIES; if(SAISNULL(¶m->sinsr)){ @@ -823,10 +820,6 @@ int doconnect(struct clientparam * param){ *SAPORT(¶m->sinsl) = 0; setopts(param->remsock, ¶m->srv->srvsockopts, param->srv->maxseg); -#ifndef _WIN32 - /* not on Windows, see the accepting side in proxymain.c */ - param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg)); -#endif if(extbindtodevice(param, param->remsock)) return 12; #ifdef WITH_UN if(*SAFAMILY(¶m->sinsl) != AF_UNIX) diff --git a/src/ftppr.c b/src/ftppr.c index 181ae76..faaa2d3 100644 --- a/src/ftppr.c +++ b/src/ftppr.c @@ -23,18 +23,11 @@ void * ftpprchild(struct clientparam* param) { SOCKET sc = INVALID_SOCKET, ss = INVALID_SOCKET, clidatasock = INVALID_SOCKET; SASIZETYPE sasize; char * req = NULL; -#ifndef _WIN32 - struct linger lg; -#endif struct pollfd fds; buf = param->tmpbuf; /* a filter may replace it with one of its own */ param->ctrlsock = param->clisock; param->operation = CONNECT; -#ifndef _WIN32 - lg.l_onoff = 1; - lg.l_linger = param->srv->timeouts[STRING_L]; -#endif if(socksend(param, param->ctrlsock, (unsigned char *)"220 Ready\r\n", 11, param->srv->timeouts[STRING_S])!=11) {RETURN (801);} for(;;){ i = sockgetlinebuf(param, CLIENT, buf, BUFSIZE - 10, '\n', param->srv->timeouts[CONNECTION_S]); @@ -269,11 +262,6 @@ void * ftpprchild(struct clientparam* param) { } sc = param->remsock; param->remsock = ss; -#ifndef _WIN32 - /* not on Windows, see the accepting side in proxymain.c */ - param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg)); - param->srv->so._setsockopt(param->sostate, clidatasock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg)); -#endif param->clisock = clidatasock; res = mapsocket(param, param->srv->timeouts[CONNECTION_S]); if(param->remsock != INVALID_SOCKET) { diff --git a/src/mainclient.c b/src/mainclient.c index d029d80..47ea0f5 100644 --- a/src/mainclient.c +++ b/src/mainclient.c @@ -447,8 +447,7 @@ void releaseparam(struct clientparam * param) { /* the sockets were closed and the filters cleared with it */ param->sostate = NULL; /* Leaving the service comes last. Once its last child is gone, srvfree() - frees the service along with its socket functions, filters and mutex, - and a close above may have lingered for seconds before getting here. */ + frees the service along with its socket functions, filters and mutex. */ if(param->srv){ _3proxy_mutex_lock(¶m->srv->counter_mutex); if(param->prev || param->next || param->srv->child == param){ diff --git a/src/maintcp.c b/src/maintcp.c index cd9d839..d9925e5 100644 --- a/src/maintcp.c +++ b/src/maintcp.c @@ -55,9 +55,7 @@ static int tcpnext(struct srvstart *s, SOCKET *client, int *nlog){ SASIZETYPE size; unsigned char buf[256]; int error = 0, opt = 1, res, waited; -#ifndef _WIN32 - struct linger lg; -#else +#ifdef _WIN32 unsigned long ul = 1; #endif @@ -166,14 +164,10 @@ static int tcpnext(struct srvstart *s, SOCKET *client, int *nlog){ #else fcntl(new_sock,F_SETFL,O_NONBLOCK | fcntl(new_sock,F_GETFL)); #endif -#ifndef _WIN32 - /* Not on Windows: there closesocket() on a non-blocking socket with a linger - timeout fails with WSAEWOULDBLOCK while data is queued, leaving the socket - open. Without it the close completes in the background, data first. */ - lg.l_onoff = 1; - lg.l_linger = srv->timeouts[STRING_L]; - srv->so._setsockopt(srv->so.state, new_sock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg)); -#endif +/* No linger: a close completes in the background, what is queued sent + first; waiting for the peer to acknowledge it would only hold the thread + (or the event loop) and the slot, and its time running out would reset + the connection, losing what was still queued. */ srv->so._setsockopt(srv->so.state, new_sock, SOL_SOCKET, SO_OOBINLINE, (char *)&opt, sizeof(int)); *client = new_sock; return NEXT_CLIENT; diff --git a/src/proxymain.c b/src/proxymain.c index 30f90c8..4cdeb83 100644 --- a/src/proxymain.c +++ b/src/proxymain.c @@ -215,7 +215,6 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int int res; #ifndef _WIN32 int opt = 1; - struct linger lg; struct clientparam *newparam; #endif @@ -255,9 +254,6 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int if(s.inetd) { fcntl(0,F_SETFL,O_NONBLOCK | fcntl(0,F_GETFL)); if(!s.isudp){ - lg.l_onoff = 1; - lg.l_linger = s.srv.timeouts[STRING_L]; - s.srv.so._setsockopt(s.srv.so.state, 0, SOL_SOCKET, SO_LINGER, (unsigned char *)&lg, sizeof(lg)); s.srv.so._setsockopt(s.srv.so.state, 0, SOL_SOCKET, SO_OOBINLINE, (unsigned char *)&opt, sizeof(int)); } s.defparam.clisock = 0;