diff --git a/src/fiber.c b/src/fiber.c index 8b6e037..8f944ef 100644 --- a/src/fiber.c +++ b/src/fiber.c @@ -30,6 +30,7 @@ #endif #ifdef FIBER_ASAN #include +#include #endif #ifdef _MSC_VER @@ -348,8 +349,30 @@ void fiberfree(struct fiber *f){ memset(f, 0, sizeof(*f)); } +/* f, left in the middle of func, to be started again from the start of + its stack: whatever its stack held is forgotten. 0, or 1 if it cannot + be (Windows), and is to be freed instead. */ +int fiberreset(struct fiber *f){ +#ifdef FIBER_WINDOWS + return 1; +#else + f->func = NULL; + f->started = 0; + f->fake = NULL; +#ifdef FIBER_ASAN + /* what AddressSanitizer knew of the frames left there */ + { + size_t page = (size_t)sysconf(_SC_PAGESIZE); + + __asan_unpoison_memory_region(f->map + page, f->mapped - page); + } +#endif + return 0; +#endif +} + /* func(arg) is what f runs once resumed: f is new, or func of its last - start returned. */ + start returned, or it was reset. */ void fiberstart(struct fiber *f, void (*func)(void *arg), void *arg){ f->func = func; f->arg = arg; diff --git a/src/fiber.h b/src/fiber.h index 90eb625..fadf88d 100644 --- a/src/fiber.h +++ b/src/fiber.h @@ -52,6 +52,7 @@ int fiberusable(void); int fiberinit(struct fiber *f, size_t stacksize); void fiberfree(struct fiber *f); void fiberstart(struct fiber *f, void (*func)(void *arg), void *arg); +int fiberreset(struct fiber *f); int fiberresume(struct fiber *f); void fiberyield(void); struct fiber *fibercurrent(void); diff --git a/src/mainclient.c b/src/mainclient.c index 9bf0a0d..512bd8f 100644 --- a/src/mainclient.c +++ b/src/mainclient.c @@ -385,6 +385,11 @@ static int srvspawn(struct srvstart *s, SOCKET client_sock){ what was freed. After this the service may be gone at any moment, so param->srv is cleared and nothing here touches it again. */ void releaseparam(struct clientparam * param) { + param->maptail = NULL; + if(param->maptaildata){ + free(param->maptaildata); + param->maptaildata = NULL; + } if(param->srv){ if(param->srv->so.freefunc) param->srv->so.freefunc(param->sostate); } diff --git a/src/mainloop.c b/src/mainloop.c index 319928e..bcf26ff 100644 --- a/src/mainloop.c +++ b/src/mainloop.c @@ -38,6 +38,9 @@ struct fiberslot { struct fiber f; size_t stacksize; struct fiberslot *next; + /* left in the middle of what it runs, never to be resumed: its + client's mapping is the loop's (maptail) */ + int abandoned; }; /* With epoll and kqueue a socket a fiber waits on is registered once, for @@ -108,6 +111,7 @@ static struct fiberslot *fibertake(struct looper *lp, size_t stacksize){ } } if(!(fs = malloc(sizeof(*fs)))) return NULL; + fs->abandoned = 0; if(fiberinit(&fs->f, stacksize)){ free(fs); return NULL; @@ -130,7 +134,19 @@ static void fibergive(struct looper *lp, struct fiberslot *fs){ /* Runs a fiber of this loop until it waits, or gives it back once its client is done. */ static void fiberrun(struct looper *lp, struct fiber *f){ - if(!fiberresume(f)) fibergive(lp, (struct fiberslot *)f); + struct fiberslot *fs = (struct fiberslot *)f; + + if(!fiberresume(f)) 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){ + fs->abandoned = 0; + if(!fiberreset(&fs->f)) fibergive(lp, fs); + else { + fiberfree(&fs->f); + free(fs); + } + } } /* A client, on a fiber: as its thread would serve it. */ @@ -424,6 +440,11 @@ struct cbmap { int sleep; /* ms to pause for: bandwidth, grace */ int paused; int stop, stopres; /* a filter's verdict ends the mapping */ + /* the client's fiber gave up its stack: the child's end (maptail) is + the loop's once mapping is over */ + int abandoned; + int timeosec, usesplice0; /* sockmap()'s, for a fiber to go on with + after a reload */ int res; }; @@ -798,13 +819,15 @@ static int cbmaprun(struct cbmap *m){ return 0; } +static void cbmapdone(struct cbmap *m); + static void cbmapio(void *data){ struct cbmap *m = (struct cbmap *)data; /* paused: what is ready waits for the pause to end */ if(m->paused) return; - /* m is on the fiber's stack: not touched once it runs */ - if(cbmaprun(m)) fiberrun(m->lp, m->f); + /* m is not touched once the mapping is over: it may be gone */ + if(cbmaprun(m)) cbmapdone(m); } static void cbmapagain(struct loop *l, struct looptimer *t){ @@ -822,7 +845,54 @@ static void cbmapidle(struct loop *l, struct looptimer *t){ return; } cbmapend(m, 92); - fiberrun(m->lp, m->f); + cbmapdone(m); +} + +/* What the child of the client does once mapping is over, as its end and + that of its fiber would: the result, its tail, the connection released + and freed. */ +static void cbmapfinish(struct clientparam *param, int res){ + param->res = res; + if(param->maptail) param->maptail(param, param->maptaildata); + releaseparam(param); + freeparam(param); +} + +/* a fiber for the client again: sockmap() goes on with the mapping after + a reload, then the child's end */ +static void cbmapresume(void *arg){ + struct cbmap *m = (struct cbmap *)arg; + struct clientparam *param = m->param; + int timeo = m->timeosec, usesplice = m->usesplice0; + + free(m); + cbmapfinish(param, sockmap(param, timeo, usesplice)); +} + +/* The mapping is over: its fiber goes on, or, with none, the loop ends it + as the child would, or gives it a fiber again for sockmap() to take it + over after a reload. m is freed. */ +static void cbmapdone(struct cbmap *m){ + struct clientparam *param = m->param; + struct fiberslot *fs; + int res; + + if(!m->abandoned){ + /* the fiber frees m */ + fiberrun(m->lp, m->f); + return; + } + if(m->res == -2){ + if((fs = fibertake(m->lp, param->stacksize))){ + fiberstart(&fs->f, cbmapresume, m); + fiberrun(m->lp, &fs->f); + return; + } + m->res = 21; + } + res = m->res; + free(m); + cbmapfinish(param, res); } /* sockmapcbfunc: sockmap() of a fiber, as callbacks, its result; -2 for @@ -831,8 +901,8 @@ static void cbmapidle(struct loop *l, struct looptimer *t){ its filters forbid. */ static int fibermap(struct clientparam *param, int timeo, int usesplice){ struct looper *lp = curlooper; - struct cbmap m; - int i; + struct cbmap *m; + int i, res, usesplice0 = usesplice; if(!lp || !fibercurrent() || param->srv->loopmode != 2) return -2; if(param->clisock == INVALID_SOCKET || param->remsock == INVALID_SOCKET || @@ -850,30 +920,33 @@ static int fibermap(struct clientparam *param, int timeo, int usesplice){ socket, data is copied */ if(param->srv->so._recvfrom != so._recvfrom || param->srv->so._sendto != so._sendto) usesplice = 0; - memset(&m, 0, sizeof(m)); - m.sock[0] = param->clisock; - m.sock[1] = param->remsock; - if(!(m.reg[0] = sockreg(lp, m.sock[0])) || !(m.reg[1] = sockreg(lp, m.sock[1]))) return -2; - if((param->ctrlsock != INVALID_SOCKET && !(m.ctrl[0] = sockreg(lp, param->ctrlsock))) || - (param->ctrlsocksrv != INVALID_SOCKET && !(m.ctrl[1] = sockreg(lp, param->ctrlsocksrv)))) + /* not on the fiber's stack: the fiber may give it up */ + if(!(m = calloc(1, sizeof(*m)))) return -2; + m->sock[0] = param->clisock; + m->sock[1] = param->remsock; + if(!(m->reg[0] = sockreg(lp, m->sock[0])) || !(m->reg[1] = sockreg(lp, m->sock[1])) || + (param->ctrlsock != INVALID_SOCKET && !(m->ctrl[0] = sockreg(lp, param->ctrlsock))) || + (param->ctrlsocksrv != INVALID_SOCKET && !(m->ctrl[1] = sockreg(lp, param->ctrlsocksrv)))){ + free(m); return -2; - m.param = param; - m.lp = lp; - m.f = fibercurrent(); - m.halfclose = param->srv->halfclose; - m.usesplice = usesplice; - m.timeo = (uint64_t)timeo * 1000; - m.linger = (uint64_t)param->srv->timeouts[LINGER_TO] * 1000; - m.way[0].src = m.way[1].dst = 0; - m.way[0].dst = m.way[1].src = 1; - m.way[0].left = m.way[1].left = CBALL; + } + m->param = param; + m->lp = lp; + m->f = fibercurrent(); + m->halfclose = param->srv->halfclose; + m->usesplice = usesplice; + m->timeo = (uint64_t)timeo * 1000; + m->linger = (uint64_t)param->srv->timeouts[LINGER_TO] * 1000; + m->way[0].src = m->way[1].dst = 0; + m->way[0].dst = m->way[1].src = 1; + m->way[0].left = m->way[1].left = CBALL; if(param->waitclient64){ - m.way[0].left = param->waitclient64; - m.way[1].left = 0; + m->way[0].left = param->waitclient64; + m->way[1].left = 0; } if(param->waitserver64){ - m.way[1].left = param->waitserver64; - m.way[0].left = 0; + m->way[1].left = param->waitserver64; + m->way[0].left = 0; } if(param->srvinbuf == param->srvoffset) param->srvinbuf = param->srvoffset = 0; if(param->cliinbuf == param->clioffset) param->cliinbuf = param->clioffset = 0; @@ -884,27 +957,41 @@ static int fibermap(struct clientparam *param, int timeo, int usesplice){ if(!(usesplice & bit)) continue; /* told to, or a body of known length large enough: spliced from the start */ - if(!(usesplice & SPLICE_AUTO) || (m.way[i].left != CBALL && - m.way[i].left >= (uint64_t)param->srv->splicemin)) - m.way[i].splice = 1; + if(!(usesplice & SPLICE_AUTO) || (m->way[i].left != CBALL && + m->way[i].left >= (uint64_t)param->srv->splicemin)) + m->way[i].splice = 1; } #endif - m.last = looptime(); - m.idle.func = cbmapidle; - m.idle.data = &m; - m.again.func = cbmapagain; - m.again.data = &m; - m.reg[0]->cb = m.reg[1]->cb = cbmapio; - m.reg[0]->cbdata = m.reg[1]->cbdata = &m; - for(i = 0; i < 2; i++) if(m.ctrl[i]){ - m.ctrl[i]->cb = cbmapio; - m.ctrl[i]->cbdata = &m; + m->timeosec = timeo; + m->usesplice0 = usesplice0; + m->last = looptime(); + m->idle.func = cbmapidle; + m->idle.data = m; + m->again.func = cbmapagain; + m->again.data = m; + m->reg[0]->cb = m->reg[1]->cb = cbmapio; + m->reg[0]->cbdata = m->reg[1]->cbdata = m; + for(i = 0; i < 2; i++) if(m->ctrl[i]){ + m->ctrl[i]->cb = cbmapio; + m->ctrl[i]->cbdata = m; } - looptimerset(&lp->l, &m.idle, cbmapdeadline(&m)); + looptimerset(&lp->l, &m->idle, cbmapdeadline(m)); /* what is ready already is moved at once, on the fiber's stack */ - if(cbmaprun(&m)) return m.res; - fiberyield(); - return m.res; + if(!cbmaprun(m)){ + /* mapping the child's last thing, its end is the loop's then, + and its stack given back; not while a reload is pending, + which sockmap() is to see to on this fiber */ + if(param->maptail && param->version >= runconf->version){ + m->abandoned = 1; + ((struct fiberslot *)m->f)->abandoned = 1; + m->f = NULL; + } + fiberyield(); + /* resumed only if not abandoned */ + } + res = m->res; + free(m); + return res; } #endif diff --git a/src/structures.h b/src/structures.h index d5076e7..4003064 100644 --- a/src/structures.h +++ b/src/structures.h @@ -937,6 +937,12 @@ struct clientparam { password[USERNAMELEN], extusername[USERNAMELEN], extpassword[USERNAMELEN]; + /* Set by a child for which mapping is the last thing it does: what it + does once mapping returns, as its own end would, given its result in + res, for a loop to end it so with no fiber of its own meanwhile. + maptaildata is malloc()'d or NULL, freed with the connection. */ + void (*maptail)(struct clientparam *param, void *data); + void *maptaildata; /* The filters this connection runs, which a new connection makes for itself: the service's are not copied to it. Ordered for each stage. */ struct filterp filters[MAX_FILTERS], diff --git a/src/tcppm.c b/src/tcppm.c index 3bed52d..cdd9e35 100644 --- a/src/tcppm.c +++ b/src/tcppm.c @@ -13,6 +13,11 @@ #endif #define RETURN(xxx) { param->res = xxx; goto CLEANRET; } +/* as the end below, once mapping is over */ +static void tcppmtail(struct clientparam *param, void *data){ + dolog(param, *param->hostname? param->hostname : NULL); +} + void * tcppmchild(struct clientparam* param) { int res; @@ -43,6 +48,7 @@ void * tcppmchild(struct clientparam* param) { return (void *)param->redirectfunc; } + param->maptail = tcppmtail; RETURN (mapsocket(param, param->srv->timeouts[CONNECTION_L])); CLEANRET: diff --git a/src/tlspr.c b/src/tlspr.c index 3571b4f..4612763 100644 --- a/src/tlspr.c +++ b/src/tlspr.c @@ -293,6 +293,11 @@ static int tlstobuf(struct clientparam *param, DIRECTION which){ return (int)len; } +/* as the end below, once mapping is over: its log line, made before */ +static void tlsprtail(struct clientparam *param, void *data){ + dolog(param, (unsigned char *)data); +} + void * tlsprchild(struct clientparam* param) { int res; char sni[SNILEN]; @@ -417,6 +422,8 @@ void * tlsprchild(struct clientparam* param) { } } + sprintf(req, "%sv%d.%d %s %s", lv<0?"NONE":lv?"TLS":"SSL", lv<0?0:lv?1:3, lv<0?0:lv?lv-1:0, *param->hostname?(char *)param->hostname:"-", proto); + if((param->maptaildata = strdup(req))) param->maptail = tlsprtail; RETURN (mapsocket(param, param->srv->timeouts[CONNECTION_L])); CLEANRET: