Event loops: the C library's resolver run on the thread's stack

glibc's getaddrinfo() sizes what it takes with alloca(), IPv6 most, by
the thread's stack, not by the fiber's it may be called on: a client
moved to a new configuration on a reload goes on on its fiber, and the
new configuration's services may resolve with the system resolver,
which otherwise is never called on a loop (threadperclient). A fiber
yields to have it called on the loop thread's own stack
(onthreadstack()), and goes on with its answer; the loop waits for it
as the fiber would have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-27 19:46:10 +03:00
parent f2cc447f73
commit 7e3182ae89
3 changed files with 76 additions and 21 deletions

View File

@ -340,6 +340,8 @@ void (*fiberblockfunc)(SOCKET s, short events) = NULL;
void (*fiberclosefunc)(SOCKET s) = NULL;
/* set as well: whether this thread is a loop's, which is not to wait */
int (*onloopfunc)(void) = NULL;
/* and what runs func(arg) on the thread's own stack, not a fiber's */
void (*onthreadstackfunc)(void (*func)(void *arg), void *arg) = NULL;
#define BLOCKED(s, ev) do { if(fiberblockfunc && (errno == EAGAIN || errno == EWOULDBLOCK)) \
fiberblockfunc((s), (ev)); } while(0)
@ -1174,12 +1176,45 @@ int afdetect(unsigned char *name){
}
/* A name resolved the way a mode says: a service's names with its own */
#if !defined(NOIPV6) && !defined(NOSTDRESOLVE)
/* What the C library's resolver is asked, and its answer */
struct stdresolve {
unsigned char *name;
int family;
struct sockaddr *sa;
uint32_t res;
};
/* getaddrinfo(): run on a thread's own stack, which glibc may take much of,
for IPv6 most (alloca() sized by the thread's stack, not a fiber's) */
static void stdresolve46(void *arg){
struct stdresolve *r = (struct stdresolve *)arg;
struct addrinfo *ai, hint;
r->res = 0;
memset(&hint, 0, sizeof(hint));
hint.ai_family = (r->family == 6 || r->family == 64)?AF_INET6:AF_INET;
if (getaddrinfo((char *)r->name, NULL, &hint, &ai)) {
if(r->family == 64 || r->family == 46){
hint.ai_family = (r->family == 64)?AF_INET:AF_INET6;
if (getaddrinfo((char *)r->name, NULL, &hint, &ai)) return;
}
else return;
}
if(ai){
if(ai->ai_addr->sa_family == AF_INET || ai->ai_addr->sa_family == AF_INET6){
*SAFAMILY(r->sa)=ai->ai_addr->sa_family;
memcpy(SAADDR(r->sa), SAADDR(ai->ai_addr), SAADDRLEN(ai->ai_addr));
r->res = *SAFAMILY(r->sa);
}
freeaddrinfo(ai);
}
}
#endif
uint32_t getip46m(int mode, int family, unsigned char *name, struct sockaddr *sa){
#ifndef NOIPV6
int detect;
#ifndef NOSTDRESOLVE
struct addrinfo *ai, hint;
#endif
RESOLVFUNC tmpresolv;
if(!sa) return 0;
@ -1210,23 +1245,16 @@ uint32_t getip46m(int mode, int family, unsigned char *name, struct sockaddr *s
return 0;
}
#ifndef NOSTDRESOLVE
memset(&hint, 0, sizeof(hint));
hint.ai_family = (family == 6 || family == 64)?AF_INET6:AF_INET;
if (getaddrinfo((char *)name, NULL, &hint, &ai)) {
if(family == 64 || family == 46){
hint.ai_family = (family == 64)?AF_INET:AF_INET6;
if (getaddrinfo((char *)name, NULL, &hint, &ai)) return 0;
}
else return 0;
}
if(ai){
if(ai->ai_addr->sa_family == AF_INET || ai->ai_addr->sa_family == AF_INET6){
*SAFAMILY(sa)=ai->ai_addr->sa_family;
memcpy(SAADDR(sa), SAADDR(ai->ai_addr), SAADDRLEN(ai->ai_addr));
freeaddrinfo(ai);
return *SAFAMILY(sa);
}
freeaddrinfo(ai);
{
struct stdresolve r;
r.name = name;
r.family = family;
r.sa = sa;
/* a fiber's stack is not one to give it */
if(onthreadstackfunc) (*onthreadstackfunc)(stdresolve46, &r);
else stdresolve46(&r);
return r.res;
}
#endif
return 0;

View File

@ -81,6 +81,9 @@ struct looper {
unsigned nidle;
struct sockreg **regs; /* by descriptor, the loop's thread only */
unsigned nregs;
/* what a fiber yielded to have run on the thread's stack */
void (*stackcall)(void *arg);
void *stackarg;
};
/* what srvspawn() posts to a loop */
@ -141,8 +144,16 @@ static void fibergive(struct looper *lp, struct fiberslot *fs){
client is done. */
static void fiberrun(struct looper *lp, struct fiber *f){
struct fiberslot *fs = (struct fiberslot *)f;
int alive;
if(!fiberresume(f)) fibergive(lp, fs);
/* a call it asked for on the thread's stack made, and it goes on */
while((alive = fiberresume(f)) && lp->stackcall){
void (*call)(void *arg) = lp->stackcall;
lp->stackcall = NULL;
call(lp->stackarg);
}
if(!alive) fibergive(lp, fs);
/* in the middle of its client's code: started over from its stack's
start for the next client, or freed where it cannot be */
else if(fs->abandoned){
@ -1132,6 +1143,20 @@ static unsigned ncpus(void){
#endif
}
/* onthreadstackfunc: func(arg) on the loop thread's own stack, for a fiber
calling what wants a big one: the loop waits as long as it takes. */
static void onthreadstack(void (*func)(void *arg), void *arg){
struct looper *lp = curlooper;
if(!lp || !fibercurrent()){
func(arg);
return;
}
lp->stackcall = func;
lp->stackarg = arg;
fiberyield();
}
/* The loops, started the first time a client is to be served on one, for
the life of the process: as many as loops said then. 0 if they run. */
static int loopsstart(void){
@ -1191,6 +1216,7 @@ static int loopsstart(void){
#endif
fiberpollfunc = fiberpoll;
onloopfunc = onlooper;
onthreadstackfunc = onthreadstack;
}
_3proxy_mutex_unlock(&loops_mutex);
return !i;

View File

@ -340,6 +340,7 @@ extern int (*fiberpollfunc)(struct pollfd *fds, unsigned nfds, int timeout);
extern void (*fiberblockfunc)(SOCKET s, short events);
extern void (*fiberclosefunc)(SOCKET s);
extern int (*onloopfunc)(void);
extern void (*onthreadstackfunc)(void (*func)(void *arg), void *arg);
int sockreadable(SOCKET s);
#ifndef _WIN32
int fdclose(int fd);