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
.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

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);}
}
else {
#ifndef _WIN32
struct linger lg = {1,param->srv->timeouts[LINGER_TO]};
#endif
int tries = RANGETRIES;
if(SAISNULL(&param->sinsr)){
@ -823,10 +820,6 @@ int doconnect(struct clientparam * param){
*SAPORT(&param->sinsl) = 0;
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;
#ifdef WITH_UN
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;
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) {

View File

@ -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(&param->srv->counter_mutex);
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;
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;

View File

@ -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;