Reattach: a new service's slot for its next client taken before it is found

A TCP service keeps one of its maxconn slots for the next client it
accepts, but took it only once its thread reached the accept loop. A
reload puts the new service on its listener (mainclaim(), in the thread
reading the configuration) before its thread even starts, and the
clients of the old services move to it as soon as they see it there: in
that window they could take every slot, the one it keeps included. The
reattach test, with maxconn 2, then had both of two downloads go on
instead of one, now and then on a loaded runner, every time with a
300 ms sleep put in the window.

mainclaim() takes the slot first; srvrun() takes it over, and
srvdiscard() gives it back for a service which never ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-23 20:00:29 +03:00
parent 2052dc0a4f
commit 7fab9c488d
3 changed files with 17 additions and 1 deletions

View File

@ -152,6 +152,11 @@ static void srvstrings(struct srvstart *s){
/* Releases what the preparation and the run took, s itself excepted. */ /* Releases what the preparation and the run took, s itself excepted. */
static void srvdiscard(struct srvstart *s){ static void srvdiscard(struct srvstart *s){
/* a slot taken for a service which never ran: srvfree() waits for all */
if(s->haveslot){
_3proxy_sem_unlock(s->srv.childslots);
s->haveslot = 0;
}
srvfree(&s->srv); srvfree(&s->srv);
srvstrings(s); srvstrings(s);
} }
@ -245,6 +250,7 @@ static int srvprepare(struct srvstart *s, int argc, char **argv){
s->cbc_string = s->cbl_string = NULL; s->cbc_string = s->cbl_string = NULL;
s->iscbc = s->iscbl = 0; s->iscbc = s->iscbl = 0;
s->haveslot = 0;
#ifdef STDMAIN #ifdef STDMAIN
#ifndef _WIN32 #ifndef _WIN32
s->inetd = 0; s->inetd = 0;

View File

@ -374,6 +374,11 @@ void mainclaim(void *service){
struct srvparam *srv = &s->srv; struct srvparam *srv = &s->srv;
struct listener *l, *found = NULL, *newl; struct listener *l, *found = NULL, *newl;
/* The slot it keeps for its next client taken first: once on the
listener, it is where the clients of older services move after a reload
(reattach()), which must find that slot taken, as it is once the service
accepts, and not take more than the others of its maxconn. */
if(!s->isudp && !s->haveslot && !_3proxy_sem_timedlock(srv->childslots, 0)) s->haveslot = 1;
/* made beforehand, as the mutex is held from finding a listener to /* made beforehand, as the mutex is held from finding a listener to
joining it, lest its last service let go of it meanwhile */ joining it, lest its last service let go of it meanwhile */
if((newl = malloc(sizeof(*newl)))){ if((newl = malloc(sizeof(*newl)))){

View File

@ -26,6 +26,9 @@ struct srvstart {
struct clientparam defparam; struct clientparam defparam;
unsigned char *cbc_string, *cbl_string; unsigned char *cbc_string, *cbl_string;
int isudp, iscbc, iscbl; int isudp, iscbc, iscbl;
/* the slot a TCP service keeps for its next client, taken before it can
be found on its listener: srvrun() takes it over */
int haveslot;
const struct udpservice *udp; const struct udpservice *udp;
#ifndef _WIN32 #ifndef _WIN32
pthread_attr_t pa; pthread_attr_t pa;
@ -121,9 +124,11 @@ static int srvrun(struct srvstart *s);
static int srvrun(struct srvstart *s){ static int srvrun(struct srvstart *s){
struct srvparam *srv = &s->srv; struct srvparam *srv = &s->srv;
struct clientparam *defparam = &s->defparam; struct clientparam *defparam = &s->defparam;
int res = 0, haveslot = 0; int res = 0, haveslot = s->haveslot;
/* -r: a TCP service connects out for each client instead of listening */ /* -r: a TCP service connects out for each client instead of listening */
int connectback = !s->isudp && s->iscbc; int connectback = !s->isudp && s->iscbc;
s->haveslot = 0;
#ifndef _WIN32 #ifndef _WIN32
pthread_attr_init(&s->pa); pthread_attr_init(&s->pa);
pthread_attr_setstacksize(&s->pa,threadstacksize(srv->stacksize)); pthread_attr_setstacksize(&s->pa,threadstacksize(srv->stacksize));