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 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-25 01:00:48 +03:00
parent c55f736a0d
commit 43f40569d4
6 changed files with 11 additions and 39 deletions

View File

@ -526,9 +526,11 @@ can use %A as produced archive name and %F as filename.
\fBCHAIN\fR timeout for reading data from chained connection \fBCHAIN\fR timeout for reading data from chained connection
.br .br
\fBLINGER\fR timeout to deliver buffered data after one of the sides has closed \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 its sending side, and for httpsrv to read what is left of a request before it
value may cause the tail of the data to be lost on slow connections, too large one closes the connection. Too small value may cause the tail of the data to be
delays release of the sockets. 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 .br
default timeouts 1 5 30 60 180 1800 15 60 15 5 5 default timeouts 1 5 30 60 180 1800 15 60 15 5 5
.br .br

View File

@ -789,9 +789,6 @@ int doconnect(struct clientparam * param){
if(param->srv->so._getpeername(param->sostate, param->remsock, (struct sockaddr *)&param->sinsr, &size)==-1) {return (14);} if(param->srv->so._getpeername(param->sostate, param->remsock, (struct sockaddr *)&param->sinsr, &size)==-1) {return (14);}
} }
else { else {
#ifndef _WIN32
struct linger lg = {1,param->srv->timeouts[LINGER_TO]};
#endif
int tries = RANGETRIES; int tries = RANGETRIES;
if(SAISNULL(&param->sinsr)){ if(SAISNULL(&param->sinsr)){
@ -823,10 +820,6 @@ int doconnect(struct clientparam * param){
*SAPORT(&param->sinsl) = 0; *SAPORT(&param->sinsl) = 0;
setopts(param->remsock, &param->srv->srvsockopts, param->srv->maxseg); setopts(param->remsock, &param->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; if(extbindtodevice(param, param->remsock)) return 12;
#ifdef WITH_UN #ifdef WITH_UN
if(*SAFAMILY(&param->sinsl) != AF_UNIX) if(*SAFAMILY(&param->sinsl) != AF_UNIX)

View File

@ -23,18 +23,11 @@ void * ftpprchild(struct clientparam* param) {
SOCKET sc = INVALID_SOCKET, ss = INVALID_SOCKET, clidatasock = INVALID_SOCKET; SOCKET sc = INVALID_SOCKET, ss = INVALID_SOCKET, clidatasock = INVALID_SOCKET;
SASIZETYPE sasize; SASIZETYPE sasize;
char * req = NULL; char * req = NULL;
#ifndef _WIN32
struct linger lg;
#endif
struct pollfd fds; struct pollfd fds;
buf = param->tmpbuf; /* a filter may replace it with one of its own */ buf = param->tmpbuf; /* a filter may replace it with one of its own */
param->ctrlsock = param->clisock; param->ctrlsock = param->clisock;
param->operation = CONNECT; 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);} if(socksend(param, param->ctrlsock, (unsigned char *)"220 Ready\r\n", 11, param->srv->timeouts[STRING_S])!=11) {RETURN (801);}
for(;;){ for(;;){
i = sockgetlinebuf(param, CLIENT, buf, BUFSIZE - 10, '\n', param->srv->timeouts[CONNECTION_S]); 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; sc = param->remsock;
param->remsock = ss; 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; param->clisock = clidatasock;
res = mapsocket(param, param->srv->timeouts[CONNECTION_S]); res = mapsocket(param, param->srv->timeouts[CONNECTION_S]);
if(param->remsock != INVALID_SOCKET) { if(param->remsock != INVALID_SOCKET) {

View File

@ -447,8 +447,7 @@ void releaseparam(struct clientparam * param) {
/* the sockets were closed and the filters cleared with it */ /* the sockets were closed and the filters cleared with it */
param->sostate = NULL; param->sostate = NULL;
/* Leaving the service comes last. Once its last child is gone, srvfree() /* Leaving the service comes last. Once its last child is gone, srvfree()
frees the service along with its socket functions, filters and mutex, frees the service along with its socket functions, filters and mutex. */
and a close above may have lingered for seconds before getting here. */
if(param->srv){ if(param->srv){
_3proxy_mutex_lock(&param->srv->counter_mutex); _3proxy_mutex_lock(&param->srv->counter_mutex);
if(param->prev || param->next || param->srv->child == param){ if(param->prev || param->next || param->srv->child == param){

View File

@ -55,9 +55,7 @@ static int tcpnext(struct srvstart *s, SOCKET *client, int *nlog){
SASIZETYPE size; SASIZETYPE size;
unsigned char buf[256]; unsigned char buf[256];
int error = 0, opt = 1, res, waited; int error = 0, opt = 1, res, waited;
#ifndef _WIN32 #ifdef _WIN32
struct linger lg;
#else
unsigned long ul = 1; unsigned long ul = 1;
#endif #endif
@ -166,14 +164,10 @@ static int tcpnext(struct srvstart *s, SOCKET *client, int *nlog){
#else #else
fcntl(new_sock,F_SETFL,O_NONBLOCK | fcntl(new_sock,F_GETFL)); fcntl(new_sock,F_SETFL,O_NONBLOCK | fcntl(new_sock,F_GETFL));
#endif #endif
#ifndef _WIN32 /* No linger: a close completes in the background, what is queued sent
/* Not on Windows: there closesocket() on a non-blocking socket with a linger first; waiting for the peer to acknowledge it would only hold the thread
timeout fails with WSAEWOULDBLOCK while data is queued, leaving the socket (or the event loop) and the slot, and its time running out would reset
open. Without it the close completes in the background, data first. */ the connection, losing what was still queued. */
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
srv->so._setsockopt(srv->so.state, new_sock, SOL_SOCKET, SO_OOBINLINE, (char *)&opt, sizeof(int)); srv->so._setsockopt(srv->so.state, new_sock, SOL_SOCKET, SO_OOBINLINE, (char *)&opt, sizeof(int));
*client = new_sock; *client = new_sock;
return NEXT_CLIENT; return NEXT_CLIENT;

View File

@ -215,7 +215,6 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
int res; int res;
#ifndef _WIN32 #ifndef _WIN32
int opt = 1; int opt = 1;
struct linger lg;
struct clientparam *newparam; struct clientparam *newparam;
#endif #endif
@ -255,9 +254,6 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
if(s.inetd) { if(s.inetd) {
fcntl(0,F_SETFL,O_NONBLOCK | fcntl(0,F_GETFL)); fcntl(0,F_SETFL,O_NONBLOCK | fcntl(0,F_GETFL));
if(!s.isudp){ 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.srv.so._setsockopt(s.srv.so.state, 0, SOL_SOCKET, SO_OOBINLINE, (unsigned char *)&opt, sizeof(int));
} }
s.defparam.clisock = 0; s.defparam.clisock = 0;