connect back: a replaced -r service stops waiting at the reload

A -r service waited up to 60 s for the go-ahead on the connection it
made, without looking at a reload. One replaced by a reload went on
waiting, holding a slot and a thread, and took a client if the far
side's go-ahead came in that time; every reload left another such
connection in the far side's queue. The wait is now a second at a time
(60 s in all, as before): at a reload the service takes a go-ahead
already there, its client going on in the service which replaced it,
and otherwise closes the connection and stops.

The far side (-R) sent its go-ahead to any connection it accepted, and
a closed one cost it a client. A connection the -r side closed, at a
reload or after its wait, is readable before any go-ahead, since that
side sends nothing first: once past the ACL it is passed over, without
counting as one of the tries for as many as the listen queue holds
(1 + maxchild/16), so that connections made to be closed or to send
something cannot keep a client's thread in the loop.

Tests: reattach's far side passes over closed connections as -R does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-19 16:22:17 +03:00
parent 63a736c693
commit 8cc7d255af
3 changed files with 44 additions and 3 deletions

View File

@ -41,6 +41,8 @@ static void serveparam(struct clientparam *param){
if(param->srv->cbsock != INVALID_SOCKET){
SASIZETYPE size = sizeof(param->sinsr);
struct pollfd fds;
/* closed connections passed over free: the listen queue's length */
int skips = 1 + (param->srv->maxchild>>4);
fds.fd = param->srv->cbsock;
fds.events = POLLIN;
fds.revents = 0;
@ -73,6 +75,24 @@ static void serveparam(struct clientparam *param){
continue;
}
#endif
{
/* the side connecting back sends nothing before the go-ahead:
readable, the connection was closed there, by a service
which stopped waiting (a reload, its time out); the next
one is taken, and this one does not count as a try, for as
many as the queue holds: past that it does */
struct pollfd rf;
rf.fd = param->remsock;
rf.events = POLLIN;
rf.revents = 0;
if(param->srv->so._poll(param->sostate, &rf, 1, 0) == 1){
param->srv->so._closesocket(param->sostate, param->remsock);
param->remsock = INVALID_SOCKET;
if(skips > 0){ skips--; i++; }
continue;
}
}
if(socksend(param, param->remsock, (unsigned char *)"C", 1, CONNBACK_TO) != 1){
dolog(param, (unsigned char *)"Connect back sending command failed");
param->srv->so._closesocket(param->sostate, param->remsock);

View File

@ -37,6 +37,9 @@ static int cblisten(struct srvstart *s){
#endif
/* How long, in seconds, a connection made back (-r) waits for its go-ahead */
#define CBWAIT 60
/* What getting the next client came to. */
enum { NEXT_CLIENT, NEXT_RETRY, NEXT_STOP };
@ -49,7 +52,7 @@ static int tcpnext(struct srvstart *s, SOCKET *client, int *nlog){
SOCKET new_sock;
SASIZETYPE size;
unsigned char buf[256];
int error = 0, opt = 1;
int error = 0, opt = 1, res, waited;
#ifndef _WIN32
struct linger lg;
#else
@ -81,7 +84,18 @@ static int tcpnext(struct srvstart *s, SOCKET *client, int *nlog){
return NEXT_RETRY;
}
if(sockrecvfrom(NULL, new_sock, NULL, buf, 1, 60*1000) != 1 || *buf!='C') {
/* the far side's go-ahead, waited for a second at a time to notice
a reload: a service paused then takes no more connections and
closes this one, unless the go-ahead came already, and its client
goes on in the service which took its place */
for(waited = 0; !(res = sockrecvfrom(NULL, new_sock, NULL, buf, 1, 1000)); ){
if(srv->config->paused != srv->paused){
res = sockrecvfrom(NULL, new_sock, NULL, buf, 1, 0);
break;
}
if(++waited >= CBWAIT) break;
}
if(res != 1 || *buf!='C') {
so._closesocket(so.state, new_sock);
usleep(SLEEPTIME);
return NEXT_RETRY;

View File

@ -3,6 +3,7 @@ the rules of the newest service on its listener, taking one of its maxconn
slots, and is dropped where that cannot be."""
import base64
import select
import socket
import threading
import time
@ -128,7 +129,13 @@ def run(t):
"""A request over a connection the -r service made: the peer is its
client once it has sent C."""
try:
c, _ = peer.accept()
# as the far side of a connect-back service does: a connection
# closed by a service which stopped waiting is passed over
while True:
c, _ = peer.accept()
if not select.select([c], [], [], 0)[0]:
break
c.close()
c.settimeout(30)
c.sendall(b"C")
auth = base64.b64encode(b"alice:one").decode()