diff --git a/src/datatypes.c b/src/datatypes.c index 4571716..cf70981 100644 --- a/src/datatypes.c +++ b/src/datatypes.c @@ -530,6 +530,14 @@ static void * ef_server_auth(struct node * node){ return "uknown"; } +static void * ef_server_listenerclients(struct node * node){ + return listenerclients((struct srvparam *)node->value); +} + +static void * ef_server_listenerprocessed(struct node * node){ + return listenerprocessed((struct srvparam *)node->value); +} + static void * ef_server_childcount(struct node * node){ return &((struct srvparam *)node->value) -> childcount; } @@ -886,6 +894,8 @@ static struct property prop_server[] = { {"nonprintable", ef_server_nonprintable, TYPE_STRING, "non printable characters"}, {"replacement", ef_server_replacement, TYPE_CHAR, "replacement character"}, {"childcount", ef_server_childcount, TYPE_INTEGER, "number of servers connected"}, + {"listenerclients", ef_server_listenerclients, TYPE_UNSIGNED64, "clients connected to the listening socket, of its services old and new"}, + {"listenerprocessed", ef_server_listenerprocessed, TYPE_UNSIGNED64, "clients of the listening socket done with"}, {"maxchild", ef_server_maxchild, TYPE_INTEGER, "max concurrent connections"}, {"backlog", ef_server_backlog, TYPE_INTEGER, "listen backlog"}, {"needuser", ef_server_needuser, TYPE_INTEGER, "require user authentication"}, diff --git a/src/proxy.h b/src/proxy.h index 997893f..89cd6ee 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -404,6 +404,8 @@ int reattach(struct clientparam *param, int auth); /* reattach() where configurations are reloaded, NULL elsewhere */ extern int (*reattachfunc)(struct clientparam *param, int auth); int clientauth(struct clientparam *param); +uint64_t *listenerclients(struct srvparam *srv); +uint64_t *listenerprocessed(struct srvparam *srv); struct passwords * copypwl (struct passwords *pwl); void freepwl(struct passwords *pw); int copyfilter(struct filter *, struct srvparam *srv); diff --git a/src/proxymain.c b/src/proxymain.c index 8232998..2784b77 100644 --- a/src/proxymain.c +++ b/src/proxymain.c @@ -903,7 +903,37 @@ struct listener { /* held while the first of its services opens the socket, binding which may take long: the others then use what it opened */ _3proxy_mutex_t open_mutex; + /* its clients, whichever of its services, old or new, they came to: + those connected now, and those done with */ + uint64_t clients, processed; + _3proxy_mutex_t count_mutex; }; + +/* A client of srv comes (1) or goes (-1). */ +static void listenercount(struct srvparam *srv, int delta){ + struct listener *l = srv->listener; + + if(!l) return; + _3proxy_mutex_lock(&l->count_mutex); + if(delta > 0) l->clients++; + else { + l->clients--; + l->processed++; + } + _3proxy_mutex_unlock(&l->count_mutex); +} + +static uint64_t nocount = 0; + +/* The counters of a running service's listener, for the admin pages; the + listener lasts as long as the service. */ +uint64_t *listenerclients(struct srvparam *srv){ + return srv->listener? &srv->listener->clients : &nocount; +} + +uint64_t *listenerprocessed(struct srvparam *srv){ + return srv->listener? &srv->listener->processed : &nocount; +} static struct listener *listeners = NULL; _3proxy_mutex_t listener_mutex; @@ -1350,6 +1380,9 @@ static int srvspawn(struct srvstart *s, SOCKET client_sock){ } else { srv->childcount++; +#ifndef STDMAIN + listenercount(srv, 1); +#endif if(!reused){ newparam->workerid = (uint64_t)thread; _3proxy_mutex_lock(&tpool.mutex); @@ -1719,7 +1752,10 @@ void mainclaim(void *service){ free(newl); newl = NULL; } - else _3proxy_mutex_init(&newl->open_mutex); + else { + _3proxy_mutex_init(&newl->open_mutex); + _3proxy_mutex_init(&newl->count_mutex); + } } _3proxy_mutex_lock(&listener_mutex); for(l = listeners; l; l = l->next){ @@ -1745,6 +1781,7 @@ void mainclaim(void *service){ _3proxy_mutex_unlock(&listener_mutex); if(newl){ _3proxy_mutex_destroy(&newl->open_mutex); + _3proxy_mutex_destroy(&newl->count_mutex); free(newl->cbdest); free(newl->netns); free(newl->device); @@ -1793,6 +1830,7 @@ static void listenerrelease(struct srvparam *srv){ _3proxy_mutex_unlock(&listener_mutex); if(!last) return; _3proxy_mutex_destroy(&l->open_mutex); + _3proxy_mutex_destroy(&l->count_mutex); if(l->netns) free(l->netns); if(l->device) free(l->device); if(l->cbdest) free(l->cbdest); @@ -2134,6 +2172,9 @@ void releaseparam(struct clientparam * param) { param->next->prev = param->prev; } (param->srv->childcount)--; +#ifndef STDMAIN + listenercount(param->srv, -1); +#endif /* under the mutex too: srvfree() may destroy the semaphore as soon as the mutex shows the last child gone */ _3proxy_sem_unlock(param->srv->childslots); diff --git a/tests/cases/takeover.py b/tests/cases/takeover.py index 461fd22..6907d69 100644 --- a/tests/cases/takeover.py +++ b/tests/cases/takeover.py @@ -2,6 +2,7 @@ the same address and port takes over the old service's socket, so clients connecting all the while are never refused nor reset.""" +import re import socket import threading import time @@ -67,6 +68,24 @@ def run(t): t.eq(0, done["failed"], "no request is refused or reset by a reload " f"(first errors: {done['errors']})") + # the listener counts its clients, whichever service took them: through + # the reloads the proxy's socket is one, its count goes on + def listener_counts(service): + """The listener counters of the running service of that type: its + item has nested ones, acl and clients, before its counters.""" + page = t.http(f"http://127.0.0.1:{adm}/S").text + m = re.search(r"type of the service/clientlistenerclients.*?CDATA\[(\d+)\]" + r".*?listenerprocessed.*?CDATA\[(\d+)\]", page, re.S) + return (int(m.group(1)), int(m.group(2))) if m else (None, None) + + clients, processed = listener_counts("PROXY") + t.eq(0, clients, "no client is connected to the proxy's listener after the run") + total = done["ok"] + done["failed"] + t.ok(f"its count of clients done with goes on through the reloads ({processed} of {total})") \ + if processed is not None and processed >= total else \ + t.fail("its count of clients done with goes on through the reloads", f"at least {total}", processed) + # --- UDP: the socket is handed over too, and a session of the old # service goes on through it, its datagrams forwarded by the new one --- echo = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)