proxy_luafgen.c C 2,120 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,120.
1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */23#include "proxy.h"4#ifdef TLS5#include "tls.h"6#endif78static mcp_funcgen_t *mcp_funcgen_route(lua_State *L, mcp_funcgen_t *fgen, mcp_parser_t *pr);9static int mcp_funcgen_router_cleanup(lua_State *L, mcp_funcgen_t *fgen);10static void _mcplib_funcgen_cache(mcp_funcgen_t *fgen, mcp_rcontext_t *rctx);11static void mcp_funcgen_cleanup(lua_State *L, mcp_funcgen_t *fgen);12static void mcp_resume_rctx_from_cb(mcp_rcontext_t *rctx);13static void proxy_return_rqu_cb(io_pending_t *pending);1415// If we're GC'ed but not closed, it means it was created but never16// attached to a function, so ensure everything is closed properly.17int mcplib_funcgen_gc(lua_State *L) {18    mcp_funcgen_t *fgen = luaL_checkudata(L, -1, "mcp.funcgen");19    if (fgen->closed) {20        return 0;21    }22    assert(fgen->self_ref == 0);2324    mcp_funcgen_cleanup(L, fgen);25    fgen->closed = true;26    return 0;27}2829// handler for *_wait_*() variants and sleep calls30static void mcp_funcgen_wait_handler(const int fd, const short which, void *arg) {31    mcp_rcontext_t *rctx = arg;3233    // if we were in waiting: reset wait mode, push wait_done + boolean true34    // if we were in sleep: reset wait mode.35    // immediately resume.36    lua_settop(rctx->Lc, 0);37    rctx->wait_count = 0;38    rctx->lua_narg = 2;39    if (rctx->wait_mode == QWAIT_HANDLE) {40        // if timed out then we shouldn't have a result. just push nil.41        lua_pushnil(rctx->Lc);42    } else if (rctx->wait_mode == QWAIT_SLEEP) {43        // no extra arg.44        rctx->lua_narg = 1;45    } else {46        // how many results were processed47        lua_pushinteger(rctx->Lc, rctx->wait_done);48    }49    // "timed out"50    lua_pushboolean(rctx->Lc, 1);5152    rctx->wait_mode = QWAIT_IDLE;5354    mcp_resume_rctx_from_cb(rctx);55}5657// For describing functions which generate functions which can execute58// requests.59// These "generator functions" handle pre-allocating and creating a memory60// heirarchy, allowing dynamic runtimes at high speed.6162// must be called with fgen on top of stack in fgen->thread->L63static void mcp_rcontext_cleanup(lua_State *L, mcp_funcgen_t *fgen, mcp_rcontext_t *rctx, int fgen_idx) {64    luaL_unref(L, LUA_REGISTRYINDEX, rctx->coroutine_ref);65    luaL_unref(L, LUA_REGISTRYINDEX, rctx->function_ref);66    if (rctx->request_ref) {67        luaL_unref(L, LUA_REGISTRYINDEX, rctx->request_ref);68    }69    assert(rctx->pending_reqs == 0);7071    // cleanup of request queue entries. recurse funcgen cleanup.72    for (int x = 0; x < fgen->max_queues; x++) {73        struct mcp_rqueue_s *rqu = &rctx->qslots[x];74        if (rqu->obj_type == RQUEUE_TYPE_POOL || rqu->obj_type == RQUEUE_TYPE_INT) {75            // nothing to do.76        } else if (rqu->obj_type == RQUEUE_TYPE_FGEN) {77            // don't need to recurse, just free the subrctx.78            mcp_rcontext_t *subrctx = rqu->obj;79            lua_rawgeti(L, LUA_REGISTRYINDEX, subrctx->fgen->self_ref);80            mcp_rcontext_cleanup(L, subrctx->fgen, subrctx, lua_absindex(L, -1));81            lua_pop(L, 1); // drop subrctx fgen82        } else if (rqu->obj_type != RQUEUE_TYPE_NONE) {83            assert(1 == 0);84        }8586        if (rqu->res_ref) {87            luaL_unref(L, LUA_REGISTRYINDEX, rqu->res_ref);88            rqu->res_ref = 0;89        }9091        if (rqu->cb_ref) {92            luaL_unref(L, LUA_REGISTRYINDEX, rqu->cb_ref);93            rqu->cb_ref = 0;94        }95    }9697    // look for rctx-local objects.98    if (rctx->uobj_count) {99        int lim = fgen->max_queues + rctx->uobj_count;100        for (int x = fgen->max_queues; x < lim; x++) {101            struct mcp_rqueue_s *rqu = &rctx->qslots[x];102            // Don't need to look at the type:103            // - slot has to be freed (thus cleaned up) before getting here104            // - any uobj is ref'ed into obj_ref105            luaL_unref(L, LUA_REGISTRYINDEX, rqu->obj_ref);106            rqu->obj_ref = 0;107        }108    }109110    // nuke alarm if set.111    // should only be paranoia here, but just in case.112    if (event_pending(&rctx->timeout_event, EV_TIMEOUT, NULL)) {113        event_del(&rctx->timeout_event);114    }115116    lua_getiuservalue(L, fgen_idx, 1);117    luaL_unref(L, -1, rctx->self_ref);118    rctx->self_ref = 0;119    lua_pop(L, 1); // drop freelist table120121    fgen->total--;122    LIBEVENT_THREAD *t = PROXY_GET_THR(L);123    // Fake an allocation when we free slots as they are long running data.124    // This tricks the GC into running and freeing them.125    t->proxy_vm_extra_kb += 2;126    mcp_sharedvm_delta(t->proxy_ctx, SHAREDVM_FGENSLOT_IDX, fgen->name, -1);127}128129// TODO: switch from an array to a STAILQ so we can avoid the memory130// management and error handling.131// Realistically it's impossible for these to error so we're safe for now.132#ifdef MEMCACHED_DEBUG133// require fewer test rounds for unit tests.134#define FGEN_FREE_PRESSURE_MAX 100135#define FGEN_FREE_PRESSURE_DROP 10136#define FGEN_FREE_WAIT 0137#else138#define FGEN_FREE_PRESSURE_MAX 5000139#define FGEN_FREE_PRESSURE_DROP 200140#define FGEN_FREE_WAIT 60 // seconds.141#endif142static void _mcplib_funcgen_cache(mcp_funcgen_t *fgen, mcp_rcontext_t *rctx) {143    bool do_cache = true;144    // Easing algorithm to decide when to "early free" rctx slots:145    // - If we recently allocated a slot, reset pressure.146    // - Each time an rctx is freed and more than half of available rctx's are147    // free, increase pressure.148    // - If free rctx are less than half of total, reduce pressure.149    // - If pressure is too high, immediately free the rctx, then drop the150    // pressure slightly.151    // - If pressure is too high, and has been for more than FGEN_FREE_WAIT152    // seconds, immediately free the rctx, then drop the pressure slightly.153    //154    // This should allow bursty traffic to avoid spinning on alloc/frees,155    // while one-time bursts will slowly free slots back down to a min of 1.156    if (fgen->free > fgen->total/2 - 1) {157        if (fgen->free_pressure++ > FGEN_FREE_PRESSURE_MAX) {158            struct timespec now;159            clock_gettime(CLOCK_REALTIME, &now);160            if (fgen->free_waiter.tv_sec == 0) {161                fgen->free_waiter.tv_sec = now.tv_sec + FGEN_FREE_WAIT;162            }163164            if (now.tv_sec >= fgen->free_waiter.tv_sec) {165                do_cache = false;166            }167            // check again in a little while.168            fgen->free_pressure -= FGEN_FREE_PRESSURE_DROP;169        }170    } else {171        fgen->free_pressure >>= 1;172        // must be too-free for a full wait period before releasing.173        fgen->free_waiter.tv_sec = 0;174    }175176    if (do_cache) {177        if (fgen->free + 1 >= fgen->free_max) {178            int x = fgen->free_max;179            fgen->free_max *= 2;180            fgen->list = realloc(fgen->list, fgen->free_max * sizeof(mcp_rcontext_t *));181            for (; x < fgen->free_max; x++) {182                fgen->list[x] = NULL;183            }184        }185        fgen->list[fgen->free] = rctx;186        fgen->free++;187    } else {188        // do not cache the rctx189        assert(fgen->self_ref);190        lua_State *L = fgen->thread->L;191        lua_rawgeti(L, LUA_REGISTRYINDEX, fgen->self_ref);192        mcp_rcontext_cleanup(L, fgen, rctx, lua_absindex(L, -1));193        lua_pop(L, 1); // drop fgen194    }195196    // we're closed and every outstanding request slot has been197    // returned.198    if (fgen->closed && fgen->free == fgen->total) {199        mcp_funcgen_cleanup(fgen->thread->L, fgen);200    }201}202203// call with stack: mcp.funcgen -2, function -1204static int _mcplib_funcgen_gencall(lua_State *L) {205    mcp_funcgen_t *fgen = luaL_checkudata(L, -2, "mcp.funcgen");206    int fgen_idx = lua_absindex(L, -2);207    // create the ctx object.208    int total_queues = fgen->max_queues + fgen->uobj_queues;209    size_t rctx_len = sizeof(mcp_rcontext_t) + sizeof(struct mcp_rqueue_s) * total_queues;210    mcp_rcontext_t *rc = lua_newuserdatauv(L, rctx_len, 0);211    memset(rc, 0, rctx_len);212213    luaL_getmetatable(L, "mcp.rcontext");214    lua_setmetatable(L, -2);215    // allow the rctx to reference the function generator.216    rc->fgen = fgen;217    rc->lua_narg = 1;218219    // initialize the queue slots based on the fgen parent220    for (int x = 0; x < fgen->max_queues; x++) {221        struct mcp_rqueue_s *frqu = &fgen->queue_list[x];222        struct mcp_rqueue_s *rqu = &rc->qslots[x];223        rqu->obj_type = frqu->obj_type;224        if (frqu->obj_type == RQUEUE_TYPE_POOL || frqu->obj_type == RQUEUE_TYPE_INT) {225            rqu->obj_ref = 0;226            rqu->obj = frqu->obj;227            mcp_resp_t *r = mcp_prep_bare_resobj(L, fgen->thread);228            rqu->res_ref = luaL_ref(L, LUA_REGISTRYINDEX);229            rqu->res_obj = r;230        } else if (frqu->obj_type == RQUEUE_TYPE_FGEN) {231            // owner funcgen already holds the subfgen reference, so here we're just232            // grabbing a subrctx to pin into the slot.233            mcp_funcgen_t *fg = frqu->obj;234            mcp_rcontext_t *subrctx = mcp_funcgen_get_rctx(L, fg->self_ref, fg);235            if (subrctx == NULL) {236                proxy_lua_error(L, "failed to generate request slot during queue_assign()");237            }238239            // if this rctx ever had a request object assigned to it, we can get240            // rid of it. we're pinning the subrctx in here and don't want241            // to waste memory.242            if (subrctx->request_ref) {243                luaL_unref(L, LUA_REGISTRYINDEX, subrctx->request_ref);244                subrctx->request_ref = 0;245                subrctx->request = NULL;246            }247248            // link the new rctx into this chain; we'll hold onto it until the249            // parent de-allocates.250            subrctx->parent = rc;251            subrctx->parent_handle = x;252            rqu->obj = subrctx;253        }254    }255256    // copy the rcontext reference257    lua_pushvalue(L, -1);258259    // issue a rotation so one rcontext is now below genfunc, and one rcontext260    // is on the top.261    // right shift: gf, rc1, rc2 -> rc2, gf, rc1262    lua_rotate(L, -3, 1);263264    // current stack should be func, mcp.rcontext.265    int call_argnum = 1;266    // stack will be func, rctx, arg if there is an arg.267    if (fgen->argument_ref) {268        lua_rawgeti(L, LUA_REGISTRYINDEX, fgen->argument_ref);269        call_argnum++;270    }271272    // can throw an error upstream.273    lua_call(L, call_argnum, 1);274275    // we should have a top level function as a result.276    if (!lua_isfunction(L, -1)) {277        proxy_lua_error(L, "function generator didn't return a function");278        return 0;279    }280    // can't fail past this point.281282    // pop the returned function.283    rc->function_ref = luaL_ref(L, LUA_REGISTRYINDEX);284285    // link the rcontext into the function generator.286    fgen->total++;287288    lua_getiuservalue(L, fgen_idx, 1); // get the reference table.289    // rc, t -> t, rc290    lua_rotate(L, -2, 1);291    rc->self_ref = luaL_ref(L, -2); // pop rcontext292    lua_pop(L, 1); // pop ref table.293294    _mcplib_funcgen_cache(fgen, rc);295296    // associate a coroutine thread with this context.297    rc->Lc = lua_newthread(L);298    assert(rc->Lc);299    rc->coroutine_ref = luaL_ref(L, LUA_REGISTRYINDEX);300301    // increment the slot counter302    LIBEVENT_THREAD *t = PROXY_GET_THR(L);303    mcp_sharedvm_delta(t->proxy_ctx, SHAREDVM_FGENSLOT_IDX, fgen->name, 1);304305    event_assign(&rc->timeout_event, t->base, -1, EV_TIMEOUT, mcp_funcgen_wait_handler, rc);306307    // return the fgen.308    // FIXME: just return 0? need to adjust caller to not mis-ref the309    // generator function.310    return 1;311}312313static void _mcp_funcgen_return_rctx(mcp_rcontext_t *rctx) {314    mcp_funcgen_t *fgen = rctx->fgen;315    assert(rctx->pending_reqs == 0);316    int res = lua_status(rctx->Lc);317    if (res != LUA_OK) {318        // Can't reuse the thread if we ended in an error.319        // Reset and close out the old thread.320        lua_resetthread(rctx->Lc);321        lua_State *L = fgen->thread->L;322        luaL_unref(L, LUA_REGISTRYINDEX, rctx->coroutine_ref);323        // Make a new thread.324        rctx->Lc = lua_newthread(L);325        assert(rctx->Lc);326        rctx->coroutine_ref = luaL_ref(L, LUA_REGISTRYINDEX);327    } else {328        // Thread is okay, clear stack and continue.329        lua_settop(rctx->Lc, 0);330    }331    rctx->wait_mode = QWAIT_IDLE;332    rctx->resp = NULL;333    rctx->ascii_multiget = false;334    if (rctx->request) {335        mcp_request_cleanup(fgen->thread, rctx->request);336    }337338    // nuke alarm if set.339    if (event_pending(&rctx->timeout_event, EV_TIMEOUT, NULL)) {340        event_del(&rctx->timeout_event);341    }342343    // reset each rqu.344    for (int x = 0; x < fgen->max_queues; x++) {345        struct mcp_rqueue_s *rqu = &rctx->qslots[x];346        if (rqu->res_ref) {347            if (rqu->res_obj) {348                // using a persistent object.349                mcp_response_cleanup(fgen->thread, rqu->res_obj);350            } else {351                // temporary error object352                luaL_unref(rctx->Lc, LUA_REGISTRYINDEX, rqu->res_ref);353                rqu->res_ref = 0;354            }355        }356        if (rqu->req_ref) {357            luaL_unref(rctx->Lc, LUA_REGISTRYINDEX, rqu->req_ref);358            rqu->req_ref = 0;359        }360        assert(rqu->state != RQUEUE_ACTIVE);361        rqu->state = RQUEUE_IDLE;362        rqu->flags = 0;363        rqu->rq = NULL;364        if (rqu->obj_type == RQUEUE_TYPE_FGEN) {365            _mcp_funcgen_return_rctx(rqu->obj);366        }367    }368369    // look for rctx-local objects.370    if (rctx->uobj_count) {371        int lim = fgen->max_queues + rctx->uobj_count;372        for (int x = fgen->max_queues; x < lim; x++) {373            struct mcp_rqueue_s *rqu = &rctx->qslots[x];374            if (rqu->obj_type == RQUEUE_TYPE_UOBJ_REQ) {375                mcp_request_t *rq = rqu->obj;376                mcp_request_cleanup(fgen->thread, rq);377            } else if (rqu->obj_type == RQUEUE_TYPE_UOBJ_RES) {378                mcp_resp_t *rs = rqu->obj;379                mcp_response_cleanup(fgen->thread, rs);380            } else {381                // no known type. only crash the debug binary.382                assert(1 == 0);383            }384        }385    }386}387388// TODO: check rctx->awaiting before returning?389// TODO: separate the "cleanup" portion from the "Return to cache" portion, so390// we can call that directly for subrctx's391void mcp_funcgen_return_rctx(mcp_rcontext_t *rctx) {392    mcp_funcgen_t *fgen = rctx->fgen;393    if (rctx->pending_reqs != 0) {394        // not ready to return to cache yet.395        return;396    }397    if (rctx->parent) {398        // Important: we need to hold the parent request reference until this399        // subrctx is fully depleted of outstanding requests itself.400        rctx->parent->pending_reqs--;401        assert(rctx->parent->pending_reqs > -1);402        if (rctx->parent->pending_reqs == 0) {403            mcp_funcgen_return_rctx(rctx->parent);404        }405        return;406    }407    WSTAT_DECR(rctx->fgen->thread, proxy_req_active, 1);408    assert(rctx->fgen->thread->stats.proxy_req_active >= 0);409    _mcp_funcgen_return_rctx(rctx);410    _mcplib_funcgen_cache(fgen, rctx);411}412413mcp_rcontext_t *mcp_funcgen_get_rctx(lua_State *L, int fgen_ref, mcp_funcgen_t *fgen) {414    mcp_rcontext_t *rctx = NULL;415    // nothing left in slot cache, generate a new function.416    if (fgen->free == 0) {417        // reset free pressure so we try to keep the rctx cached418        fgen->free_pressure = 0;419        fgen->free_waiter.tv_sec = 0;420        // TODO (perf): pre-create this c closure somewhere hidden.421        lua_pushcclosure(L, _mcplib_funcgen_gencall, 0);422        // pull in the funcgen object423        lua_rawgeti(L, LUA_REGISTRYINDEX, fgen_ref);424        // then generator function425        lua_rawgeti(L, LUA_REGISTRYINDEX, fgen->generator_ref);426        // then generate a new function slot.427        int res = lua_pcall(L, 2, 1, 0);428        if (res != LUA_OK) {429            LOGGER_LOG(NULL, LOG_PROXYEVENTS, LOGGER_PROXY_ERROR, NULL, lua_tostring(L, -1));430            lua_settop(L, 0);431            return NULL;432        }433        lua_pop(L, 1); // drop the extra funcgen434    } else {435        P_DEBUG("%s: serving from cache\n", __func__);436    }437438    rctx = fgen->list[fgen->free-1];439    fgen->list[fgen->free-1] = NULL;440    fgen->free--;441442    // on non-error, return the response object upward.443    return rctx;444}445446mcp_rcontext_t *mcp_funcgen_start(lua_State *L, mcp_funcgen_t *fgen, mcp_parser_t *pr) {447    if (fgen->is_router) {448        fgen = mcp_funcgen_route(L, fgen, pr);449        if (fgen == NULL) {450            return NULL;451        }452    }453    // fgen->self_ref must be valid because we cannot start a function that454    // hasn't been referenced anywhere.455    mcp_rcontext_t *rctx = mcp_funcgen_get_rctx(L, fgen->self_ref, fgen);456457    if (rctx == NULL) {458        return NULL;459    }460461    // only top level rctx's can have a request object assigned to them.462    // so we create them late here, in the start function.463    // Note that we can _technically_ fail with an OOM here, but we've not set464    // up lua in a way that OOM's are possible.465    if (rctx->request_ref == 0) {466        mcp_request_t *rq = lua_newuserdatauv(L, sizeof(mcp_request_t) + MCP_REQUEST_MAXLEN, 0);467        memset(rq, 0, sizeof(mcp_request_t));468        luaL_getmetatable(L, "mcp.request");469        lua_setmetatable(L, -2);470471        rctx->request_ref = luaL_ref(L, LUA_REGISTRYINDEX); // pop the request472        rctx->request = rq;473    }474475    // TODO: could probably move a few more lines from proto_proxy into here,476    // but that's splitting hairs.477    WSTAT_INCR(fgen->thread, proxy_req_active, 1);478    return rctx;479}480481// calling either with self_ref set, or with fgen in stack -1 (ie; from GC482// function without ever being attached to anything)483static void mcp_funcgen_cleanup(lua_State *L, mcp_funcgen_t *fgen) {484    int fgen_idx = 0;485    lua_checkstack(L, 5); // paranoia. this can recurse from a router.486    // pull the fgen into the stack.487    if (fgen->self_ref) {488        // pull self onto the stack and hold until the end of the func.489        lua_rawgeti(L, LUA_REGISTRYINDEX, fgen->self_ref);490        fgen_idx = lua_absindex(L, -1); // remember fgen offset491        // remove the C reference to the fgen492        luaL_unref(L, LUA_REGISTRYINDEX, fgen->self_ref);493        fgen->self_ref = 0;494    } else if (fgen->closed) {495        // we've already cleaned up, probably redundant call from _gc()496        return;497    } else {498        // not closed, no self-ref, so must be unattached and coming from GC499        fgen_idx = lua_absindex(L, -1);500    }501502    if (fgen->is_router) {503        // we're actually a "router", send this out for cleanup.504        mcp_funcgen_router_cleanup(L, fgen);505    }506507    // decrement the slot counter508    LIBEVENT_THREAD *t = PROXY_GET_THR(L);509    mcp_sharedvm_delta(t->proxy_ctx, SHAREDVM_FGEN_IDX, fgen->name, -1);510511    // Walk every request context and issue cleanup.512    for (int x = 0; x < fgen->free_max; x++) {513        mcp_rcontext_t *rctx = fgen->list[x];514        if (rctx == NULL) {515            continue;516        }517        mcp_rcontext_cleanup(L, fgen, rctx, fgen_idx);518    }519520    if (fgen->argument_ref) {521        luaL_unref(L, LUA_REGISTRYINDEX, fgen->argument_ref);522        fgen->argument_ref = 0;523    }524525    if (fgen->generator_ref) {526        luaL_unref(L, LUA_REGISTRYINDEX, fgen->generator_ref);527        fgen->generator_ref = 0;528    }529530    if (fgen->queue_list) {531        for (int x = 0; x < fgen->max_queues; x++) {532            struct mcp_rqueue_s *rqu = &fgen->queue_list[x];533            if (rqu->obj_type == RQUEUE_TYPE_POOL || rqu->obj_type == RQUEUE_TYPE_INT) {534                // just the obj_ref535                luaL_unref(L, LUA_REGISTRYINDEX, rqu->obj_ref);536            } else if (rqu->obj_type == RQUEUE_TYPE_FGEN) {537                // don't need to recurse, just deref.538                mcp_funcgen_t *subfgen = rqu->obj;539                mcp_funcgen_dereference(L, subfgen);540            } else if (rqu->obj_type != RQUEUE_TYPE_NONE) {541                assert(1 == 0);542            }543        }544        free(fgen->queue_list);545    }546547    free(fgen->list);548    fgen->list = NULL;549    lua_pop(L, 1); // drop funcgen reference550}551552// Must be called with the function generator at on top of stack553// Pops the value from the stack.554void mcp_funcgen_reference(lua_State *L) {555    mcp_funcgen_t *fgen = luaL_checkudata(L, -1, "mcp.funcgen");556    if (fgen->self_ref) {557        fgen->refcount++;558        lua_pop(L, 1); // ensure we drop the extra value.559    } else {560        fgen->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);561        fgen->refcount = 1;562    }563    P_DEBUG("%s: funcgen referenced: %d\n", __func__, fgen->refcount);564}565566void mcp_funcgen_dereference(lua_State *L, mcp_funcgen_t *fgen) {567    assert(fgen->refcount > 0);568    fgen->refcount--;569    P_DEBUG("%s: funcgen dereferenced: %d\n", __func__, fgen->refcount);570    if (fgen->refcount == 0) {571        fgen->closed = true;572573        P_DEBUG("%s: funcgen cleaning up\n", __func__);574        if (fgen->free == fgen->total) {575            mcp_funcgen_cleanup(L, fgen);576        }577    }578}579580// All we need to do here is copy the function reference we've stashed into581// the C closure's upvalue and return it.582static int _mcplib_funcgenbare_generator(lua_State *L) {583    lua_pushvalue(L, lua_upvalueindex(1));584    return 1;585}586587// helper function to create a function generator with a "default" function.588// the function passed in here is a standard 'function(r) etc end' prototype,589// which we want to always return instead of calling a real generator590// function.591int mcplib_funcgenbare_new(lua_State *L) {592    if (!lua_isfunction(L, -1)) {593        proxy_lua_error(L, "Must pass a function to mcp.funcgenbare_new");594        return 0;595    }596597    // Pops the function into the upvalue of this C closure function.598    lua_pushcclosure(L, _mcplib_funcgenbare_generator, 1);599    // FIXME: not urgent, but this function chain isn't stack balanced, and its caller has600    // to drop an extra reference.601    // Need to re-audit and decide if we still need this pushvalue here or if602    // we can drop the pop from the caller and leave this function balanced.603    lua_pushvalue(L, -1);604    int gen_ref = luaL_ref(L, LUA_REGISTRYINDEX);605606    // Pass our fakeish generator function down the line.607    mcplib_funcgen_new(L);608609    mcp_funcgen_t *fgen = lua_touserdata(L, -1);610    strncpy(fgen->name, "anonymous", FGEN_NAME_MAXLEN);611    mcp_sharedvm_delta(fgen->thread->proxy_ctx, SHAREDVM_FGEN_IDX, fgen->name, 1);612613    fgen->generator_ref = gen_ref;614    fgen->ready = true;615    return 1;616}617618#define FGEN_DEFAULT_FREELIST_SIZE 8619int mcplib_funcgen_new(lua_State *L) {620    LIBEVENT_THREAD *t = PROXY_GET_THR(L);621622    mcp_funcgen_t *fgen = lua_newuserdatauv(L, sizeof(mcp_funcgen_t), 2);623    memset(fgen, 0, sizeof(mcp_funcgen_t));624    fgen->thread = t;625    fgen->free_max = FGEN_DEFAULT_FREELIST_SIZE;626    fgen->list = calloc(fgen->free_max, sizeof(mcp_rcontext_t *));627628    luaL_getmetatable(L, "mcp.funcgen");629    lua_setmetatable(L, -2);630631    // the table we will use to hold references to rctx's632    lua_createtable(L, 8, 0);633    // set our table into the uservalue 1 of fgen (idx -2)634    // pops the table.635    lua_setiuservalue(L, -2, 1);636637    return 1;638}639640int mcplib_funcgen_new_handle(lua_State *L) {641    mcp_funcgen_t *fgen = lua_touserdata(L, 1);642    mcp_pool_proxy_t *pp = NULL;643    mcp_funcgen_t *fg = NULL;644    void *test = NULL;645646    if (fgen->ready) {647        proxy_lua_error(L, "cannot modify function generator after calling ready");648        return 0;649    }650651    if ((pp = luaL_testudata(L, 2, "mcp.pool_proxy")) != NULL) {652        // good.653    } else if ((test = luaL_testudata(L, 2, "mcp.internal_be")) != NULL) {654        // also good.655    } else if ((fg = luaL_testudata(L, 2, "mcp.funcgen")) != NULL) {656        if (fg->is_router) {657            proxy_lua_error(L, "cannot assign a router to a handle in new_handle");658            return 0;659        }660        if (fg->closed) {661            proxy_lua_error(L, "cannot use a replaced function in new_handle");662            return 0;663        }664    } else {665        proxy_lua_error(L, "invalid argument to new_handle");666        return 0;667    }668669    fgen->max_queues++;670    if (fgen->queue_list == NULL) {671        fgen->queue_list = malloc(sizeof(struct mcp_rqueue_s));672    } else {673        fgen->queue_list = realloc(fgen->queue_list, fgen->max_queues * sizeof(struct mcp_rqueue_s));674    }675    if (fgen->queue_list == NULL) {676        proxy_lua_error(L, "failed to realloc queue list during new_handle()");677        return 0;678    }679680    struct mcp_rqueue_s *rqu = &fgen->queue_list[fgen->max_queues-1];681    memset(rqu, 0, sizeof(*rqu));682683    if (pp) {684        // pops pp from the stack685        rqu->obj_ref = luaL_ref(L, LUA_REGISTRYINDEX);686        rqu->obj_type = RQUEUE_TYPE_POOL;687        rqu->obj = pp;688    } else if (test) {689        // pops test from the stack690        rqu->obj_ref = luaL_ref(L, LUA_REGISTRYINDEX);691        rqu->obj_type = RQUEUE_TYPE_INT;692        rqu->obj = test;693    } else {694        // pops the fgen from the stack.695        mcp_funcgen_reference(L);696        rqu->obj_type = RQUEUE_TYPE_FGEN;697        rqu->obj = fg;698    }699700    lua_pushinteger(L, fgen->max_queues-1);701    return 1;702}703704int mcplib_funcgen_ready(lua_State *L) {705    mcp_funcgen_t *fgen = lua_touserdata(L, 1);706    luaL_checktype(L, 2, LUA_TTABLE);707708    if (fgen->ready) {709        proxy_lua_error(L, "cannot modify function generator after calling ready");710        return 0;711    }712713    if (lua_getfield(L, 2, "f") != LUA_TFUNCTION) {714        proxy_lua_error(L, "Must specify generator function ('f') to fgen:ready");715        return 0;716    }717    fgen->generator_ref = luaL_ref(L, LUA_REGISTRYINDEX);718719    if (lua_getfield(L, 2, "a") != LUA_TNIL) {720        fgen->argument_ref = luaL_ref(L, LUA_REGISTRYINDEX);721    } else {722        lua_pop(L, 1);723    }724725    if (lua_getfield(L, 2, "n") == LUA_TSTRING) {726        size_t len = 0;727        const char *name = lua_tolstring(L, -1, &len);728        strncpy(fgen->name, name, FGEN_NAME_MAXLEN);729    } else {730        strncpy(fgen->name, "anonymous", FGEN_NAME_MAXLEN);731        lua_pop(L, 1);732    }733734    if (lua_getfield(L, 2, "u") == LUA_TNUMBER) {735        int uobj_queues = luaL_checkinteger(L, -1);736        if (uobj_queues < 1 || uobj_queues > RQUEUE_UOBJ_MAX) {737            proxy_lua_ferror(L, "user obj ('u') in fgen:ready must be between 1 and %d", RQUEUE_UOBJ_MAX);738            return 0;739        }740        fgen->uobj_queues = uobj_queues;741    }742    lua_pop(L, 1);743744    // now we test the generator function and create the first slot.745    lua_pushvalue(L, 1); // copy the funcgen to pass into gencall746    lua_rawgeti(L, LUA_REGISTRYINDEX, fgen->generator_ref); // for gencall747    _mcplib_funcgen_gencall(L);748    lua_pop(L, 1); // drop extra funcgen ref.749750    // add us to the global state751    mcp_sharedvm_delta(fgen->thread->proxy_ctx, SHAREDVM_FGEN_IDX, fgen->name, 1);752753    fgen->ready = true;754    return 1;755}756757// Handlers for request contexts758759int mcplib_rcontext_handle_set_cb(lua_State *L) {760    mcp_rcontext_t *rctx = lua_touserdata(L, 1);761    luaL_checktype(L, 2, LUA_TNUMBER);762    luaL_checktype(L, 3, LUA_TFUNCTION);763764    int handle = lua_tointeger(L, 2);765    if (handle < 0 || handle >= rctx->fgen->max_queues) {766        proxy_lua_error(L, "invalid handle passed to queue_set_cb");767        return 0;768    }769770    struct mcp_rqueue_s *rqu = &rctx->qslots[handle];771    if (rqu->cb_ref) {772        luaL_unref(L, LUA_REGISTRYINDEX, rqu->cb_ref);773    }774    rqu->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);775776    return 0;777}778779// call with request object on top of stack.780// pops the request object781// FIXME: callers are doing a pushvalue(L, 2) and then in here we're also782// pushvalue(L, 2)783// Think this should just document as needing the request object top of stack784// and xmove without the extra push bits.785static void _mcplib_rcontext_queue(lua_State *L, mcp_rcontext_t *rctx, mcp_request_t *rq, int handle) {786    if (handle < 0 || handle >= rctx->fgen->max_queues) {787        proxy_lua_error(L, "attempted to enqueue an invalid handle");788        return;789    }790    struct mcp_rqueue_s *rqu = &rctx->qslots[handle];791792    if (rqu->state != RQUEUE_IDLE) {793        lua_pop(L, 1);794        return;795    }796797    // If we're queueing to an fgen, arm the coroutine while we have the798    // objects handy. Else this requires roundtripping a luaL_ref/luaL_unref799    // later.800    if (rqu->obj_type == RQUEUE_TYPE_FGEN) {801        mcp_rcontext_t *subrctx = rqu->obj;802        lua_pushvalue(L, 2); // duplicate the request obj803        lua_rawgeti(subrctx->Lc, LUA_REGISTRYINDEX, subrctx->function_ref);804        lua_xmove(L, subrctx->Lc, 1); // move the requet object.805    }806807    // hold the request reference.808    rqu->req_ref = luaL_ref(L, LUA_REGISTRYINDEX);809810    rqu->state = RQUEUE_QUEUED;811    rqu->rq = rq;812}813814// first arg is rcontext815// then a request object816// then either a handle (integer) or array style table of handles817int mcplib_rcontext_enqueue(lua_State *L) {818    mcp_rcontext_t *rctx = lua_touserdata(L, 1);819    mcp_request_t *rq = luaL_checkudata(L, 2, "mcp.request");820821    if (rctx->wait_mode != QWAIT_IDLE) {822        proxy_lua_error(L, "enqueue: cannot enqueue new requests while in a wait");823        return 0;824    }825826    if (!rq->pr.keytoken) {827        proxy_lua_error(L, "cannot queue requests without a key");828        return 0;829    }830831    int type = lua_type(L, 3);832    if (type == LUA_TNUMBER) {833        int handle = lua_tointeger(L, 3);834835        lua_pushvalue(L, 2);836        _mcplib_rcontext_queue(L, rctx, rq, handle);837    } else if (type == LUA_TTABLE) {838        unsigned int len = lua_rawlen(L, 3);839        for (int x = 0; x < len; x++) {840            type = lua_rawgeti(L, 3, x+1);841            if (type != LUA_TNUMBER) {842                proxy_lua_error(L, "invalid handle passed to queue via array table");843                return 0;844            }845846            int handle = lua_tointeger(L, 4);847            lua_pop(L, 1);848849            lua_pushvalue(L, 2);850            _mcplib_rcontext_queue(L, rctx, rq, handle);851        }852    } else {853        proxy_lua_error(L, "must pass a handle or a table to queue");854        return 0;855    }856857    return 0;858}859860// TODO: pre-generate a result object into sub-rctx's that we can pull up for861// this, instead of allocating outside of a protected call.862static void _mcp_resume_rctx_process_error(mcp_rcontext_t *rctx, struct mcp_rqueue_s *rqu) {863    // we have an error. need to mark the error into the parent rqu864    rqu->flags |= RQUEUE_R_ERROR|RQUEUE_R_ANY;865    mcp_resp_t *r = mcp_prep_bare_resobj(rctx->Lc, rctx->fgen->thread);866    r->status = MCMC_ERR;867    r->resp.code = MCMC_CODE_SERVER_ERROR;868    assert(rqu->res_ref == 0);869    rqu->res_ref = luaL_ref(rctx->Lc, LUA_REGISTRYINDEX);870    mcp_process_rqueue_return(rctx->parent, rctx->parent_handle, r);871    if (rctx->parent->wait_count) {872        mcp_process_rctx_wait(rctx->parent, rctx->parent_handle);873    }874}875876static void _mcp_start_rctx_process_error(mcp_rcontext_t *rctx, struct mcp_rqueue_s *rqu) {877    // we have an error. need to mark the error into the parent rqu878    rqu->flags |= RQUEUE_R_ERROR|RQUEUE_R_ANY;879    mcp_resp_t *r = mcp_prep_bare_resobj(rctx->Lc, rctx->fgen->thread);880    r->status = MCMC_ERR;881    r->resp.code = MCMC_CODE_SERVER_ERROR;882    assert(rqu->res_ref == 0);883    rqu->res_ref = luaL_ref(rctx->Lc, LUA_REGISTRYINDEX);884885    // queue an IO to return later.886    io_pending_proxy_t *p = mcp_queue_rctx_io(rctx->parent, NULL, NULL, r);887    p->return_cb = proxy_return_rqu_cb;888    p->queue_handle = rctx->parent_handle;889    p->background = true;890}891892static void mcp_start_subrctx(mcp_rcontext_t *rctx) {893    int res = proxy_run_rcontext(rctx);894    struct mcp_rqueue_s *rqu = &rctx->parent->qslots[rctx->parent_handle];895    if (res == LUA_OK) {896        int type = lua_type(rctx->Lc, 1);897        mcp_resp_t *r = NULL;898        if (type == LUA_TUSERDATA && (r = luaL_testudata(rctx->Lc, 1, "mcp.response")) != NULL) {899            // move stack result object into parent rctx rqu slot.900            assert(rqu->res_ref == 0);901            rqu->res_ref = luaL_ref(rctx->Lc, LUA_REGISTRYINDEX);902903            io_pending_proxy_t *p = mcp_queue_rctx_io(rctx->parent, NULL, NULL, r);904            p->return_cb = proxy_return_rqu_cb;905            p->queue_handle = rctx->parent_handle;906            // TODO: change name of property to fast-return once mcp.await is907            // retired.908            p->background = true;909        } else if (type == LUA_TSTRING) {910            // TODO: wrap with a resobj and parse it.911            // for now we bypass the rqueue process handling912            // meaning no callbacks/etc.913            assert(rqu->res_ref == 0);914            rqu->res_ref = luaL_ref(rctx->Lc, LUA_REGISTRYINDEX);915            rqu->flags |= RQUEUE_R_ANY;916            rqu->state = RQUEUE_COMPLETE;917            io_pending_proxy_t *p = mcp_queue_rctx_io(rctx->parent, NULL, NULL, NULL);918            p->return_cb = proxy_return_rqu_cb;919            p->queue_handle = rctx->parent_handle;920            p->background = true;921        } else {922            // generate a generic object with an error.923            _mcp_start_rctx_process_error(rctx, rqu);924        }925    } else if (res == LUA_YIELD) {926        // normal.927    } else {928        lua_pop(rctx->Lc, 1); // drop the error message.929        _mcp_start_rctx_process_error(rctx, rqu);930    }931}932933static void mcp_resume_rctx_from_cb(mcp_rcontext_t *rctx) {934    int res = proxy_run_rcontext(rctx);935    if (rctx->parent) {936        struct mcp_rqueue_s *rqu = &rctx->parent->qslots[rctx->parent_handle];937        if (res == LUA_OK) {938            mcp_rcontext_t *parent = rctx->parent;939            int handle = rctx->parent_handle;940            int type = lua_type(rctx->Lc, 1);941            mcp_resp_t *r = NULL;942            if (type == LUA_TUSERDATA && (r = luaL_testudata(rctx->Lc, 1, "mcp.response")) != NULL) {943                // move stack result object into parent rctx rqu slot.944                assert(rqu->res_ref == 0);945                rqu->res_ref = luaL_ref(rctx->Lc, LUA_REGISTRYINDEX);946                mcp_process_rqueue_return(rctx->parent, rctx->parent_handle, r);947            } else if (type == LUA_TSTRING) {948                // TODO: wrap with a resobj and parse it.949                // for now we bypass the rqueue process handling950                // meaning no callbacks/etc.951                assert(rqu->res_ref == 0);952                rqu->res_ref = luaL_ref(rctx->Lc, LUA_REGISTRYINDEX);953                rqu->flags |= RQUEUE_R_ANY;954                rqu->state = RQUEUE_COMPLETE;955            } else {956                // generate a generic object with an error.957                _mcp_resume_rctx_process_error(rctx, rqu);958                mcp_funcgen_return_rctx(rctx);959                return;960            }961962            // return ourself before telling the parent to wait.963            mcp_funcgen_return_rctx(rctx);964            if (parent->wait_count) {965                mcp_process_rctx_wait(parent, handle);966            }967        } else if (res == LUA_YIELD) {968            // normal.969        } else {970            lua_pop(rctx->Lc, 1); // drop the error message.971            _mcp_resume_rctx_process_error(rctx, rqu);972            mcp_funcgen_return_rctx(rctx);973        }974    }975}976977// This "Dummy" IO immediately resumes the yielded function, without a result978// attached.979static void proxy_return_rqu_dummy_cb(io_pending_t *pending) {980    io_pending_proxy_t *p = (io_pending_proxy_t *)pending;981    mcp_rcontext_t *rctx = p->rctx;982983    rctx->pending_reqs--;984    assert(rctx->pending_reqs > -1);985986    lua_settop(rctx->Lc, 0);987    lua_pushinteger(rctx->Lc, 0); // return a "0" done count to the function.988    mcp_resume_rctx_from_cb(rctx);989990    do_cache_free(p->thread->io_cache, p);991}992993void mcp_process_rctx_wait(mcp_rcontext_t *rctx, int handle) {994    struct mcp_rqueue_s *rqu = &rctx->qslots[handle];995    int status = rqu->flags;996    assert(rqu->state == RQUEUE_COMPLETE);997    // waiting for some IO's to complete before continuing.998    // meaning if we "match good" here, we can resume.999    // we can also resume if we are in wait mode but pending_reqs is down1000    // to 1.1001    switch (rctx->wait_mode) {1002        case QWAIT_IDLE:1003            // should be impossible to get here.1004            // TODO: find a better path for throwing real errors from these1005            // side cases. would feel better long term.1006            abort();1007            break;1008        case QWAIT_GOOD:1009            if (status & RQUEUE_R_GOOD) {1010                rctx->wait_done++;1011                rqu->state = RQUEUE_WAITED;1012            }1013            break;1014        case QWAIT_OK:1015            if (status & (RQUEUE_R_GOOD|RQUEUE_R_OK)) {1016                rctx->wait_done++;1017                rqu->state = RQUEUE_WAITED;1018            }1019            break;1020        case QWAIT_ANY:1021            rctx->wait_done++;1022            rqu->state = RQUEUE_WAITED;1023            break;1024        case QWAIT_FASTGOOD:1025            if (status & RQUEUE_R_GOOD) {1026                rctx->wait_done++;1027                rqu->state = RQUEUE_WAITED;1028                // resume early if "good"1029                status |= RQUEUE_R_RESUME;1030            } else if (status & RQUEUE_R_OK) {1031                // count but don't resume early if "ok"1032                rctx->wait_done++;1033                rqu->state = RQUEUE_WAITED;1034            }1035            break;1036        case QWAIT_HANDLE:1037            // waiting for a specific handle to return1038            if (handle == rctx->wait_handle) {1039                rctx->wait_done++;1040                rqu->state = RQUEUE_WAITED;1041            }1042            break;1043        case QWAIT_SLEEP:1044            assert(1 == 0); // should not get here.1045            break;1046    }10471048    assert(rctx->pending_reqs != 0);1049    bool should_resume = (status & RQUEUE_R_RESUME);1050    if (rctx->wait_done == rctx->wait_count || rctx->pending_reqs == 1) {1051        should_resume = true;1052    } else if (status & RQUEUE_R_ERROR) {1053        // An error condition could happen without the pending req1054        // decrementing. Since this should be a rare state we do a slow check1055        // on if the wait result is still possible.1056        int possible = 0;1057        // move an errored RQU to a WAITED state so we don't block the wait on1058        // it during a future error.1059        rqu->state = RQUEUE_WAITED;1060        for (int x = 0; x < rctx->fgen->max_queues; x++) {1061            struct mcp_rqueue_s *rqu = &rctx->qslots[x];1062            if (rqu->state != RQUEUE_IDLE && rqu->state != RQUEUE_WAITED) {1063                possible++;1064            }1065        }1066        if (possible == 0) {1067            should_resume = true;1068        }1069    }10701071    if (should_resume) {1072        // ran out of stuff to wait for. time to resume.1073        // TODO: can we do the settop at the yield? nothing we need to1074        // keep in the stack in this mode.1075        lua_settop(rctx->Lc, 0);1076        rctx->wait_count = 0;1077        if (rctx->wait_mode == QWAIT_HANDLE) {1078            mcp_rcontext_push_rqu_res(rctx->Lc, rctx, handle);1079        } else {1080            lua_pushinteger(rctx->Lc, rctx->wait_done);1081        }1082        rctx->wait_mode = QWAIT_IDLE;10831084        // nuke alarm if set.1085        if (event_pending(&rctx->timeout_event, EV_TIMEOUT, NULL)) {1086            event_del(&rctx->timeout_event);1087        }10881089        mcp_resume_rctx_from_cb(rctx);1090    }1091}10921093// sets the slot's return status code, to be used for filtering responses1094// later.1095// if a callback was set, execute it now.1096int mcp_process_rqueue_return(mcp_rcontext_t *rctx, int handle, mcp_resp_t *res) {1097    struct mcp_rqueue_s *rqu = &rctx->qslots[handle];1098    uint8_t flag = RQUEUE_R_ANY;10991100    assert(rqu->state == RQUEUE_ACTIVE);1101    rqu->state = RQUEUE_COMPLETE;1102    if (res->status == MCMC_OK) {1103        if (res->resp.code != MCMC_CODE_END) {1104            flag = RQUEUE_R_GOOD;1105        } else {1106            flag = RQUEUE_R_OK;1107        }1108    } else {1109        flag |= RQUEUE_R_ERROR;1110    }11111112    if (rqu->cb_ref) {1113        lua_settop(rctx->Lc, 0);1114        lua_rawgeti(rctx->Lc, LUA_REGISTRYINDEX, rqu->cb_ref);1115        lua_rawgeti(rctx->Lc, LUA_REGISTRYINDEX, rqu->res_ref);1116        lua_rawgeti(rctx->Lc, LUA_REGISTRYINDEX, rqu->req_ref);1117        if (lua_pcall(rctx->Lc, 2, 2, 0) != LUA_OK) {1118            LOGGER_LOG(NULL, LOG_PROXYEVENTS, LOGGER_PROXY_ERROR, NULL, lua_tostring(rctx->Lc, -1));1119        } else if (lua_isinteger(rctx->Lc, 1)) {1120            // allow overriding the result flag from the callback.1121            enum mcp_rqueue_e mode = lua_tointeger(rctx->Lc, 1);1122            switch (mode) {1123                case QWAIT_GOOD:1124                    flag = RQUEUE_R_GOOD;1125                    break;1126                case QWAIT_OK:1127                    flag = RQUEUE_R_OK;1128                    break;1129                case QWAIT_ANY:1130                    break;1131                default:1132                    // ANY1133                    break;1134            }11351136            // if second result return shortcut status code1137            if (lua_toboolean(rctx->Lc, 2)) {1138                flag |= RQUEUE_R_RESUME;1139            }1140        }1141        lua_settop(rctx->Lc, 0); // FIXME: This might not be necessary.1142                                 // we settop _before_ calling cb's and1143                                 // _before_ setting up for a coro resume.1144    }11451146    rqu->flags |= flag;1147    return rqu->flags;1148}11491150// specific function for queue-based returns.1151static void proxy_return_rqu_cb(io_pending_t *pending) {1152    io_pending_proxy_t *p = (io_pending_proxy_t *)pending;1153    mcp_rcontext_t *rctx = p->rctx;11541155    if (p->client_resp) {1156        mcp_resp_t *res = p->client_resp;1157        if (res->blen) {1158            res->thread->proxy_buffer_memory_used += res->blen;1159        }1160        mcp_process_rqueue_return(rctx, p->queue_handle, res);1161        if (res->be && res->be->use_logging) {1162            struct mcp_rqueue_s *rqu = &rctx->qslots[p->queue_handle];1163            int conn_fd = 0;1164            // TODO: would be nice to have fast-access to top level.1165            mcp_rcontext_t *n_rctx = rctx;1166            while (n_rctx) {1167                if (!n_rctx->parent) {1168                    conn_fd = n_rctx->conn_fd;1169                    break;1170                }1171                n_rctx = n_rctx->parent;1172            }1173            mcplib_rqu_log(rqu->rq, res, rqu->flags, conn_fd);1174        }1175    }1176    rctx->pending_reqs--;1177    assert(rctx->pending_reqs > -1);11781179    if (rctx->wait_count) {1180        mcp_process_rctx_wait(rctx, p->queue_handle);1181    } else {1182        mcp_funcgen_return_rctx(rctx);1183    }11841185    do_cache_free(p->thread->io_cache, p);1186}11871188void mcp_run_rcontext_handle(mcp_rcontext_t *rctx, int handle) {1189    struct mcp_rqueue_s *rqu = NULL;1190    rqu = &rctx->qslots[handle];11911192    if (rqu->state == RQUEUE_QUEUED) {1193        rqu->state = RQUEUE_ACTIVE;1194        if (rqu->obj_type == RQUEUE_TYPE_POOL) {1195            mcp_request_t *rq = rqu->rq;1196            mcp_backend_t *be = mcplib_pool_proxy_call_helper(rqu->obj, MCP_PARSER_KEY(&rq->pr), rq->pr.klen);11971198            mcp_set_resobj(rqu->res_obj, rq, be, rctx->fgen->thread);1199            io_pending_proxy_t *p = mcp_queue_rctx_io(rctx, rq, be, rqu->res_obj);1200            p->return_cb = proxy_return_rqu_cb;1201            p->queue_handle = handle;1202            rctx->pending_reqs++;1203        } else if (rqu->obj_type == RQUEUE_TYPE_INT) {1204            mcp_request_t *rq = rqu->rq;1205            mc_resp *resp = mcp_rcontext_internal(rctx, rq, rqu->res_obj);1206            if (resp == NULL) {1207                // NOTE: This can be OOM (no resp alloc)1208                // or bad parse (no such command)1209                // we _could_ set an ERRMSG here.1210                mcp_resp_t *r = rqu->res_obj;1211                r->status = MCMC_ERR;1212                r->resp.code = MCMC_CODE_SERVER_ERROR;1213                io_pending_proxy_t *p = mcp_queue_rctx_io(rctx, NULL, NULL, rqu->res_obj);1214                p->return_cb = proxy_return_rqu_cb;1215                p->queue_handle = handle;1216                p->background = true;1217                rctx->pending_reqs++;1218            } else if (resp->io_pending) {1219                resp->io_pending->return_cb = proxy_return_rqu_cb;1220                // Add io object to extstore submission queue.1221                io_queue_t *q = thread_io_queue_get(rctx->fgen->thread, IO_QUEUE_EXTSTORE);1222                io_pending_proxy_t *io = (io_pending_proxy_t *)resp->io_pending;1223                io->queue_handle = handle;1224                io->client_resp = rqu->res_obj;12251226                STAILQ_INSERT_TAIL(&q->stack, (io_pending_t *)io, iop_next);12271228                io->rctx = rctx;1229                io->c = rctx->c;1230                // mark the buffer into the mcp_resp for freeing later.1231                rqu->res_obj->buf = io->eio.buf;1232                rctx->pending_reqs++;1233            } else {1234                io_pending_proxy_t *p = mcp_queue_rctx_io(rctx, NULL, NULL, rqu->res_obj);1235                p->return_cb = proxy_return_rqu_cb;1236                p->queue_handle = handle;1237                p->background = true;1238                rctx->pending_reqs++;1239            }1240        } else if (rqu->obj_type == RQUEUE_TYPE_FGEN) {1241            // TODO: NULL the ->c post-return?1242            mcp_rcontext_t *subrctx = rqu->obj;1243            subrctx->c = rctx->c;1244            subrctx->pending_reqs++;1245            rctx->pending_reqs++;1246            mcp_start_subrctx(subrctx);1247        } else {1248            assert(1==0);1249        }1250    } else if (rqu->state == RQUEUE_COMPLETE && rctx->wait_count) {1251        // The slot was previously completed from an earlier dispatch, but we1252        // haven't "waited" on it yet.1253        mcp_process_rctx_wait(rctx, handle);1254    }1255}12561257static inline void _mcplib_set_rctx_alarm(lua_State *L, mcp_rcontext_t *rctx, int arg) {1258    int isnum = 0;1259    lua_Number secondsf = lua_tonumberx(L, arg, &isnum);1260    if (!isnum) {1261        proxy_lua_error(L, "timeout argument to wait or sleep must be a number");1262        return;1263    }1264    int pending = event_pending(&rctx->timeout_event, EV_TIMEOUT, NULL);1265    if ((pending & (EV_TIMEOUT)) == 0) {1266        struct timeval tv = { .tv_sec = 0, .tv_usec = 0 };1267        lua_Integer secondsi = (lua_Integer) secondsf;1268        lua_Number subseconds = secondsf - secondsi;12691270        tv.tv_sec = secondsi;1271        tv.tv_usec = MICROSECONDS(subseconds);1272        event_add(&rctx->timeout_event, &tv);1273    }1274}12751276// TODO: one more function to wait on a list of handles? to queue and wait on1277// a list of handles? expand wait_cond()12781279static inline int _mcplib_rcontext_wait_prep(lua_State *L, mcp_rcontext_t *rctx, int argc) {1280    int mode = QWAIT_ANY;1281    int wait = 0;12821283    if (rctx->wait_mode != QWAIT_IDLE) {1284        proxy_lua_error(L, "wait_cond: cannot call while already in wait mode");1285        return 0;1286    }12871288    if (argc < 2) {1289        proxy_lua_error(L, "must pass at least count to wait_cond");1290        return 0;1291    }12921293    int isnum = 0;1294    wait = lua_tointegerx(L, 2, &isnum);1295    if (!isnum || wait < 0) {1296        proxy_lua_error(L, "wait count for wait_cond must be a positive integer");1297        return 0;1298    }12991300    if (argc > 2) {1301        mode = lua_tointeger(L, 3);1302    }13031304    switch (mode) {1305        case QWAIT_ANY:1306        case QWAIT_OK:1307        case QWAIT_GOOD:1308        case QWAIT_FASTGOOD:1309            break;1310        default:1311            proxy_lua_error(L, "invalid mode sent to wait_cond");1312            return 0;1313    }13141315    rctx->wait_count = wait;1316    rctx->wait_done = 0;1317    rctx->wait_mode = mode;13181319    return 0;1320}13211322// takes num, filter mode1323int mcplib_rcontext_wait_cond(lua_State *L) {1324    int argc = lua_gettop(L);1325    mcp_rcontext_t *rctx = lua_touserdata(L, 1);13261327    _mcplib_rcontext_wait_prep(L, rctx, argc);13281329    // waiting for none, meaning just execute the queues.1330    if (rctx->wait_count == 0) {1331        io_pending_proxy_t *p = mcp_queue_rctx_io(rctx, NULL, NULL, NULL);1332        p->return_cb = proxy_return_rqu_dummy_cb;1333        p->background = true;1334        rctx->pending_reqs++;1335        rctx->wait_mode = QWAIT_IDLE; // not actually waiting.1336    } else if (argc > 3) {1337        // optional wait timeout. does not cancel existing request!1338        _mcplib_set_rctx_alarm(L, rctx, 4);1339    }13401341    lua_pushinteger(L, MCP_YIELD_WAITCOND);1342    return lua_yield(L, 1);1343}13441345int mcplib_rcontext_enqueue_and_wait(lua_State *L) {1346    mcp_rcontext_t *rctx = lua_touserdata(L, 1);1347    mcp_request_t *rq = luaL_checkudata(L, 2, "mcp.request");1348    int isnum = 0;1349    int handle = lua_tointegerx(L, 3, &isnum);13501351    if (rctx->wait_mode != QWAIT_IDLE) {1352        proxy_lua_error(L, "wait_cond: cannot call while already in wait mode");1353        return 0;1354    }13551356    if (!rq->pr.keytoken) {1357        proxy_lua_error(L, "cannot queue requests without a key");1358        return 0;1359    }13601361    if (!isnum) {1362        proxy_lua_error(L, "invalid handle passed to enqueue_and_wait");1363        return 0;1364    }13651366    // queue up this handle and yield for the direct wait.1367    lua_pushvalue(L, 2);1368    _mcplib_rcontext_queue(L, rctx, rq, handle);13691370    if (lua_gettop(L) > 3) {1371        _mcplib_set_rctx_alarm(L, rctx, 4);1372    }13731374    rctx->wait_done = 0;1375    rctx->wait_count = 1;1376    rctx->wait_mode = QWAIT_HANDLE;1377    rctx->wait_handle = handle;13781379    lua_pushinteger(L, MCP_YIELD_WAITHANDLE);1380    return lua_yield(L, 1);1381}13821383int mcplib_rcontext_wait_handle(lua_State *L) {1384    mcp_rcontext_t *rctx = lua_touserdata(L, 1);1385    int isnum = 0;1386    int handle = lua_tointegerx(L, 2, &isnum);13871388    if (rctx->wait_mode != QWAIT_IDLE) {1389        proxy_lua_error(L, "wait: cannot call while already in wait mode");1390        return 0;1391    }13921393    if (!isnum || handle < 0 || handle >= rctx->fgen->max_queues) {1394        proxy_lua_error(L, "invalid handle passed to wait_handle");1395        return 0;1396    }13971398    struct mcp_rqueue_s *rqu = &rctx->qslots[handle];1399    if (rqu->state == RQUEUE_IDLE) {1400        proxy_lua_error(L, "wait_handle called on unqueued handle");1401        return 0;1402    }14031404    if (lua_gettop(L) > 2) {1405        _mcplib_set_rctx_alarm(L, rctx, 3);1406    }14071408    rctx->wait_done = 0;1409    rctx->wait_count = 1;1410    rctx->wait_mode = QWAIT_HANDLE;1411    rctx->wait_handle = handle;14121413    lua_pushinteger(L, MCP_YIELD_WAITHANDLE);1414    return lua_yield(L, 1);1415}14161417int mcplib_rcontext_sleep(lua_State *L) {1418    mcp_rcontext_t *rctx = lua_touserdata(L, 1);1419    if (rctx->wait_mode != QWAIT_IDLE) {1420        proxy_lua_error(L, "sleep: cannot call while already in wait mode");1421        return 0;1422    };14231424    _mcplib_set_rctx_alarm(L, rctx, 2);1425    rctx->wait_mode = QWAIT_SLEEP;14261427    lua_pushinteger(L, MCP_YIELD_SLEEP);1428    return lua_yield(L, 1);1429}14301431static inline struct mcp_rqueue_s *_mcplib_rcontext_checkhandle(lua_State *L) {1432    mcp_rcontext_t *rctx = lua_touserdata(L, 1);1433    int isnum = 0;1434    int handle = lua_tointegerx(L, 2, &isnum);1435    if (!isnum || handle < 0 || handle >= rctx->fgen->max_queues) {1436        proxy_lua_error(L, "invalid queue handle passed to :good/:ok:/:any");1437        return NULL;1438    }14391440    struct mcp_rqueue_s *rqu = &rctx->qslots[handle];1441    return rqu;1442}14431444int mcplib_rcontext_res_good(lua_State *L) {1445    struct mcp_rqueue_s *rqu = _mcplib_rcontext_checkhandle(L);1446    if (rqu->flags & RQUEUE_R_GOOD) {1447        lua_rawgeti(L, LUA_REGISTRYINDEX, rqu->res_ref);1448    } else {1449        lua_pushnil(L);1450    }1451    return 1;1452}14531454int mcplib_rcontext_res_ok(lua_State *L) {1455    struct mcp_rqueue_s *rqu = _mcplib_rcontext_checkhandle(L);1456    if (rqu->flags & (RQUEUE_R_OK|RQUEUE_R_GOOD)) {1457        lua_rawgeti(L, LUA_REGISTRYINDEX, rqu->res_ref);1458    } else {1459        lua_pushnil(L);1460    }1461    return 1;1462}14631464int mcplib_rcontext_res_any(lua_State *L) {1465    struct mcp_rqueue_s *rqu = _mcplib_rcontext_checkhandle(L);1466    if (rqu->flags & (RQUEUE_R_ANY|RQUEUE_R_OK|RQUEUE_R_GOOD)) {1467        lua_rawgeti(L, LUA_REGISTRYINDEX, rqu->res_ref);1468    } else {1469        // Shouldn't be possible to get here, unless you're asking about a1470        // queue that was never armed or hasn't completed yet.1471        lua_pushnil(L);1472    }1473    return 1;1474}14751476// returns res, RES_GOOD|OK|ANY1477int mcplib_rcontext_result(lua_State *L) {1478    struct mcp_rqueue_s *rqu = _mcplib_rcontext_checkhandle(L);1479    if (rqu->flags & (RQUEUE_R_ANY|RQUEUE_R_OK|RQUEUE_R_GOOD)) {1480        lua_rawgeti(L, LUA_REGISTRYINDEX, rqu->res_ref);1481        // mask away any other queue flags.1482        lua_pushinteger(L, rqu->flags & (RQUEUE_R_ANY|RQUEUE_R_OK|RQUEUE_R_GOOD));1483    } else {1484        lua_pushnil(L);1485        lua_pushnil(L);1486    }14871488    return 2;1489}14901491// arg must be an array table.1492// returns res, GOOD|OK|ANY1493// tries to find a result in that order.1494int mcplib_rcontext_best_result(lua_State *L) {1495    mcp_rcontext_t *rctx = lua_touserdata(L, 1);14961497    if (lua_istable(L, 2)) {1498        int final_handle = -1;1499        int final_flag = -1;1500        unsigned int len = lua_rawlen(L, 2);1501        for (int x = 0; x < len; x++) {1502            lua_rawgeti(L, 2, x+1);1503            int handle = lua_tointeger(L, 3);1504            lua_pop(L, 1);15051506            if (handle < 0 || handle >= rctx->fgen->max_queues) {1507                proxy_lua_error(L, "invalid queue handle passed to best_result");1508            }15091510            struct mcp_rqueue_s *rqu = &rctx->qslots[handle];1511            if (!rqu->flags) {1512                continue; // error or unprocessed.1513            } else if (rqu->flags & RQUEUE_R_GOOD) {1514                final_handle = handle;1515                break;1516            } else if (rqu->flags & RQUEUE_R_OK) {1517                final_handle = handle;1518                final_flag = RQUEUE_R_OK;1519            } else if (final_flag != RQUEUE_R_OK) {1520                // only use an error if we don't already have an OK1521                final_handle = handle;1522            }1523        }15241525        if (final_handle != -1) {1526            struct mcp_rqueue_s *rqu = &rctx->qslots[final_handle];1527            lua_rawgeti(L, LUA_REGISTRYINDEX, rqu->res_ref);1528            lua_pushinteger(L, rqu->flags & (RQUEUE_R_ANY|RQUEUE_R_OK|RQUEUE_R_GOOD));1529        } else {1530            lua_pushnil(L);1531            lua_pushnil(L);1532        }1533    } else {1534        proxy_lua_error(L, "must pass a table to :best_result");1535    }1536    return 2;1537}15381539// arg must be an array table.1540// returns res, ANY|OK|GOOD1541// tries to find a result in that order.1542// TODO: test with incomplete requests? not sure what the data looks like.1543int mcplib_rcontext_worst_result(lua_State *L) {1544    mcp_rcontext_t *rctx = lua_touserdata(L, 1);15451546    if (lua_istable(L, 2)) {1547        int final_handle = -1;1548        int final_flags = 0;1549        unsigned int len = lua_rawlen(L, 2);1550        for (int x = 0; x < len; x++) {1551            lua_rawgeti(L, 2, x+1);1552            int handle = lua_tointeger(L, 3);1553            lua_pop(L, 1);15541555            if (handle < 0 || handle >= rctx->fgen->max_queues) {1556                proxy_lua_error(L, "invalid queue handle passed to worst_result");1557            }15581559            struct mcp_rqueue_s *rqu = &rctx->qslots[handle];1560            if (!rqu->flags) {1561                continue;1562            } else if (final_flags <= rqu->flags) {1563                // flag values increase by how bad they are.1564                final_handle = handle;1565                final_flags = rqu->flags;1566            }1567        }15681569        if (final_handle != -1) {1570            struct mcp_rqueue_s *rqu = &rctx->qslots[final_handle];1571            lua_rawgeti(L, LUA_REGISTRYINDEX, rqu->res_ref);1572            lua_pushinteger(L, rqu->flags & (RQUEUE_R_ANY|RQUEUE_R_OK|RQUEUE_R_GOOD));1573        } else {1574            lua_pushnil(L);1575            lua_pushnil(L);1576        }1577    } else {1578        proxy_lua_error(L, "must pass a table to :worst_result");1579    }1580    return 2;1581}15821583int mcplib_rcontext_cfd(lua_State *L) {1584    mcp_rcontext_t *rctx = lua_touserdata(L, 1);1585    lua_pushinteger(L, rctx->conn_fd);1586    return 1;1587}15881589// Must not call this if rctx has returned result to client already.1590int mcplib_rcontext_tls_peer_cn(lua_State *L) {1591    mcp_rcontext_t *rctx = lua_touserdata(L, 1);1592    if (!rctx->c) {1593        lua_pushnil(L);1594        return 1;1595    }15961597#ifdef TLS1598    int len = 0;1599    const unsigned char *cn = ssl_get_peer_cn(rctx->c, &len);1600    if (cn) {1601        lua_pushlstring(L, (const char *)cn, len);1602    } else {1603        lua_pushnil(L);1604    }1605#else1606    lua_pushnil(L);1607#endif1608    return 1;1609}16101611// call with uobj on top of stack1612static void _mcplib_rcontext_ref_uobj(lua_State *L, mcp_rcontext_t *rctx, void *obj, int otype) {1613    lua_pushvalue(L, -1); // dupe rq for the rqueue slot1614    struct mcp_rqueue_s *rqu = &rctx->qslots[rctx->fgen->max_queues + rctx->uobj_count];1615    rctx->uobj_count++;1616    // hold the request reference into the rctx for memory management.1617    rqu->obj_ref = luaL_ref(L, LUA_REGISTRYINDEX);1618    rqu->obj_type = otype;1619    rqu->obj = obj;1620}16211622// Creates request object that's tracked by request context so we can call1623// cleanup routines post-run.1624int mcplib_rcontext_request_new(lua_State *L) {1625    mcp_rcontext_t *rctx = lua_touserdata(L, 1);1626    if (rctx->uobj_count == rctx->fgen->uobj_queues) {1627        proxy_lua_error(L, "rctx request new: object count limit reached");1628        return 0;1629    }16301631    // create new request object1632    mcp_parser_t pr = {0};1633    mcp_request_t *rq = mcp_new_request(L, &pr, " ", 1);16341635    _mcplib_rcontext_ref_uobj(L, rctx, rq, RQUEUE_TYPE_UOBJ_REQ);1636    return 1;1637}16381639int mcplib_rcontext_response_new(lua_State *L) {1640    mcp_rcontext_t *rctx = lua_touserdata(L, 1);1641    if (rctx->uobj_count == rctx->fgen->uobj_queues) {1642        proxy_lua_error(L, "rctx request new: object count limit reached");1643        return 0;1644    }16451646    mcp_resp_t *r = lua_newuserdatauv(L, sizeof(mcp_resp_t), 0);1647    memset(r, 0, sizeof(mcp_resp_t));1648    luaL_getmetatable(L, "mcp.response");1649    lua_setmetatable(L, -2);16501651    _mcplib_rcontext_ref_uobj(L, rctx, r, RQUEUE_TYPE_UOBJ_RES);1652    return 1;1653}16541655// the supplied handle must be valid.1656void mcp_rcontext_push_rqu_res(lua_State *L, mcp_rcontext_t *rctx, int handle) {1657    struct mcp_rqueue_s *rqu = &rctx->qslots[handle];1658    lua_rawgeti(L, LUA_REGISTRYINDEX, rqu->res_ref);1659}16601661/*1662 * Specialized router funcgen.1663 * For routing a key across a map of possible function generators, we use a1664 * specialized function generator. This is to keep the attach and start code1665 * consistent, as they only need to think about function generators.1666 * It also keeps the cleanup code consistent, as when a "router" funcgen is1667 * replaced by mcp.attach() during a reload, we can immediately dereference1668 * all of the route fgens, rather than have to wait for GC.1669 *1670 * Another upside is when we're starting a new request, we can immediately1671 * swap out the top level fgen object, rather than force all routes to be1672 * processed as sub-funcs, which is a tiny bit slower and disallows custom1673 * request object sizes.1674 *1675 * The downside is this will appear to be bolted onto the side of the existing1676 * structs rather than be its own object, like I initially wanted.1677 */16781679static inline const char *_mcp_router_shortsep(const char *key, const int klen, const char needle, size_t *len) {1680    const char *end = NULL;1681    const char *lookup = NULL;16821683    end = memchr(key, needle, klen);1684    if (end == NULL) {1685        lookup = key;1686    } else {1687        lookup = key;1688        *len = end - key;1689    }16901691    return lookup;1692}16931694// we take some liberties here because we know needle and key can't be zero1695// this isn't the most hyper optimized search but prefixes and separators1696// should both be short.1697static inline const char *_mcp_router_longsep(const char *key, const int klen, const char *needle, size_t *len) {1698    const char *end = NULL;1699    const char *lookup = key;1700    size_t nlen = strlen(needle);17011702    end = memchr(key, needle[0], klen);1703    if (end == NULL) {1704        // definitely no needle in this haystack.1705        return key;1706    }17071708    // find the last possible position1709    const char *last = key + (klen - nlen);17101711    while (end <= last) {1712        if (*end == needle[0] && memcmp(end, needle, nlen) == 0) {1713            lookup = key;1714            *len = end - key;1715            break;1716        }1717        end++;1718    }17191720    return lookup;1721}17221723static inline const char *_mcp_router_anchorsm(const char *key, const int klen, const char *needle, size_t *len) {1724    // check the first byte anchor.1725    if (key[0] != needle[0]) {1726        return NULL;1727    }17281729    // rest is same as shortsep.1730    return _mcp_router_shortsep(key+1, klen-1, needle[1], len);1731}17321733static inline const char *_mcp_router_anchorbig(const char *key, const int klen, const struct mcp_router_long_s *conf, size_t *len) {1734    // check long anchored prefix.1735    size_t slen = strlen(conf->start);1736    // check for start len+2 to avoid sending a zero byte haystack to longsep1737    if (slen+2 > klen || memcmp(key, conf->start, slen) != 0) {1738        return NULL;1739    }17401741    // rest is same as longsep1742    return _mcp_router_longsep(key+slen, klen-slen, conf->stop, len);1743}17441745static inline mcp_funcgen_t *_mcp_funcgen_route_fallback(struct mcp_funcgen_router *fr, int cmd) {1746    if (fr->cmap[cmd]) {1747        return fr->cmap[cmd];1748    }1749    return fr->def_fgen;1750}17511752static mcp_funcgen_t *mcp_funcgen_route(lua_State *L, mcp_funcgen_t *fgen, mcp_parser_t *pr) {1753    struct mcp_funcgen_router *fr = (struct mcp_funcgen_router *)fgen;1754    if (pr->klen == 0) {1755        return NULL;1756    }1757    const char *key = &pr->request[pr->tok.tokens[pr->keytoken]];1758    const char *lookup = NULL;1759    size_t lookuplen = 0;1760    switch(fr->type) {1761        case FGEN_ROUTER_NONE:1762            break;1763        case FGEN_ROUTER_CMDMAP:1764            // short circuit if all we can do is cmap and default.1765            return _mcp_funcgen_route_fallback(fr, pr->command);1766            break;1767        case FGEN_ROUTER_SHORTSEP:1768            lookup = _mcp_router_shortsep(key, pr->klen, fr->conf.sep, &lookuplen);1769            break;1770        case FGEN_ROUTER_LONGSEP:1771            lookup = _mcp_router_longsep(key, pr->klen, fr->conf.lsep, &lookuplen);1772            break;1773        case FGEN_ROUTER_ANCHORSM:1774            lookup = _mcp_router_anchorsm(key, pr->klen, fr->conf.anchorsm, &lookuplen);1775            break;1776        case FGEN_ROUTER_ANCHORBIG:1777            lookup = _mcp_router_anchorbig(key, pr->klen, &fr->conf.big, &lookuplen);1778            break;1779    }17801781    if (lookuplen == 0) {1782        return _mcp_funcgen_route_fallback(fr, pr->command);1783    }17841785    // hoping the lua short string cache helps us avoid allocations at least.1786    // since this lookup code is internal to the router object we can optimize1787    // this later and remove the lua bits.1788    lua_rawgeti(L, LUA_REGISTRYINDEX, fr->map_ref);1789    lua_pushlstring(L, lookup, lookuplen);1790    lua_rawget(L, -2); // pops key, returns value1791    if (lua_isnil(L, -1)) {1792        lua_pop(L, 2); // drop nil and map.1793        return _mcp_funcgen_route_fallback(fr, pr->command);1794    } else {1795        int type = lua_type(L, -1);1796        if (type == LUA_TUSERDATA) {1797            mcp_funcgen_t *nfgen = lua_touserdata(L, -1);1798            lua_pop(L, 2); // drop fgen and map.1799            return nfgen;1800        } else if (type == LUA_TTABLE) {1801            lua_rawgeti(L, -1, pr->command);1802            // If nil, check CMD_ANY_STORAGE index for a cmap default1803            if (lua_isnil(L, -1)) {1804                lua_pop(L, 1); // drop nil.1805                // check if we have a local-default1806                lua_rawgeti(L, -1, CMD_ANY_STORAGE);1807                if (lua_isnil(L, -1)) {1808                    lua_pop(L, 3); // drop map, cmd map, nil1809                    return _mcp_funcgen_route_fallback(fr, pr->command);1810                } else {1811                    mcp_funcgen_t *nfgen = lua_touserdata(L, -1);1812                    lua_pop(L, 3); // drop map, cmd map, fgen1813                    return nfgen;1814                }1815            }1816            mcp_funcgen_t *nfgen = lua_touserdata(L, -1);1817            lua_pop(L, 3); // drop fgen, cmd map, map1818            return nfgen;1819        } else {1820            return _mcp_funcgen_route_fallback(fr, pr->command);1821        }1822    }1823}18241825// called from mcp_funcgen_cleanup if necessary.1826static int mcp_funcgen_router_cleanup(lua_State *L, mcp_funcgen_t *fgen) {1827    struct mcp_funcgen_router *fr = (struct mcp_funcgen_router *)fgen;1828    if (fr->map_ref) {1829        lua_rawgeti(L, LUA_REGISTRYINDEX, fr->map_ref);18301831        // walk the map, de-ref any funcgens found.1832        int tidx = lua_absindex(L, -1);1833        lua_pushnil(L);1834        while (lua_next(L, tidx) != 0) {1835            int type = lua_type(L, -1);1836            if (type == LUA_TUSERDATA) {1837                mcp_funcgen_t *mfgen = lua_touserdata(L, -1);1838                mcp_funcgen_dereference(L, mfgen);1839                lua_pop(L, 1);1840            } else if (type == LUA_TTABLE) {1841                int midx = lua_absindex(L, -1);1842                lua_pushnil(L);1843                while (lua_next(L, midx) != 0) {1844                    mcp_funcgen_t *mfgen = lua_touserdata(L, -1);1845                    mcp_funcgen_dereference(L, mfgen);1846                    lua_pop(L, 1); // drop value1847                }1848                lua_pop(L, 1); // drop command map table1849            }1850        }18511852        lua_pop(L, 1); // drop the table.1853        luaL_unref(L, LUA_REGISTRYINDEX, fr->map_ref);1854        fr->map_ref = 0;1855    }18561857    // release any command map entries.1858    for (int x = 0; x < CMD_END_STORAGE; x++) {1859        if (fr->cmap[x]) {1860            mcp_funcgen_dereference(L, fr->cmap[x]);1861            fr->cmap[x] = NULL;1862        }1863    }18641865    if (fr->def_fgen) {1866        mcp_funcgen_dereference(L, fr->def_fgen);1867        fr->def_fgen = NULL;1868    }18691870    return 0;1871}18721873// Note: the string should be safe to use after popping it here, because we1874// were fetching it from a table, but I might consider copying it into a1875// buffer from the caller first.1876static const char *_mcplib_router_new_check(lua_State *L, const char *arg, size_t *len) {1877    int type = lua_getfield(L, 1, arg);1878    if (type == LUA_TSTRING) {1879        const char *sep = lua_tolstring(L, -1, len);1880        if (*len == 0) {1881            proxy_lua_ferror(L, "must pass a non-zero length string to %s in mcp.router_new", arg);1882        } else if (*len > KEY_HASH_FILTER_MAX) {1883            proxy_lua_ferror(L, "%s is too long in mcp.router_new", arg);1884        }1885        lua_pop(L, 1); // drop key1886        return sep;1887    } else if (type != LUA_TNIL) {1888        proxy_lua_ferror(L, "must pass a string to %s in mcp.router_new", arg);1889    }1890    return NULL;1891}18921893static void _mcplib_router_new_cmapcheck(lua_State *L) {1894    int tidx = lua_absindex(L, -1);1895    lua_pushnil(L); // init next table key.1896    while (lua_next(L, tidx) != 0) {1897        if (!lua_isinteger(L, -2)) {1898            proxy_lua_error(L, "Non integer key in router command map in router_new");1899        }1900        int cmd = lua_tointeger(L, -2);1901        if ((cmd <= 0 || cmd >= CMD_END_STORAGE) && cmd != CMD_ANY_STORAGE) {1902            proxy_lua_error(L, "Bad command in router command map in router_new");1903        }1904        luaL_checkudata(L, -1, "mcp.funcgen");1905        lua_pop(L, 1); // drop val, keep key.1906    }1907}19081909static size_t _mcplib_router_new_mapcheck(lua_State *L) {1910    size_t route_count = 0;1911    if (!lua_istable(L, -1)) {1912        proxy_lua_error(L, "Must pass a table to map argument of router_new");1913    }1914    // walk map table, get size count.1915    lua_pushnil(L); // init table key.1916    while (lua_next(L, 2) != 0) {1917        int type = lua_type(L, -1);1918        if (type == LUA_TUSERDATA) {1919            luaL_checkudata(L, -1, "mcp.funcgen");1920        } else if (type == LUA_TTABLE) {1921            // If table, it's a command map, poke in and validate.1922            _mcplib_router_new_cmapcheck(L);1923        } else {1924            proxy_lua_error(L, "unhandled data in router_new map");1925        }1926        route_count++;1927        lua_pop(L, 1); // drop val, keep key.1928    }19291930    return route_count;1931}19321933// reads the configuration for the router based on the mode.1934static void _mcplib_router_new_mode(lua_State *L, struct mcp_funcgen_router *fr) {1935    const char *type = lua_tostring(L, -1);1936    size_t len = 0;1937    const char *sep = NULL;19381939    // change internal type based on length of separator1940    if (strcmp(type, "prefix") == 0) {1941        sep = _mcplib_router_new_check(L, "stop", &len);1942        if (sep == NULL) {1943            // defaults1944            fr->type = FGEN_ROUTER_SHORTSEP;1945            fr->conf.sep = '/';1946        } else if (len == 1) {1947            // optimized shortsep case.1948            fr->type = FGEN_ROUTER_SHORTSEP;1949            fr->conf.sep = sep[0];1950        } else {1951            // len is long.1952            fr->type = FGEN_ROUTER_LONGSEP;1953            memcpy(fr->conf.lsep, sep, len);1954            fr->conf.lsep[len] = '\0'; // cap it.1955        }1956    } else if (strcmp(type, "anchor") == 0) {1957        size_t elen = 0; // stop len.1958        const char *usep = _mcplib_router_new_check(L, "stop", &elen);1959        sep = _mcplib_router_new_check(L, "start", &len);1960        if (sep == NULL && usep == NULL) {1961            // no arguments, use a default.1962            fr->type = FGEN_ROUTER_ANCHORSM;1963            fr->conf.anchorsm[0] = '/';1964            fr->conf.anchorsm[1] = '/';1965        } else if (sep == NULL || usep == NULL) {1966            // reduce the combinatorial space because I'm lazy.1967            proxy_lua_error(L, "must specify start and stop if mode is anchor in mcp.router_new");1968        } else if (len == 1 && elen == 1) {1969            fr->type = FGEN_ROUTER_ANCHORSM;1970            fr->conf.anchorsm[0] = sep[0];1971            fr->conf.anchorsm[1] = usep[0];1972        } else {1973            fr->type = FGEN_ROUTER_ANCHORBIG;1974            memcpy(fr->conf.big.start, sep, len);1975            memcpy(fr->conf.big.stop, usep, elen);1976            fr->conf.big.start[len] = '\0';1977            fr->conf.big.stop[elen] = '\0';1978        }1979    } else {1980        proxy_lua_error(L, "unknown type passed to mcp.router_new");1981    }1982}19831984// FIXME: error if map or cmap not passed in?1985int mcplib_router_new(lua_State *L) {1986    struct mcp_funcgen_router fr = {0};1987    size_t route_count = 0;1988    bool has_map = false;19891990    if (!lua_istable(L, 1)) {1991        proxy_lua_error(L, "Must pass a table of arguments to mcp.router_new");1992    }19931994    if (lua_getfield(L, 1, "map") != LUA_TNIL) {1995        route_count = _mcplib_router_new_mapcheck(L);1996        has_map = true;1997    }1998    lua_pop(L, 1); // drop map or nil19992000    if (lua_getfield(L, 1, "cmap") != LUA_TNIL) {

