diff --git a/src/mainclient.c b/src/mainclient.c index a23849c..e18f7de 100644 --- a/src/mainclient.c +++ b/src/mainclient.c @@ -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); diff --git a/src/maintcp.c b/src/maintcp.c index a5bf1f9..336bc71 100644 --- a/src/maintcp.c +++ b/src/maintcp.c @@ -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; diff --git a/tests/cases/reattach.py b/tests/cases/reattach.py index c340aa1..903a120 100644 --- a/tests/cases/reattach.py +++ b/tests/cases/reattach.py @@ -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()