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) {