Code quality findings 35

Warning: realloc() result must be stored in a temporary variable. Using 'ptr = realloc(ptr, ...)' directly causes a memory leak if realloc fails and returns NULL.
warning correctness realloc-unchecked
fgen->list = realloc(fgen->list, fgen->free_max * sizeof(mcp_rcontext_t *));
Warning: Allocation result must be checked for NULL before use to prevent null pointer dereference.
warning correctness malloc-unchecked
fgen->list = calloc(fgen->free_max, sizeof(mcp_rcontext_t *));
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
if (res->blen) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
if (res->be && res->be->use_logging) {
Warning: Possible missing break/return in switch case, which causes fall-through to the next case. Add 'break;', 'return', or a '/* fallthrough */' comment if intentional.
warning correctness missing-break-switch
case QWAIT_ANY:
Warning: Possible missing break/return in switch case, which causes fall-through to the next case. Add 'break;', 'return', or a '/* fallthrough */' comment if intentional.
warning correctness missing-break-switch
case QWAIT_OK:
Warning: Possible missing break/return in switch case, which causes fall-through to the next case. Add 'break;', 'return', or a '/* fallthrough */' comment if intentional.
warning correctness missing-break-switch
case QWAIT_GOOD:
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
while (lua_next(L, tidx) != 0) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
if (type == LUA_TUSERDATA) {
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(fgen->self_ref == 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rctx->pending_reqs == 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(1 == 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(fgen->self_ref);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rc->Lc);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rctx->pending_reqs == 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rctx->Lc);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rqu->state != RQUEUE_ACTIVE);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(1 == 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rctx->parent->pending_reqs > -1);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rctx->fgen->thread->stats.proxy_req_active >= 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(1 == 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(fgen->refcount > 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rqu->res_ref == 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rqu->res_ref == 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rqu->res_ref == 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rqu->res_ref == 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rqu->res_ref == 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rqu->res_ref == 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rctx->pending_reqs > -1);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rqu->state == RQUEUE_COMPLETE);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(1 == 0); // should not get here.
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rctx->pending_reqs != 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rqu->state == RQUEUE_ACTIVE);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(rctx->pending_reqs > -1);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(1==0);

Security findings 4

Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(fr->conf.lsep, sep, len);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(fr->conf.big.start, sep, len);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(fr->conf.big.stop, usep, elen);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(router, &fr, sizeof(struct mcp_funcgen_router));

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.