proxy_lua.c C 2,035 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,035.
1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */23#include "proxy.h"4#include "proxy_tls.h"5#include "storage.h" // for stats call67// func prototype example:8// static int fname (lua_State *L)9// normal library open:10// int luaopen_mcp(lua_State *L) { }1112struct _mcplib_statctx_s {13    lua_State *L;14};1516static void _mcplib_append_stats(const char *key, const uint16_t klen,17                  const char *val, const uint32_t vlen,18                  const void *cookie) {19    // k + v == 0 means END, but we don't use END for this lua API.20    if (klen == 0) {21        return;22    }2324    // cookie -> struct25    const struct _mcplib_statctx_s *c = cookie;26    lua_State *L = c->L;27    // table should always be on the top.28    lua_pushlstring(L, key, klen);29    lua_pushlstring(L, val, vlen);30    lua_rawset(L, -3);31}3233static void _mcplib_append_section_stats(const char *key, const uint16_t klen,34                  const char *val, const uint32_t vlen,35                  const void *cookie) {36    char stat[STAT_KEY_LEN];37    long section = 0;38    if (klen == 0) {39        return;40    }4142    const struct _mcplib_statctx_s *c = cookie;43    lua_State *L = c->L;44    // table must be at the top when this function is called.45    int tidx = lua_absindex(L, -1);4647    // NOTE: sscanf is not great, especially with numerics due to UD for out48    // of range data. It is safe to use here because we're generating the49    // strings, and we don't use this function on anything that has user50    // defined data (ie; stats proxy). Otherwise sscanf saves a lot of code so51    // we use it here.52    if (sscanf(key, "items:%ld:%s", &section, stat) == 253            || sscanf(key, "%ld:%s", &section, stat) == 2) {54        // stats [items, slabs, conns]55        if (lua_rawgeti(L, tidx, section) == LUA_TNIL) {56            lua_pop(L, 1); // drop the nil57            // no sub-section table yet, create one.58            lua_newtable(L);59            lua_pushvalue(L, -1); // copy the table60            lua_rawseti(L, tidx, section); // remember the table61            // now top of stack is the table.62        }6364        lua_pushstring(L, stat);65        lua_pushlstring(L, val, vlen);66        lua_rawset(L, -3); // put key/val into sub-table67        lua_pop(L, 1); // pop sub-table.68    } else {69        // normal stat counter.70        lua_pushlstring(L, key, klen);71        lua_pushlstring(L, val, vlen);72        lua_rawset(L, tidx);73    }74}7576// reimplementation of proto_text.c:process_stat()77static int mcplib_server_stats(lua_State *L) {78    int argc = lua_gettop(L);79    proxy_ctx_t *ctx = PROXY_GET_CTX(L);80    lua_newtable(L); // the table to return.81    struct _mcplib_statctx_s c = {82        L,83    };8485    if (argc == 0 || lua_isnil(L, 1)) {86        server_stats(&_mcplib_append_stats, &c);87        get_stats(NULL, 0, &_mcplib_append_stats, &c);88    } else {89        const char *cmd = luaL_checkstring(L, 1);90        if (strcmp(cmd, "settings") == 0) {91            process_stat_settings(&_mcplib_append_stats, &c);92        } else if (strcmp(cmd, "conns") == 0) {93            process_stats_conns(&_mcplib_append_section_stats, &c);94#ifdef EXTSTORE95        } else if (strcmp(cmd, "extstore") == 0) {96            process_extstore_stats(&_mcplib_append_stats, &c);97#endif98        } else if (strcmp(cmd, "proxy") == 0) {99            process_proxy_stats(ctx, &_mcplib_append_stats, &c);100        } else if (strcmp(cmd, "proxyfuncs") == 0) {101            process_proxy_funcstats(ctx, &_mcplib_append_stats, &c);102        } else if (strcmp(cmd, "proxybe") == 0) {103            process_proxy_bestats(ctx, &_mcplib_append_stats, &c);104        } else {105            if (get_stats(cmd, strlen(cmd), &_mcplib_append_section_stats, &c)) {106                // all good.107            } else {108                // unknown command.109                proxy_lua_error(L, "unknown subcommand passed to server_stats");110            }111        }112    }113114    // return the table.115    return 1;116}117118static lua_Integer _mcplib_backend_get_waittime(lua_Number secondsf) {119    lua_Integer secondsi = (lua_Integer) secondsf;120    lua_Number subseconds = secondsf - secondsi;121    if (subseconds >= 0.5) {122        // Yes, I know this rounding is probably wrong. it's close enough.123        // Rounding functions have tricky portability and whole-integer124        // rounding is at least simpler to reason about.125        secondsi++;126    }127    if (secondsi < 1) {128        secondsi = 1;129    }130    return secondsi;131}132133// take string, table as arg:134// name, { every =, rerun = false, func = f }135// repeat defaults to true136static int mcplib_register_cron(lua_State *L) {137    proxy_ctx_t *ctx = PROXY_GET_CTX(L);138    const char *name = luaL_checkstring(L, 1);139    luaL_checktype(L, 2, LUA_TTABLE);140141    // reserve an upvalue for storing the function.142    mcp_cron_t *ce = lua_newuserdatauv(L, sizeof(mcp_cron_t), 1);143    memset(ce, 0, sizeof(*ce));144145    // default repeat.146    ce->repeat = true;147    // sync config generation.148    ce->gen = ctx->config_generation;149150    if (lua_getfield(L, 2, "func") != LUA_TNIL) {151        luaL_checktype(L, -1, LUA_TFUNCTION);152        lua_setiuservalue(L, 3, 1); // pop value153    } else {154        proxy_lua_error(L, "proxy cron entry missing 'func' field");155        return 0;156    }157158    if (lua_getfield(L, 2, "rerun") != LUA_TNIL) {159        int rerun = lua_toboolean(L, -1);160        if (!rerun) {161            ce->repeat = false;162        }163    }164    lua_pop(L, 1); // pop val or nil165166    // TODO: set a limit on 'every' so we don't have to worry about167    // underflows. a year? a month?168    if (lua_getfield(L, 2, "every") != LUA_TNIL) {169        luaL_checktype(L, -1, LUA_TNUMBER);170        int every = lua_tointeger(L, -1);171        if (every < 1) {172            proxy_lua_error(L, "proxy cron entry 'every' must be > 0");173            return 0;174        }175        ce->every = every;176    } else {177        proxy_lua_error(L, "proxy cron entry missing 'every' field");178        return 0;179    }180    lua_pop(L, 1); // pop val or nil181182    // schedule the next cron run183    struct timespec now;184    clock_gettime(CLOCK_REALTIME, &now);185    ce->next = now.tv_sec + ce->every;186    // we may adjust ce->next shortly, so don't update global yet.187188    // valid cron entry, now place into cron table.189    lua_rawgeti(L, LUA_REGISTRYINDEX, ctx->cron_ref);190191    // first, check if a cron of this name already exists.192    // if so and the 'every' field matches, inherit its 'next' field193    // so we don't perpetually reschedule all crons.194    if (lua_getfield(L, -1, name) != LUA_TNIL) {195        mcp_cron_t *oldce = lua_touserdata(L, -1);196        if (ce->every == oldce->every) {197            ce->next = oldce->next;198        }199    }200    lua_pop(L, 1); // drop val/nil201202    lua_pushvalue(L, 3); // duplicate cron entry203    lua_setfield(L, -2, name); // pop duplicate cron entry204    lua_pop(L, 1); // drop cron table205206    // update central cron sleep.207    if (ctx->cron_next > ce->next) {208        ctx->cron_next = ce->next;209    }210211    return 0;212}213214// just set ctx->loading = true215// called from config thread, so config_lock must be held, so it's safe to216// modify protected ctx contents.217static int mcplib_schedule_config_reload(lua_State *L) {218    proxy_ctx_t *ctx = PROXY_GET_CTX(L);219    ctx->loading = true;220    return 0;221}222223static int mcplib_time_real_millis(lua_State *L) {224    struct timespec now;225    clock_gettime(CLOCK_REALTIME, &now);226    lua_Integer t = now.tv_nsec / 1000000 + (lua_Integer) now.tv_sec * 1000;227    lua_pushinteger(L, t);228    return 1;229}230231static int mcplib_time_mono_millis(lua_State *L) {232    struct timespec now;233    clock_gettime(CLOCK_MONOTONIC, &now);234    lua_Integer t = now.tv_nsec / 1000000 + (lua_Integer) now.tv_sec * 1000;235    lua_pushinteger(L, t);236    return 1;237}238239// end util funcs.240241// NOTE: backends are global objects owned by pool objects.242// Each pool has a "proxy pool object" distributed to each worker VM.243// proxy pool objects are held at the same time as any request exists on a244// backend, in the coroutine stack during yield()245// To free a backend: All proxies for a pool are collected, then the central246// pool is collected, which releases backend references, which allows backend247// to be collected.248static int mcplib_backend_wrap_gc(lua_State *L) {249    mcp_backend_wrap_t *bew = luaL_checkudata(L, -1, "mcp.backendwrap");250    proxy_ctx_t *ctx = PROXY_GET_CTX(L);251252    if (bew->be != NULL) {253        mcp_backend_t *be = bew->be;254        // TODO (v3): technically a race where a backend could be created,255        // queued, but not picked up before being gc'ed again. In practice256        // this is impossible but at some point we should close the loop here.257        // Since we're running in the config thread it could just busy poll258        // until the connection was picked up.259        assert(be->transferred);260        // There has to be at least one connection, and the event_thread will261        // always be the same.262        proxy_event_thread_t *e = be->be[0].event_thread;263        pthread_mutex_lock(&e->mutex);264        STAILQ_INSERT_TAIL(&e->beconn_head_in, be, beconn_next);265        pthread_mutex_unlock(&e->mutex);266267        // Signal to check queue.268#ifdef USE_EVENTFD269        uint64_t u = 1;270        // TODO (v2): check result? is it ever possible to get a short write/failure271        // for an eventfd?272        if (write(e->be_event_fd, &u, sizeof(uint64_t)) != sizeof(uint64_t)) {273            assert(1 == 0);274        }275#else276        if (write(e->be_notify_send_fd, "w", 1) <= 0) {277            assert(1 == 0);278        }279#endif280    }281282    STAT_DECR(ctx, backend_total, 1);283284    return 0;285}286287static int mcplib_backend_gc(lua_State *L) {288    mcp_backend_label_t *be = lua_touserdata(L, 1);289    if (be->logging.detail)290        free(be->logging.detail);291292    return 0;293}294295static int _mcplib_backend_log(lua_State *L, mcp_backend_label_t *be) {296    be->use_logging = true;297298    if (lua_getfield(L, -1, "deadline") != LUA_TNIL) {299        int deadline = luaL_checkinteger(L, -1);300        if (deadline < 0) {301            proxy_lua_error(L, "backend log deadline must be >= 0");302        }303        // convert to milliseconds.304        be->logging.deadline = deadline * 1000;305    }306    lua_pop(L, 1);307308    if (lua_getfield(L, -1, "rate") != LUA_TNIL) {309        int rate = luaL_checkinteger(L, -1);310        if (rate < 0) {311            proxy_lua_error(L, "backend log sample rate must be >= 0");312        }313        be->logging.rate = rate;314    }315    lua_pop(L, 1);316317    if (lua_getfield(L, -1, "errors") != LUA_TNIL) {318        luaL_checktype(L, -1, LUA_TBOOLEAN);319        int errors = lua_toboolean(L, -1);320        if (errors) {321            be->logging.all_errors = true;322        } else {323            be->logging.all_errors = false;324        }325    }326    lua_pop(L, 1);327328    if (lua_getfield(L, -1, "tag") != LUA_TNIL) {329        size_t tlen = 0;330        const char *tag = luaL_checklstring(L, -1, &tlen);331        be->logging.detail = malloc(tlen+1);332        memcpy(be->logging.detail, tag, tlen);333        be->logging.detail[tlen] = '\0';334    }335    lua_pop(L, 1);336337    // If user didn't set deadline, or errors, or rate, we would log nothing:338    // instead default to a rate of 1.339    if (be->logging.deadline == 0 &&340        !be->logging.all_errors &&341        be->logging.rate == 0) {342        be->logging.rate = 1;343    }344345    return 0;346}347348// backend label object; given to pools which then find or create backend349// objects as necessary.350// allow optionally passing a table of arguments for extended options:351// { label = "etc", "host" = "127.0.0.1", port = "11211",352//   readtimeout = 0.5, connecttimeout = 1, retrytime = 3,353//   failurelimit = 3, tcpkeepalive = false }354static int mcplib_backend(lua_State *L) {355    size_t llen = 0;356    size_t nlen = 0;357    size_t plen = 0;358    const char *label;359    const char *name;360    const char *port;361    proxy_ctx_t *ctx = PROXY_GET_CTX(L);362    mcp_backend_label_t *be = lua_newuserdatauv(L, sizeof(mcp_backend_label_t), 0);363    memset(be, 0, sizeof(*be));364    // copy global defaults for tunables.365    memcpy(&be->tunables, &ctx->tunables, sizeof(be->tunables));366    be->conncount = 1; // one connection per backend as default.367    // set the metatable early so the GC handler can free partial allocations368    luaL_getmetatable(L, "mcp.backend");369    lua_setmetatable(L, -2); // set metatable to userdata.370371    if (lua_istable(L, 1)) {372373        // We don't pop the label/host/port strings so lua won't change them374        // until after the function call.375        if (lua_getfield(L, 1, "label") != LUA_TNIL) {376            label = luaL_checklstring(L, -1, &llen);377        } else {378            proxy_lua_error(L, "backend must have a label argument");379            return 0;380        }381382        if (lua_getfield(L, 1, "host") != LUA_TNIL) {383            name = luaL_checklstring(L, -1, &nlen);384        } else {385            proxy_lua_error(L, "backend must have a host argument");386            return 0;387        }388389        // TODO: allow a default port.390        if (lua_getfield(L, 1, "port") != LUA_TNIL) {391            port = luaL_checklstring(L, -1, &plen);392        } else {393            proxy_lua_error(L, "backend must have a port argument");394            return 0;395        }396397        if (lua_getfield(L, 1, "tcpkeepalive") != LUA_TNIL) {398            be->tunables.tcp_keepalive = lua_toboolean(L, -1);399        }400        lua_pop(L, 1);401402        if (lua_getfield(L, 1, "tls") != LUA_TNIL) {403            be->tunables.use_tls = lua_toboolean(L, -1);404        }405        lua_pop(L, 1);406407        if (lua_getfield(L, 1, "failurelimit") != LUA_TNIL) {408            int limit = luaL_checkinteger(L, -1);409            if (limit < 0) {410                proxy_lua_error(L, "failurelimit must be >= 0");411                return 0;412            }413414            be->tunables.backend_failure_limit = limit;415        }416        lua_pop(L, 1);417418        if (lua_getfield(L, 1, "depthlimit") != LUA_TNIL) {419            int limit = luaL_checkinteger(L, -1);420            if (limit < 0) {421                proxy_lua_error(L, "depthlimit must be >= 0");422                return 0;423            }424425            be->tunables.backend_depth_limit = limit;426        }427        lua_pop(L, 1);428429        if (lua_getfield(L, 1, "connecttimeout") != LUA_TNIL) {430            lua_Number secondsf = luaL_checknumber(L, -1);431            lua_Integer secondsi = (lua_Integer) secondsf;432            lua_Number subseconds = secondsf - secondsi;433434            be->tunables.connect.tv_sec = secondsi;435            be->tunables.connect.tv_usec = MICROSECONDS(subseconds);436        }437        lua_pop(L, 1);438439        // TODO (v2): print deprecation warning.440        if (lua_getfield(L, 1, "retrytimeout") != LUA_TNIL) {441            be->tunables.retry.tv_sec =442                _mcplib_backend_get_waittime(luaL_checknumber(L, -1));443        }444        lua_pop(L, 1);445446        if (lua_getfield(L, 1, "retrywaittime") != LUA_TNIL) {447            be->tunables.retry.tv_sec =448                _mcplib_backend_get_waittime(luaL_checknumber(L, -1));449        }450        lua_pop(L, 1);451452        if (lua_getfield(L, 1, "retrytimeout") != LUA_TNIL) {453            lua_Number secondsf = luaL_checknumber(L, -1);454            lua_Integer secondsi = (lua_Integer) secondsf;455            lua_Number subseconds = secondsf - secondsi;456457            be->tunables.retry.tv_sec = secondsi;458            be->tunables.retry.tv_usec = MICROSECONDS(subseconds);459        }460        lua_pop(L, 1);461462        if (lua_getfield(L, 1, "readtimeout") != LUA_TNIL) {463            lua_Number secondsf = luaL_checknumber(L, -1);464            lua_Integer secondsi = (lua_Integer) secondsf;465            lua_Number subseconds = secondsf - secondsi;466467            be->tunables.read.tv_sec = secondsi;468            be->tunables.read.tv_usec = MICROSECONDS(subseconds);469        }470        lua_pop(L, 1);471472        if (lua_getfield(L, 1, "down") != LUA_TNIL) {473            int down = lua_toboolean(L, -1);474            be->tunables.down = down;475        }476        lua_pop(L, 1);477478        if (lua_getfield(L, 1, "flaptime") != LUA_TNIL) {479            lua_Number secondsf = luaL_checknumber(L, -1);480            lua_Integer secondsi = (lua_Integer) secondsf;481            lua_Number subseconds = secondsf - secondsi;482483            be->tunables.flap.tv_sec = secondsi;484            be->tunables.flap.tv_usec = MICROSECONDS(subseconds);485        }486        lua_pop(L, 1);487488        if (lua_getfield(L, 1, "flapbackofframp") != LUA_TNIL) {489            float ramp = luaL_checknumber(L, -1);490            if (ramp <= 1.1) {491                ramp = 1.1;492            }493            be->tunables.flap_backoff_ramp = ramp;494        }495        lua_pop(L, 1);496497        if (lua_getfield(L, 1, "flapbackoffmax") != LUA_TNIL) {498            luaL_checknumber(L, -1);499            uint32_t max = lua_tointeger(L, -1);500            be->tunables.flap_backoff_max = max;501        }502        lua_pop(L, 1);503504        if (lua_getfield(L, 1, "connections") != LUA_TNIL) {505            int c = luaL_checkinteger(L, -1);506            if (c <= 0) {507                proxy_lua_error(L, "backend connections argument must be >= 0");508                return 0;509            } else if (c > 8) {510                proxy_lua_error(L, "backend connections argument must be <= 8");511                return 0;512            }513514            be->conncount = c;515        }516        lua_pop(L, 1);517518        if (lua_getfield(L, 1, "log") != LUA_TNIL) {519            if (lua_istable(L, -1)) {520                _mcplib_backend_log(L, be);521            } else {522                proxy_lua_error(L, "backend log option must be a table");523            }524        }525        lua_pop(L, 1);526    } else {527        label = luaL_checklstring(L, 1, &llen);528        name = luaL_checklstring(L, 2, &nlen);529        port = luaL_checklstring(L, 3, &plen);530    }531532    if (llen > MAX_LABELLEN-1) {533        proxy_lua_error(L, "backend label too long");534        return 0;535    }536537    if (nlen > MAX_NAMELEN-1) {538        proxy_lua_error(L, "backend name too long");539        return 0;540    }541542    if (plen > MAX_PORTLEN-1) {543        proxy_lua_error(L, "backend port too long");544        return 0;545    }546547    memcpy(be->label, label, llen);548    be->label[llen] = '\0';549    memcpy(be->name, name, nlen);550    be->name[nlen] = '\0';551    memcpy(be->port, port, plen);552    be->port[plen] = '\0';553    be->llen = llen;554    if (lua_istable(L, 1)) {555        lua_pop(L, 3); // drop label, name, port.556    }557558    return 1; // return be object.559}560561// Called with the cache label at top of the stack.562static mcp_backend_wrap_t *_mcplib_backend_checkcache(lua_State *L, mcp_backend_label_t *bel) {563    // first check our reference table to compare.564    // Note: The upvalue won't be found unless we're running from a function with it565    // set as an upvalue.566    int ret = lua_gettable(L, lua_upvalueindex(MCP_BACKEND_UPVALUE));567    if (ret != LUA_TNIL) {568        mcp_backend_wrap_t *be_orig = luaL_checkudata(L, -1, "mcp.backendwrap");569        if (strncmp(be_orig->be->name, bel->name, MAX_NAMELEN) == 0570                && strncmp(be_orig->be->port, bel->port, MAX_PORTLEN) == 0571                && be_orig->be->conncount == bel->conncount572                && memcmp(&be_orig->be->tunables, &bel->tunables, sizeof(bel->tunables)) == 0573                && memcmp(&be_orig->be->logging, &bel->logging, sizeof(bel->logging)) == 0) {574            // backend is the same, return it.575            return be_orig;576        } else {577            // backend not the same, pop from stack and make new one.578            lua_pop(L, 1);579        }580    } else {581        lua_pop(L, 1); // pop the nil.582    }583584    return NULL;585}586587static mcp_backend_wrap_t *_mcplib_make_backendconn(lua_State *L, mcp_backend_label_t *bel,588        proxy_event_thread_t *e) {589    proxy_ctx_t *ctx = PROXY_GET_CTX(L);590591    mcp_backend_wrap_t *bew = lua_newuserdatauv(L, sizeof(mcp_backend_wrap_t), 0);592    luaL_getmetatable(L, "mcp.backendwrap");593    lua_setmetatable(L, -2); // set metatable to userdata.594595    mcp_backend_t *be = calloc(1, sizeof(mcp_backend_t) + sizeof(struct mcp_backendconn_s) * bel->conncount);596    if (be == NULL) {597        proxy_lua_error(L, "out of memory allocating backend connection");598        return NULL;599    }600601    bew->be = be;602603    strncpy(be->name, bel->name, MAX_NAMELEN+1);604    strncpy(be->port, bel->port, MAX_PORTLEN+1);605    strncpy(be->label, bel->label, MAX_LABELLEN+1);606    memcpy(&be->tunables, &bel->tunables, sizeof(bel->tunables));607    memcpy(&be->logging, &bel->logging, sizeof(bel->logging));608    be->use_logging = bel->use_logging;609    // TODO: check for errors.610    // not really going to happen and if it does the tag just blanks out..611    if (bel->logging.detail) {612        be->logging.detail = strdup(bel->logging.detail);613    }614615    be->conncount = bel->conncount;616    STAILQ_INIT(&be->iop_head);617618    for (int x = 0; x < bel->conncount; x++) {619        struct mcp_backendconn_s *bec = &be->be[x];620        bec->be_parent = be;621        memcpy(&bec->tunables, &bel->tunables, sizeof(bel->tunables));622        STAILQ_INIT(&bec->iop_write);623        STAILQ_INIT(&bec->iop_read);624        bec->state = mcp_backend_read;625626        // this leaves a permanent buffer on the backend, which is fine627        // unless you have billions of backends.628        // we can later optimize for pulling buffers from idle backends.629        bec->rbuf = malloc(READ_BUFFER_SIZE);630        if (bec->rbuf == NULL) {631            proxy_lua_error(L, "out of memory allocating backend");632            return NULL;633        }634635        // initialize the client636        bec->client = malloc(mcmc_size(MCMC_OPTION_BLANK));637        if (bec->client == NULL) {638            proxy_lua_error(L, "out of memory allocating backend");639            return NULL;640        }641        // TODO (v2): no way to change the TCP_KEEPALIVE state post-construction.642        // This is a trivial fix if we ensure a backend's owning event thread is643        // set before it can be used in the proxy, as it would have access to the644        // tunables structure. _reset_bad_backend() may not have its event thread645        // set 100% of the time and I don't want to introduce a crash right now,646        // so I'm writing this overly long comment. :)647        int flags = MCMC_OPTION_NONBLOCK;648        STAT_L(ctx);649        if (ctx->tunables.tcp_keepalive) {650            flags |= MCMC_OPTION_TCP_KEEPALIVE;651        }652        STAT_UL(ctx);653        bec->connect_flags = flags;654655        // FIXME: remove ifdef via an initialized checker? or656        // mcp_tls_backend_init response code?657#ifdef PROXY_TLS658        if (be->tunables.use_tls && !ctx->tls_ctx) {659            proxy_lua_error(L, "TLS requested but not initialized: call mcp.init_tls()");660            return NULL;661        }662#endif663        mcp_tls_backend_init(ctx, bec);664665        bec->event_thread = e;666    }667    pthread_mutex_lock(&e->mutex);668    STAILQ_INSERT_TAIL(&e->beconn_head_in, be, beconn_next);669    pthread_mutex_unlock(&e->mutex);670671    // Signal to check queue.672#ifdef USE_EVENTFD673    uint64_t u = 1;674    // TODO (v2): check result? is it ever possible to get a short write/failure675    // for an eventfd?676    if (write(e->be_event_fd, &u, sizeof(uint64_t)) != sizeof(uint64_t)) {677        assert(1 == 0);678    }679#else680    if (write(e->be_notify_send_fd, "w", 1) <= 0) {681        assert(1 == 0);682    }683#endif684685    lua_pushvalue(L, -2); // push the label string back to the top.686    // Add this new backend connection to the object cache.687    lua_pushvalue(L, -2); // copy the backend reference to the top.688    // set our new backend wrapper object into the reference table.689    lua_settable(L, lua_upvalueindex(MCP_BACKEND_UPVALUE));690    // stack is back to having backend on the top.691692    STAT_INCR(ctx, backend_total, 1);693694    return bew;695}696697static int mcplib_pool_gc(lua_State *L) {698    mcp_pool_t *p = luaL_checkudata(L, -1, "mcp.pool");699700    mcp_gobj_finalize(&p->g);701702    luaL_unref(L, LUA_REGISTRYINDEX, p->phc_ref);703704    for (int x = 0; x < p->pool_be_total; x++) {705        if (p->pool[x].ref) {706            luaL_unref(L, LUA_REGISTRYINDEX, p->pool[x].ref);707        }708    }709710    return 0;711}712713// Looks for a short string in a key to separate which part gets hashed vs714// sent to the backend node.715// ie: "foo:bar|#|restofkey" - only "foo:bar" gets hashed.716static const char *mcp_key_hash_filter_stop(const char *conf, const char *key, size_t klen, size_t *newlen) {717    char temp[KEY_MAX_LENGTH+1];718    *newlen = klen;719    if (klen > KEY_MAX_LENGTH) {720        // Hedging against potential bugs.721        return key;722    }723724    memcpy(temp, key, klen);725    temp[klen] = '\0';726727    // TODO (v2): memmem would avoid the temp key and memcpy here, but it's728    // not technically portable. An easy improvement would be to detect729    // memmem() in `configure` and only use strstr/copy as a fallback.730    // Since keys are short it's unlikely this would be a major performance731    // win.732    char *found = strstr(temp, conf);733734    if (found) {735        *newlen = found - temp;736    }737738    // hash stop can't change where keys start.739    return key;740}741742// Takes a two character "tag", ie; "{}", or "$$", searches string for the743// first then second character. Only hashes the portion within these tags.744// *conf _must_ be two characters.745static const char *mcp_key_hash_filter_tag(const char *conf, const char *key, size_t klen, size_t *newlen) {746    *newlen = klen;747748    const char *t1 = memchr(key, conf[0], klen);749    if (t1) {750        size_t remain = klen - (t1 - key);751        // must be at least one character inbetween the tags to hash.752        if (remain > 1) {753            const char *t2 = memchr(t1+1, conf[1], remain-1);754755            if (t2) {756                *newlen = t2 - t1 - 1;757                return t1+1;758            }759        }760    }761762    return key;763}764765static void _mcplib_pool_dist(lua_State *L, mcp_pool_t *p) {766    luaL_checktype(L, -1, LUA_TTABLE);767    if (lua_getfield(L, -1, "new") != LUA_TFUNCTION) {768        proxy_lua_error(L, "key distribution object missing 'new' function");769        return;770    }771772    // - now create the copy pool table773    lua_createtable(L, p->pool_size, 0); // give the new pool table a sizing hint.774    for (int x = 1; x <= p->pool_size; x++) {775        mcp_backend_t *be = p->pool[x-1].be;776        lua_createtable(L, 0, 4);777        // stack = [p, h, f, optN, newpool, backend]778        // the key should be fine for id? maybe don't need to duplicate779        // this?780        lua_pushinteger(L, x);781        lua_setfield(L, -2, "id");782        // we don't use the hostname for ketama hashing783        // so passing ip for hostname is fine784        lua_pushstring(L, be->name);785        lua_setfield(L, -2, "addr");786        lua_pushstring(L, be->port);787        lua_setfield(L, -2, "port");788789        // set the backend table into the new pool table.790        lua_rawseti(L, -2, x);791    }792793    // we can either use lua_insert() or possibly _rotate to shift794    // things into the right place, but simplest is to just copy the795    // option arg to the end of the stack.796    lua_pushvalue(L, 2);797    //   - stack should be: pool, opts, func, pooltable, opts798799    // call the dist new function.800    int res = lua_pcall(L, 2, 2, 0);801802    if (res != LUA_OK) {803        lua_error(L); // error should be on the stack already.804        return;805    }806807    // -1 is lightuserdata ptr to the struct (which must be owned by the808    // userdata), which is later used for internal calls.809    struct proxy_hash_caller *phc;810811    luaL_checktype(L, -1, LUA_TLIGHTUSERDATA);812    luaL_checktype(L, -2, LUA_TUSERDATA);813    phc = lua_touserdata(L, -1);814    memcpy(&p->phc, phc, sizeof(*phc));815    lua_pop(L, 1);816    // -2 was userdata we need to hold a reference to817    p->phc_ref = luaL_ref(L, LUA_REGISTRYINDEX);818    // UD now popped from stack.819}820821// in the proxy object, we can alias a ptr to the pool to where it needs to be822// based on worker number or io_thread right?823static void _mcplib_pool_make_be_loop(lua_State *L, mcp_pool_t *p, int offset, proxy_event_thread_t *t) {824    // remember lua arrays are 1 indexed.825    for (int x = 1; x <= p->pool_size; x++) {826        mcp_pool_be_t *s = &p->pool[x-1 + (offset * p->pool_size)];827        lua_geti(L, 1, x); // get next server into the stack.828        // If we bail here, the pool _gc() should handle releasing any backend829        // references we made so far.830        mcp_backend_label_t *bel = luaL_checkudata(L, -1, "mcp.backend");831832        // check label for pre-existing backend conn/wrapper833        // TODO (v2): there're native ways of "from C make lua strings"834        int toconcat = 1;835        if (p->beprefix[0] != '\0') {836            lua_pushstring(L, p->beprefix);837            toconcat++;838        }839        if (p->use_iothread) {840            lua_pushstring(L, ":io:");841            toconcat++;842        } else {843            lua_pushstring(L, ":w");844            lua_pushinteger(L, offset);845            lua_pushstring(L, ":");846            toconcat += 3;847        }848        lua_pushlstring(L, bel->label, bel->llen);849        lua_concat(L, toconcat);850851        lua_pushvalue(L, -1); // copy the label string for the create method.852        mcp_backend_wrap_t *bew = _mcplib_backend_checkcache(L, bel);853        if (bew == NULL) {854            bew = _mcplib_make_backendconn(L, bel, t);855        }856        s->be = bew->be; // unwrap the backend connection for direct ref.857        bew->be->use_io_thread = p->use_iothread;858859        // If found from cache or made above, the backend wrapper is on the860        // top of the stack, so we can now take its reference.861        // The wrapper abstraction allows the be memory to be owned by its862        // destination thread (IO thread/etc).863864        s->ref = luaL_ref(L, LUA_REGISTRYINDEX); // references and pops object.865        lua_pop(L, 1); // pop the mcp.backend label object.866        lua_pop(L, 1); // drop extra label copy.867    }868}869870// call with table of backends in 1871static void _mcplib_pool_make_be(lua_State *L, mcp_pool_t *p) {872    if (p->use_iothread) {873        proxy_ctx_t *ctx = PROXY_GET_CTX(L);874        _mcplib_pool_make_be_loop(L, p, 0, ctx->proxy_io_thread);875    } else {876        // TODO (v3) globals.877        for (int n = 0; n < settings.num_threads; n++) {878            LIBEVENT_THREAD *t = get_worker_thread(n);879            _mcplib_pool_make_be_loop(L, p, t->thread_baseid, t->proxy_event_thread);880        }881    }882}883884// p = mcp.pool(backends, { dist = f, hashfilter = f, seed = "a", hash = f })885static int mcplib_pool(lua_State *L) {886    proxy_ctx_t *ctx = PROXY_GET_CTX(L);887    int argc = lua_gettop(L);888    luaL_checktype(L, 1, LUA_TTABLE);889    int n = luaL_len(L, 1); // get length of array table890    int workers = settings.num_threads; // TODO (v3): globals usage.891892    size_t plen = sizeof(mcp_pool_t) + (sizeof(mcp_pool_be_t) * n * workers);893    mcp_pool_t *p = lua_newuserdatauv(L, plen, 0);894    // Zero the memory before use, so we can realibly use __gc to clean up895    memset(p, 0, plen);896    p->pool_size = n;897    p->pool_be_total = n * workers;898    p->use_iothread = ctx->tunables.use_iothread;899    // TODO (v2): Nicer if this is fetched from mcp.default_key_hash900    p->key_hasher = XXH3_64bits_withSeed;901    pthread_mutex_init(&p->g.lock, NULL);902    p->ctx = PROXY_GET_CTX(L);903904    luaL_setmetatable(L, "mcp.pool");905906    // Allow passing an ignored nil as a second argument. Makes the lua easier907    int type = lua_type(L, 2);908    if (argc == 1 || type == LUA_TNIL) {909        _mcplib_pool_make_be(L, p);910        lua_getglobal(L, "mcp");911        // TODO (v2): decide on a mcp.default_dist and use that instead912        if (lua_getfield(L, -1, "dist_jump_hash") != LUA_TNIL) {913            _mcplib_pool_dist(L, p);914            lua_pop(L, 1); // pop "dist_jump_hash" value.915        } else {916            lua_pop(L, 1);917        }918        lua_pop(L, 1); // pop "mcp"919        return 1;920    }921922    // Supplied with an options table. We inspect this table to decorate the923    // pool, then pass it along to the a constructor if necessary.924    luaL_checktype(L, 2, LUA_TTABLE);925926    if (lua_getfield(L, 2, "iothread") != LUA_TNIL) {927        luaL_checktype(L, -1, LUA_TBOOLEAN);928        int use_iothread = lua_toboolean(L, -1);929        if (use_iothread) {930            p->use_iothread = true;931        } else {932            p->use_iothread = false;933        }934        lua_pop(L, 1); // remove value.935    } else {936        lua_pop(L, 1); // pop the nil.937    }938939    if (lua_getfield(L, 2, "beprefix") != LUA_TNIL) {940        luaL_checktype(L, -1, LUA_TSTRING);941        size_t len = 0;942        const char *bepfx = lua_tolstring(L, -1, &len);943        if (len > MAX_LABELLEN-1) {944            len = MAX_LABELLEN-1;945        }946        memcpy(p->beprefix, bepfx, len);947        p->beprefix[len+1] = '\0';948        lua_pop(L, 1); // pop beprefix string.949    } else {950        lua_pop(L, 1); // pop the nil.951    }952    _mcplib_pool_make_be(L, p);953954    // stack: backends, options, mcp.pool955    if (lua_getfield(L, 2, "dist") != LUA_TNIL) {956        // overriding the distribution function.957        _mcplib_pool_dist(L, p);958        lua_pop(L, 1); // remove the dist table from stack.959    } else {960        lua_pop(L, 1); // pop the nil.961962        // use the default dist if not specified with an override table.963        lua_getglobal(L, "mcp");964        // TODO (v2): decide on a mcp.default_dist and use that instead965        if (lua_getfield(L, -1, "dist_jump_hash") != LUA_TNIL) {966            _mcplib_pool_dist(L, p);967            lua_pop(L, 1); // pop "dist_jump_hash" value.968        } else {969            lua_pop(L, 1);970        }971        lua_pop(L, 1); // pop "mcp"972    }973974    if (lua_getfield(L, 2, "filter") != LUA_TNIL) {975        luaL_checktype(L, -1, LUA_TSTRING);976        const char *f_type = lua_tostring(L, -1);977        if (strcmp(f_type, "stop") == 0) {978            p->key_filter = mcp_key_hash_filter_stop;979        } else if (strcmp(f_type, "tags") == 0) {980            p->key_filter = mcp_key_hash_filter_tag;981        } else {982            proxy_lua_ferror(L, "unknown hash filter specified: %s\n", f_type);983        }984985        lua_pop(L, 1); // pops "filter" value.986987        if (lua_getfield(L, 2, "filter_conf") == LUA_TSTRING) {988            size_t len = 0;989            const char *conf = lua_tolstring(L, -1, &len);990            if (len < 2 || len > KEY_HASH_FILTER_MAX) {991                proxy_lua_ferror(L, "hash filter conf must be between 2 and %d characters", KEY_HASH_FILTER_MAX);992            }993994            memcpy(p->key_filter_conf, conf, len);995            p->key_filter_conf[len] = '\0';996        } else {997            proxy_lua_error(L, "hash filter requires 'filter_conf' string");998        }999        lua_pop(L, 1); // pops "filter_conf" value.1000    } else {1001        lua_pop(L, 1); // pop the nil.1002    }10031004    if (lua_getfield(L, 2, "hash") != LUA_TNIL) {1005        luaL_checktype(L, -1, LUA_TLIGHTUSERDATA);1006        struct proxy_hash_func *phf = lua_touserdata(L, -1);1007        p->key_hasher = phf->func;1008        lua_pop(L, 1);1009    } else {1010        lua_pop(L, 1); // pop the nil.1011    }10121013    if (lua_getfield(L, 2, "seed") != LUA_TNIL) {1014        luaL_checktype(L, -1, LUA_TSTRING);1015        size_t seedlen;1016        const char *seedstr = lua_tolstring(L, -1, &seedlen);1017        // Note: the custom hasher for a dist may be "weird" in some cases, so1018        // we use a standard hash method for the seed here.1019        // I'm open to changing this (ie; mcp.pool_seed_hasher = etc)1020        p->hash_seed = XXH3_64bits(seedstr, seedlen);10211022        lua_pop(L, 1);1023    } else {1024        lua_pop(L, 1); // pop the nil.1025    }10261027    if (p->phc.selector_func == NULL) {1028        proxy_lua_error(L, "cannot create pool missing 'dist' argument");1029    }10301031    return 1;1032}10331034static int mcplib_pool_proxy_gc(lua_State *L) {1035    mcp_pool_proxy_t *pp = luaL_checkudata(L, -1, "mcp.pool_proxy");1036    mcp_pool_t *p = pp->main;1037    pthread_mutex_lock(&p->g.lock);1038    p->g.refcount--;1039    if (p->g.refcount == 0) {1040        proxy_ctx_t *ctx = p->ctx;1041        pthread_mutex_lock(&ctx->manager_lock);1042        STAILQ_INSERT_TAIL(&ctx->manager_head, &p->g, next);1043        pthread_cond_signal(&ctx->manager_cond);1044        pthread_mutex_unlock(&ctx->manager_lock);1045    }1046    pthread_mutex_unlock(&p->g.lock);10471048    return 0;1049}10501051mcp_backend_t *mcplib_pool_proxy_call_helper(mcp_pool_proxy_t *pp, const char *key, size_t len) {1052    mcp_pool_t *p = pp->main;1053    if (p->key_filter) {1054        key = p->key_filter(p->key_filter_conf, key, len, &len);1055        P_DEBUG("%s: filtered key for hashing (%.*s)\n", __func__, (int)len, key);1056    }1057    uint64_t hash = p->key_hasher(key, len, p->hash_seed);1058    uint32_t lookup = p->phc.selector_func(hash, p->phc.ctx);10591060    assert(p->phc.ctx != NULL);1061    if (lookup >= p->pool_size) {1062        return NULL;1063    }10641065    return pp->pool[lookup].be;1066}10671068static int mcplib_backend_use_iothread(lua_State *L) {1069    luaL_checktype(L, -1, LUA_TBOOLEAN);1070    int state = lua_toboolean(L, -1);1071    proxy_ctx_t *ctx = PROXY_GET_CTX(L);10721073    STAT_L(ctx);1074    ctx->tunables.use_iothread = state;1075    STAT_UL(ctx);10761077    return 0;1078}10791080static int mcplib_backend_use_tls(lua_State *L) {1081    luaL_checktype(L, -1, LUA_TBOOLEAN);1082    int state = lua_toboolean(L, -1);1083    proxy_ctx_t *ctx = PROXY_GET_CTX(L);1084#ifndef PROXY_TLS1085    if (state == 1) {1086        proxy_lua_error(L, "cannot set mcp.backend_use_tls: TLS support not compiled");1087    }1088#endif1089    STAT_L(ctx);1090    ctx->tunables.use_tls = state;1091    STAT_UL(ctx);10921093    return 0;1094}10951096// TODO: error checking.1097static int mcplib_init_tls(lua_State *L) {1098#ifndef PROXY_TLS1099    proxy_lua_error(L, "cannot run mcp.init_tls: TLS support not compiled");1100#else1101    proxy_ctx_t *ctx = PROXY_GET_CTX(L);1102    mcp_tls_init(ctx);1103#endif11041105    return 0;1106}11071108static int mcplib_tcp_keepalive(lua_State *L) {1109    luaL_checktype(L, -1, LUA_TBOOLEAN);1110    int state = lua_toboolean(L, -1);1111    proxy_ctx_t *ctx = PROXY_GET_CTX(L);11121113    STAT_L(ctx);1114    ctx->tunables.tcp_keepalive = state;1115    STAT_UL(ctx);11161117    return 0;1118}11191120static int mcplib_backend_failure_limit(lua_State *L) {1121    int limit = luaL_checkinteger(L, -1);1122    proxy_ctx_t *ctx = PROXY_GET_CTX(L);11231124    if (limit < 0) {1125        proxy_lua_error(L, "backend_failure_limit must be >= 0");1126        return 0;1127    }11281129    STAT_L(ctx);1130    ctx->tunables.backend_failure_limit = limit;1131    STAT_UL(ctx);11321133    return 0;1134}11351136static int mcplib_backend_depth_limit(lua_State *L) {1137    int limit = luaL_checkinteger(L, -1);1138    proxy_ctx_t *ctx = PROXY_GET_CTX(L);11391140    if (limit < 0) {1141        proxy_lua_error(L, "backend_depth_limit must be >= 0");1142        return 0;1143    }11441145    STAT_L(ctx);1146    ctx->tunables.backend_depth_limit = limit;1147    STAT_UL(ctx);11481149    return 0;1150}11511152static int mcplib_backend_connect_timeout(lua_State *L) {1153    lua_Number secondsf = luaL_checknumber(L, -1);1154    lua_Integer secondsi = (lua_Integer) secondsf;1155    lua_Number subseconds = secondsf - secondsi;1156    proxy_ctx_t *ctx = PROXY_GET_CTX(L);11571158    STAT_L(ctx);1159    ctx->tunables.connect.tv_sec = secondsi;1160    ctx->tunables.connect.tv_usec = MICROSECONDS(subseconds);1161    STAT_UL(ctx);11621163    return 0;1164}11651166static int mcplib_backend_retry_waittime(lua_State *L) {1167    lua_Number secondsf = luaL_checknumber(L, -1);1168    proxy_ctx_t *ctx = PROXY_GET_CTX(L);1169    lua_Integer secondsi = _mcplib_backend_get_waittime(secondsf);11701171    STAT_L(ctx);1172    ctx->tunables.retry.tv_sec = secondsi;1173    ctx->tunables.retry.tv_usec = 0;1174    STAT_UL(ctx);11751176    return 0;1177}11781179// TODO (v2): deprecation notice print when using this function.1180static int mcplib_backend_retry_timeout(lua_State *L) {1181    return mcplib_backend_retry_waittime(L);1182}11831184static int mcplib_backend_read_timeout(lua_State *L) {1185    lua_Number secondsf = luaL_checknumber(L, -1);1186    lua_Integer secondsi = (lua_Integer) secondsf;1187    lua_Number subseconds = secondsf - secondsi;1188    proxy_ctx_t *ctx = PROXY_GET_CTX(L);11891190    STAT_L(ctx);1191    ctx->tunables.read.tv_sec = secondsi;1192    ctx->tunables.read.tv_usec = MICROSECONDS(subseconds);1193    STAT_UL(ctx);11941195    return 0;1196}11971198static int mcplib_backend_flap_time(lua_State *L) {1199    lua_Number secondsf = luaL_checknumber(L, -1);1200    lua_Integer secondsi = (lua_Integer) secondsf;1201    lua_Number subseconds = secondsf - secondsi;1202    proxy_ctx_t *ctx = PROXY_GET_CTX(L);12031204    STAT_L(ctx);1205    ctx->tunables.flap.tv_sec = secondsi;1206    ctx->tunables.flap.tv_usec = MICROSECONDS(subseconds);1207    STAT_UL(ctx);12081209    return 0;1210}12111212static int mcplib_backend_flap_backoff_ramp(lua_State *L) {1213    float factor = luaL_checknumber(L, -1);1214    proxy_ctx_t *ctx = PROXY_GET_CTX(L);1215    if (factor <= 1.1) {1216        factor = 1.1;1217    }12181219    STAT_L(ctx);1220    ctx->tunables.flap_backoff_ramp = factor;1221    STAT_UL(ctx);12221223    return 0;1224}12251226static int mcplib_backend_flap_backoff_max(lua_State *L) {1227    luaL_checknumber(L, -1);1228    uint32_t max = lua_tointeger(L, -1);1229    proxy_ctx_t *ctx = PROXY_GET_CTX(L);12301231    STAT_L(ctx);1232    ctx->tunables.flap_backoff_max = max;1233    STAT_UL(ctx);12341235    return 0;1236}12371238static int mcplib_luagc_ratio(lua_State *L) {1239    float ratio = luaL_checknumber(L, -1);1240    proxy_ctx_t *ctx = PROXY_GET_CTX(L);1241    if (ratio < 1.1) {1242        ratio = 1.1;1243    }12441245    STAT_L(ctx);1246    ctx->tunables.gc_ratio = ratio;1247    STAT_UL(ctx);12481249    return 0;1250}12511252static int mcplib_stat_limit(lua_State *L) {1253    proxy_ctx_t *ctx = PROXY_GET_CTX(L);1254    int limit = luaL_checkinteger(L, -1);12551256    if (limit == 0) {1257        limit = MAX_USTATS_DEFAULT;1258    }1259    if (limit > MAX_USTATS_DEFAULT) {1260        fprintf(stderr, "PROXY WARNING: setting ustats limit above default may cause performance problems\n");1261    }12621263    // lock isn't necessary as this is only used from the config thread.1264    // keeping the lock call for code consistency.1265    STAT_L(ctx);1266    ctx->tunables.max_ustats = limit;1267    STAT_UL(ctx);1268    return 0;1269}12701271static int mcplib_active_req_limit(lua_State *L) {1272    proxy_ctx_t *ctx = PROXY_GET_CTX(L);1273    int64_t limit = luaL_checkinteger(L, -1);12741275    if (limit == 0) {1276        limit = INT64_MAX;1277    } else {1278        // FIXME: global1279        int tcount = settings.num_threads;1280        // The actual limit is per-worker-thread, so divide it up.1281        if (limit > tcount * 2) {1282            limit /= tcount;1283        }1284    }12851286    STAT_L(ctx);1287    ctx->active_req_limit = limit;1288    STAT_UL(ctx);12891290    return 0;1291}12921293// limit specified in kilobytes1294static int mcplib_buffer_memory_limit(lua_State *L) {1295    proxy_ctx_t *ctx = PROXY_GET_CTX(L);1296    uint64_t limit = luaL_checkinteger(L, -1);12971298    if (limit == 0) {1299        limit = UINT64_MAX;1300    } else {1301        limit *= 1024;13021303        int tcount = settings.num_threads;1304        if (limit > tcount * 2) {1305            limit /= tcount;1306        }1307    }1308    ctx->buffer_memory_limit = limit;13091310    return 0;1311}13121313// mcp.attach(mcp.HOOK_NAME, function)1314// fill hook structure: if lua function, use luaL_ref() to store the func1315static int mcplib_attach(lua_State *L) {1316    // Pull the original worker thread out of the shared mcplib upvalue.1317    LIBEVENT_THREAD *t = PROXY_GET_THR(L);13181319    int hook = luaL_checkinteger(L, 1);1320    // pushvalue to dupe func and etc.1321    // can leave original func on stack afterward because it'll get cleared.1322    int loop_end = 0;1323    int loop_start = 1;1324    if (hook == CMD_ANY) {1325        // if CMD_ANY we need individually set loop 1 to CMD_SIZE.1326        loop_end = CMD_SIZE;1327    } else if (hook == CMD_ANY_STORAGE) {1328        // if CMD_ANY_STORAGE we only override get/set/etc.1329        loop_end = CMD_END_STORAGE;1330    } else {1331        loop_start = hook;1332        loop_end = hook + 1;1333    }13341335    mcp_funcgen_t *fgen = NULL;1336    if (lua_isfunction(L, 2)) {1337        // create a funcgen with null generator that calls this function1338        lua_pushvalue(L, 2); // function must be at top of stack.1339        mcplib_funcgenbare_new(L); // convert it into a function generator.1340        fgen = luaL_checkudata(L, -1, "mcp.funcgen"); // set our pointer ref.1341        lua_replace(L, 2); // move the function generator over the input1342                           // function. necessary for alignment with the rest1343                           // of the code.1344        lua_pop(L, 1); // drop the extra generator function reference.1345    } else if ((fgen = luaL_testudata(L, 2, "mcp.funcgen")) != NULL) {1346        // good1347    } else {1348        proxy_lua_error(L, "mcp.attach: must pass a function");1349        return 0;1350    }13511352    if (fgen->closed) {1353        proxy_lua_error(L, "mcp.attach: cannot use a previously replaced function");1354        return 0;1355    }13561357    {1358        struct proxy_hook *hooks = t->proxy_hooks;1359        uint64_t tag = 0; // listener socket tag13601361        if (lua_isstring(L, 3)) {1362            size_t len;1363            const char *stag = lua_tolstring(L, 3, &len);1364            if (len < 1 || len > 8) {1365                proxy_lua_error(L, "mcp.attach: tag must be 1 to 8 characters");1366                return 0;1367            }1368            memcpy(&tag, stag, len);1369        }13701371        for (int x = loop_start; x < loop_end; x++) {1372            struct proxy_hook *h = &hooks[x];1373            if (x == CMD_MN) {1374                // disallow overriding MN so client pipeline flushes work.1375                // need to add flush support before allowing override1376                continue;1377            }1378            lua_pushvalue(L, 2); // duplicate the ref.1379            struct proxy_hook_ref *href = &h->ref;13801381            if (tag) {1382                // listener was tagged. use the extended hook structure.1383                struct proxy_hook_tagged *pht = h->tagged;13841385                if (h->tagcount == 0) {1386                    pht = calloc(1, sizeof(struct proxy_hook_tagged));1387                    if (pht == NULL) {1388                        proxy_lua_error(L, "mcp.attach: failure allocating tagged hooks");1389                        return 0;1390                    }1391                    h->tagcount = 1;1392                    h->tagged = pht;1393                }13941395                bool found = false;1396                for (int x = 0; x < h->tagcount; x++) {1397                    if (pht->tag == tag || pht->tag == 0) {1398                        found = true;1399                        break;1400                    }1401                    pht++;1402                }14031404                // need to resize the array to fit the new tag.1405                if (!found) {1406                    struct proxy_hook_tagged *temp = realloc(h->tagged, sizeof(struct proxy_hook_tagged) * (h->tagcount+1));1407                    if (!temp) {1408                        proxy_lua_error(L, "mcp.attach: failure to resize tagged hooks");1409                        return 0;1410                    }1411                    pht = &temp[h->tagcount];1412                    memset(pht, 0, sizeof(*pht));1413                    h->tagcount++;1414                    h->tagged = temp;1415                }14161417                href = &pht->ref;1418                pht->tag = tag;1419            }14201421            // now assign our hook reference.1422            if (href->lua_ref) {1423                // Found existing tagged hook.1424                luaL_unref(L, LUA_REGISTRYINDEX, href->lua_ref);1425                mcp_funcgen_dereference(L, href->ctx);1426            }14271428            lua_pushvalue(L, -1); // duplicate the funcgen1429            mcp_funcgen_reference(L);1430            href->lua_ref = luaL_ref(L, LUA_REGISTRYINDEX);1431            href->ctx = fgen;1432            assert(href->lua_ref != 0);1433        }1434    }14351436    return 0;1437}14381439/*** START lua interface to logger ***/14401441// user logger specific to the config thread1442static int mcplib_ct_log(lua_State *L) {1443    const char *msg = luaL_checkstring(L, -1);1444    // The only difference is we pull the logger from thread local storage.1445    LOGGER_LOG(NULL, LOG_PROXYUSER, LOGGER_PROXY_USER, NULL, msg);1446    return 0;1447}14481449static int mcplib_log(lua_State *L) {1450    LIBEVENT_THREAD *t = PROXY_GET_THR(L);1451    const char *msg = luaL_checkstring(L, -1);1452    LOGGER_LOG(t->l, LOG_PROXYUSER, LOGGER_PROXY_USER, NULL, msg);1453    return 0;1454}14551456// (request, resp, "detail")1457static int mcplib_log_req(lua_State *L) {1458    LIBEVENT_THREAD *t = PROXY_GET_THR(L);1459    logger *l = t->l;1460    // Not using the LOGGER_LOG macro so we can avoid as much overhead as1461    // possible when logging is disabled.1462    if (! (l->eflags & LOG_PROXYREQS)) {1463        return 0;1464    }1465    int rtype = 0;1466    int rcode = 0;1467    int rstatus = 0;1468    long elapsed = 0;1469    char *rname = NULL;1470    char *rport = NULL;14711472    mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");1473    int type = lua_type(L, 2);1474    if (type == LUA_TUSERDATA) {1475        mcp_resp_t *rs = luaL_checkudata(L, 2, "mcp.response");1476        rtype = rs->resp.type;1477        rcode = rs->resp.code;1478        rstatus = rs->status;1479        if (rs->be) {1480            rname = rs->be->name;1481            rport = rs->be->port;1482        } else {1483            rname = "internal";1484            rport = "0";1485        }1486        elapsed = rs->elapsed;1487    }1488    size_t dlen = 0;1489    const char *detail = luaL_optlstring(L, 3, NULL, &dlen);1490    int cfd = luaL_optinteger(L, 4, 0);1491    uint8_t flag = RQUEUE_R_ANY;1492    if (rstatus == MCMC_OK) {1493        if (rcode != MCMC_CODE_END) {1494            flag = RQUEUE_R_GOOD;1495        } else {1496            flag = RQUEUE_R_OK;1497        }1498    }14991500    logger_log(l, LOGGER_PROXY_REQ, NULL, rq->pr.request, rq->pr.reqlen, elapsed, rtype, rcode, rstatus, flag, cfd, detail, dlen, rname, rport);15011502    return 0;1503}15041505static inline uint32_t _mcp_rotl(const uint32_t x, int k) {1506    return (x << k) | (x >> (32 - k));1507}15081509// xoroshiro128++ 32bit version.1510static uint32_t _mcp_nextrand(uint32_t *s) {1511    const uint32_t result = _mcp_rotl(s[0] + s[3], 7) + s[0];15121513    const uint32_t t = s[1] << 9;15141515    s[2] ^= s[0];1516    s[3] ^= s[1];1517    s[1] ^= s[2];1518    s[0] ^= s[3];15191520    s[2] ^= t;15211522    s[3] = _mcp_rotl(s[3], 11);15231524    return result;1525}15261527void mcplib_rqu_log(mcp_request_t *rq, mcp_resp_t *rs, int flag, int cfd) {1528    LIBEVENT_THREAD *t = rs->thread;1529    logger *l = t->l;15301531    long elapsed = 0;15321533    int rtype = rs->resp.type;1534    int rcode = rs->resp.code;1535    int rstatus = rs->status;1536    elapsed = rs->elapsed;15371538    bool do_log = false;1539    struct proxy_logging *pl = &rs->be->logging;1540    if (pl->rate == 1) {1541        do_log = true;1542    } else if (pl->all_errors && rstatus != MCMC_OK) {1543        do_log = true;1544    } else if (pl->deadline > 0 && elapsed > pl->deadline) {1545        do_log = true;1546    } else if (pl->rate > 0) {1547        // slightly biased random-to-rate without adding a loop, which is1548        // completely fine for this use case.1549        uint32_t rnd = (uint64_t)_mcp_nextrand(t->proxy_rng) * (uint64_t)pl->rate >> 32;1550        if (rnd == 0) {1551            do_log = true;1552        }1553    }15541555    if (do_log) {1556        char *rname = rs->be->name;1557        char *rport = rs->be->port;1558        size_t dlen = 0;1559        const char *detail = rs->be->logging.detail;15601561        if (detail) {1562            dlen = strlen(detail);1563        }15641565        logger_log(l, LOGGER_PROXY_REQ, NULL, rq->pr.request, rq->pr.reqlen, elapsed, rtype, rcode, rstatus, flag, cfd, detail, dlen, rname, rport);1566    }1567}15681569// (milliseconds, sample_rate, allerrors, request, resp, "detail")1570static int mcplib_log_reqsample(lua_State *L) {1571    LIBEVENT_THREAD *t = PROXY_GET_THR(L);1572    logger *l = t->l;1573    // Not using the LOGGER_LOG macro so we can avoid as much overhead as1574    // possible when logging is disabled.1575    if (! (l->eflags & LOG_PROXYREQS)) {1576        return 0;1577    }1578    int rtype = 0;1579    int rcode = 0;1580    int rstatus = 0;1581    long elapsed = 0;1582    char *rname = NULL;1583    char *rport = NULL;15841585    int ms = luaL_checkinteger(L, 1);1586    int rate = luaL_checkinteger(L, 2);1587    int allerr = lua_toboolean(L, 3);1588    mcp_request_t *rq = luaL_checkudata(L, 4, "mcp.request");1589    int type = lua_type(L, 5);1590    if (type == LUA_TUSERDATA) {1591        mcp_resp_t *rs = luaL_checkudata(L, 5, "mcp.response");1592        rtype = rs->resp.type;1593        rcode = rs->resp.code;1594        rstatus = rs->status;1595        if (rs->be) {1596            rname = rs->be->name;1597            rport = rs->be->port;1598        } else {1599            rname = "internal";1600            rport = "0";1601        }1602        elapsed = rs->elapsed;1603    }1604    size_t dlen = 0;1605    const char *detail = luaL_optlstring(L, 6, NULL, &dlen);1606    int cfd = luaL_optinteger(L, 7, 0);16071608    bool do_log = false;1609    if (allerr && rstatus != MCMC_OK) {1610        do_log = true;1611    } else if (ms > 0 && elapsed > ms * 1000) {1612        do_log = true;1613    } else if (rate > 0) {1614        // slightly biased random-to-rate without adding a loop, which is1615        // completely fine for this use case.1616        uint32_t rnd = (uint64_t)_mcp_nextrand(t->proxy_rng) * (uint64_t)rate >> 32;1617        if (rnd == 0) {1618            do_log = true;1619        }1620    }1621    uint8_t flag = RQUEUE_R_ANY;1622    if (rstatus == MCMC_OK) {1623        if (rcode != MCMC_CODE_END) {1624            flag = RQUEUE_R_GOOD;1625        } else {1626            flag = RQUEUE_R_OK;1627        }1628    }16291630    if (do_log) {1631        logger_log(l, LOGGER_PROXY_REQ, NULL, rq->pr.request, rq->pr.reqlen, elapsed, rtype, rcode, rstatus, flag, cfd, detail, dlen, rname, rport);1632    }16331634    return 0;1635}16361637// TODO: slowsample1638// _err versions?16391640/*** END lua interface to logger ***/16411642static void proxy_register_defines(lua_State *L) {1643#define X(x) \1644    lua_pushinteger(L, x); \1645    lua_setfield(L, -2, #x);1646#define Y(x, l) \1647    lua_pushinteger(L, x); \1648    lua_setfield(L, -2, l);16491650    X(MCMC_CODE_STORED);1651    X(MCMC_CODE_EXISTS);1652    X(MCMC_CODE_DELETED);1653    X(MCMC_CODE_TOUCHED);1654    X(MCMC_CODE_VERSION);1655    X(MCMC_CODE_NOT_FOUND);1656    X(MCMC_CODE_NOT_STORED);1657    X(MCMC_CODE_OK);1658    X(MCMC_CODE_NOP);1659    X(MCMC_CODE_END);1660    X(MCMC_CODE_ERROR);1661    X(MCMC_CODE_CLIENT_ERROR);1662    X(MCMC_CODE_SERVER_ERROR);1663    X(MCMC_ERR);1664    X(P_OK);1665    X(CMD_ANY);1666    X(CMD_ANY_STORAGE);1667    Y(QWAIT_ANY, "WAIT_ANY");1668    Y(QWAIT_OK, "WAIT_OK");1669    Y(QWAIT_GOOD, "WAIT_GOOD");1670    Y(QWAIT_FASTGOOD, "WAIT_FASTGOOD");1671    Y(RQUEUE_R_GOOD, "RES_GOOD");1672    Y(RQUEUE_R_OK, "RES_OK");1673    Y(RQUEUE_R_ANY, "RES_ANY");1674    CMD_FIELDS1675#undef X1676#undef Y16771678    lua_pushboolean(L, 1);1679    lua_setfield(L, -2, "WAIT_RESUME");1680}16811682// TODO: low priority malloc error handling.1683static void proxy_register_startarg(lua_State *L) {1684    int idx = lua_absindex(L, -1); // remember 'mcp' table.1685    if (settings.proxy_startarg == NULL) {1686        // no argument given.1687        lua_pushboolean(L, 0);1688        lua_setfield(L, idx, "start_arg");1689        return;1690    }16911692    char *sarg = strdup(settings.proxy_startarg);1693    if (strchr(sarg, ':') == NULL) {1694        // just upload the string1695        lua_pushstring(L, sarg);1696    } else {1697        // split into a table and set that instead.1698        lua_newtable(L);1699        int nidx = lua_absindex(L, -1);1700        char *b = NULL;1701        for (char *p = strtok_r(sarg, ":", &b);1702                p != NULL;1703                p = strtok_r(NULL, ":", &b)) {1704            char *e = NULL;1705            char *name = strtok_r(p, "_", &e);1706            lua_pushstring(L, name); // table -> key1707            char *value = strtok_r(NULL, "_", &e);1708            if (value == NULL) {1709                lua_pushboolean(L, 1); // table -> key -> True1710            } else {1711                lua_pushstring(L, value); // table -> key -> value1712            }1713            lua_settable(L, nidx);1714        }1715    }1716    free(sarg);1717    lua_setfield(L, idx, "start_arg");1718}17191720// Creates and returns the top level "mcp" module1721int proxy_register_libs(void *ctx, LIBEVENT_THREAD *t, void *state) {1722    lua_State *L = state;17231724    const struct luaL_Reg mcplib_backend_m[] = {1725        {"__gc", mcplib_backend_gc},1726        {NULL, NULL}1727    };17281729    const struct luaL_Reg mcplib_backend_wrap_m[] = {1730        {"__gc", mcplib_backend_wrap_gc},1731        {NULL, NULL}1732    };17331734    const struct luaL_Reg mcplib_request_m[] = {1735        {"command", mcplib_request_command},1736        {"key", mcplib_request_key},1737        {"ltrimkey", mcplib_request_ltrimkey},1738        {"rtrimkey", mcplib_request_rtrimkey},1739        {"raw_line", mcplib_request_raw_line},1740        {"raw_value", mcplib_request_raw_value},1741        {"token", mcplib_request_token},1742        {"token_int", mcplib_request_token_int},1743        {"ntokens", mcplib_request_ntokens},1744        {"has_flag", mcplib_request_has_flag},1745        {"flag_token", mcplib_request_flag_token},1746        {"flag_token_int", mcplib_request_flag_token_int},1747        {"flag_add", mcplib_request_flag_add},1748        {"flag_set", mcplib_request_flag_set},1749        {"flag_replace", mcplib_request_flag_replace},1750        {"flag_del", mcplib_request_flag_del},1751        {"match_res", mcplib_request_match_res},1752        {"__tostring", NULL},1753        {"__gc", mcplib_request_gc},1754        {NULL, NULL}1755    };17561757    const struct luaL_Reg mcplib_response_m[] = {1758        {"ok", mcplib_response_ok},1759        {"hit", mcplib_response_hit},1760        {"vlen", mcplib_response_vlen},1761        {"code", mcplib_response_code},1762        {"line", mcplib_response_line},1763        {"flag_blank", mcplib_response_flag_blank},1764        {"elapsed", mcplib_response_elapsed},1765        {"raw_string", mcplib_response_raw_string},1766        {"__gc", mcplib_response_gc},1767        {"__close", mcplib_response_close},1768        {"close", mcplib_response_close},1769        {NULL, NULL}1770    };17711772    const struct luaL_Reg mcplib_pool_m[] = {1773        {"__gc", mcplib_pool_gc},1774        {NULL, NULL}1775    };17761777    const struct luaL_Reg mcplib_pool_proxy_m[] = {1778        {"__gc", mcplib_pool_proxy_gc},1779        {NULL, NULL}1780    };17811782    const struct luaL_Reg mcplib_ratelim_tbf_m[] = {1783        {"__call", mcplib_ratelim_tbf_call},1784        {NULL, NULL}1785    };17861787    const struct luaL_Reg mcplib_ratelim_global_tbf_m[] = {1788        {"__gc", mcplib_ratelim_global_tbf_gc},1789        {NULL, NULL}1790    };17911792    const struct luaL_Reg mcplib_ratelim_proxy_tbf_m[] = {1793        {"__call", mcplib_ratelim_proxy_tbf_call},1794        {"__gc", mcplib_ratelim_proxy_tbf_gc},1795        {NULL, NULL}1796    };17971798    const struct luaL_Reg mcplib_rcontext_m[] = {1799        {"handle_set_cb", mcplib_rcontext_handle_set_cb},1800        {"enqueue", mcplib_rcontext_enqueue},1801        {"wait_cond", mcplib_rcontext_wait_cond},1802        {"enqueue_and_wait", mcplib_rcontext_enqueue_and_wait},1803        {"wait_handle", mcplib_rcontext_wait_handle},1804        {"res_good", mcplib_rcontext_res_good},1805        {"res_ok", mcplib_rcontext_res_ok},1806        {"res_any", mcplib_rcontext_res_any},1807        {"result", mcplib_rcontext_result},1808        {"best_result", mcplib_rcontext_best_result},1809        {"worst_result", mcplib_rcontext_worst_result},1810        {"cfd", mcplib_rcontext_cfd},1811        {"tls_peer_cn", mcplib_rcontext_tls_peer_cn},1812        {"request_new", mcplib_rcontext_request_new},1813        {"response_new", mcplib_rcontext_response_new},1814        {"sleep", mcplib_rcontext_sleep},1815        {NULL, NULL}1816    };18171818    const struct luaL_Reg mcplib_funcgen_m[] = {1819        {"__gc", mcplib_funcgen_gc},1820        {"new_handle", mcplib_funcgen_new_handle},1821        {"ready", mcplib_funcgen_ready},1822        {NULL, NULL}1823    };18241825    const struct luaL_Reg mcplib_inspector_m[] = {1826        {"__gc", mcplib_inspector_gc},1827        {"__call", mcplib_inspector_call},1828        {NULL, NULL},1829    };18301831    const struct luaL_Reg mcplib_mutator_m[] = {1832        {"__gc", mcplib_mutator_gc},1833        {"__call", mcplib_mutator_call},1834        {NULL, NULL},1835    };18361837    const struct luaL_Reg mcplib_f_config [] = {1838        {"pool", mcplib_pool},1839        {"backend", mcplib_backend},1840        {"add_stat", mcplib_add_stat},1841        {"ratelim_global_tbf", mcplib_ratelim_global_tbf},1842        {"luagc_ratio", mcplib_luagc_ratio},1843        {"stat_limit", mcplib_stat_limit},1844        {"backend_connect_timeout", mcplib_backend_connect_timeout},1845        {"backend_retry_timeout", mcplib_backend_retry_timeout},1846        {"backend_retry_waittime", mcplib_backend_retry_waittime},1847        {"backend_read_timeout", mcplib_backend_read_timeout},1848        {"backend_failure_limit", mcplib_backend_failure_limit},1849        {"backend_depth_limit", mcplib_backend_depth_limit},1850        {"backend_flap_time", mcplib_backend_flap_time},1851        {"backend_flap_backoff_ramp", mcplib_backend_flap_backoff_ramp},1852        {"backend_flap_backoff_max", mcplib_backend_flap_backoff_max},1853        {"backend_use_iothread", mcplib_backend_use_iothread},1854        {"backend_use_tls", mcplib_backend_use_tls},1855        {"init_tls", mcplib_init_tls},1856        {"tcp_keepalive", mcplib_tcp_keepalive},1857        {"active_req_limit", mcplib_active_req_limit},1858        {"buffer_memory_limit", mcplib_buffer_memory_limit},1859        {"schedule_config_reload", mcplib_schedule_config_reload},1860        {"register_cron", mcplib_register_cron},1861        {"server_stats", mcplib_server_stats},1862        {"log", mcplib_ct_log},1863        {NULL, NULL}1864    };18651866    const struct luaL_Reg mcplib_f_routes [] = {1867        {"internal", mcplib_internal},1868        {"attach", mcplib_attach},1869        {"funcgen_new", mcplib_funcgen_new},1870        {"router_new", mcplib_router_new},1871        {"log", mcplib_log},1872        {"log_req", mcplib_log_req},1873        {"log_reqsample", mcplib_log_reqsample},1874        {"stat", mcplib_stat},1875        {"request", mcplib_request},1876        {"ratelim_tbf", mcplib_ratelim_tbf},1877        {"req_inspector_new", mcplib_req_inspector_new},1878        {"res_inspector_new", mcplib_res_inspector_new},1879        {"req_mutator_new", mcplib_req_mutator_new},1880        {"res_mutator_new", mcplib_res_mutator_new},1881        {"time_real_millis", mcplib_time_real_millis},1882        {"time_mono_millis", mcplib_time_mono_millis},1883        {NULL, NULL}1884    };1885    // VM's have void* extra space in the VM by default for fast-access to a1886    // context pointer like this. In some cases upvalues are inaccessible (ie;1887    // GC's) but we still need access to the proxy global context.1888    void **extra = lua_getextraspace(L);18891890    if (t != NULL) {1891        // If thread VM, extra is the libevent thread1892        *extra = t;1893        luaL_newmetatable(L, "mcp.request");1894        lua_pushvalue(L, -1); // duplicate metatable.1895        lua_setfield(L, -2, "__index"); // mt.__index = mt1896        luaL_setfuncs(L, mcplib_request_m, 0); // register methods1897        lua_pop(L, 1);18981899        luaL_newmetatable(L, "mcp.response");1900        lua_pushvalue(L, -1); // duplicate metatable.1901        lua_setfield(L, -2, "__index"); // mt.__index = mt1902        luaL_setfuncs(L, mcplib_response_m, 0); // register methods1903        lua_pop(L, 1);19041905        luaL_newmetatable(L, "mcp.pool_proxy");1906        lua_pushvalue(L, -1); // duplicate metatable.1907        lua_setfield(L, -2, "__index"); // mt.__index = mt1908        luaL_setfuncs(L, mcplib_pool_proxy_m, 0); // register methods1909        lua_pop(L, 1); // drop the hash selector metatable19101911        luaL_newmetatable(L, "mcp.ratelim_tbf");1912        lua_pushvalue(L, -1); // duplicate metatable.1913        lua_setfield(L, -2, "__index"); // mt.__index = mt1914        luaL_setfuncs(L, mcplib_ratelim_tbf_m, 0); // register methods1915        lua_pop(L, 1);19161917        luaL_newmetatable(L, "mcp.ratelim_proxy_tbf");1918        lua_pushvalue(L, -1); // duplicate metatable.1919        lua_setfield(L, -2, "__index"); // mt.__index = mt1920        luaL_setfuncs(L, mcplib_ratelim_proxy_tbf_m, 0); // register methods1921        lua_pop(L, 1);19221923        luaL_newmetatable(L, "mcp.inspector");1924        lua_pushvalue(L, -1); // duplicate metatable.1925        lua_setfield(L, -2, "__index"); // mt.__index = mt1926        luaL_setfuncs(L, mcplib_inspector_m, 0); // register methods1927        lua_pop(L, 1);19281929        luaL_newmetatable(L, "mcp.mutator");1930        lua_pushvalue(L, -1); // duplicate metatable.1931        lua_setfield(L, -2, "__index"); // mt.__index = mt1932        luaL_setfuncs(L, mcplib_mutator_m, 0); // register methods1933        lua_pop(L, 1);19341935        luaL_newmetatable(L, "mcp.rcontext");1936        lua_pushvalue(L, -1); // duplicate metatable.1937        lua_setfield(L, -2, "__index"); // mt.__index = mt1938        luaL_setfuncs(L, mcplib_rcontext_m, 0); // register methods1939        lua_pop(L, 1);19401941        luaL_newmetatable(L, "mcp.funcgen");1942        lua_pushvalue(L, -1); // duplicate metatable.1943        lua_setfield(L, -2, "__index"); // mt.__index = mt1944        luaL_setfuncs(L, mcplib_funcgen_m, 0); // register methods1945        lua_pop(L, 1);19461947        // marks a special C-compatible route function.1948        luaL_newmetatable(L, "mcp.rfunc");1949        lua_pop(L, 1);19501951        // function generator userdata.1952        luaL_newmetatable(L, "mcp.funcgen");1953        lua_pop(L, 1);19541955        // mt for magical null wrapper for using internal cache as backend1956        luaL_newmetatable(L, "mcp.internal_be");1957        lua_pop(L, 1);19581959        luaL_newlibtable(L, mcplib_f_routes);1960    } else {1961        // Change the extra space override for the configuration VM to just point1962        // straight to ctx.1963        *extra = ctx;19641965        luaL_newmetatable(L, "mcp.backend");1966        lua_pushvalue(L, -1); // duplicate metatable.1967        lua_setfield(L, -2, "__index"); // mt.__index = mt1968        luaL_setfuncs(L, mcplib_backend_m, 0); // register methods1969        lua_pop(L, 1);19701971        luaL_newmetatable(L, "mcp.backendwrap");1972        lua_pushvalue(L, -1); // duplicate metatable.1973        lua_setfield(L, -2, "__index"); // mt.__index = mt1974        luaL_setfuncs(L, mcplib_backend_wrap_m, 0); // register methods1975        lua_pop(L, 1);19761977        luaL_newmetatable(L, "mcp.pool");1978        lua_pushvalue(L, -1); // duplicate metatable.1979        lua_setfield(L, -2, "__index"); // mt.__index = mt1980        luaL_setfuncs(L, mcplib_pool_m, 0); // register methods1981        lua_pop(L, 1); // drop the hash selector metatable19821983        luaL_newmetatable(L, "mcp.ratelim_global_tbf");1984        lua_pushvalue(L, -1); // duplicate metatable.1985        lua_setfield(L, -2, "__index"); // mt.__index = mt1986        luaL_setfuncs(L, mcplib_ratelim_global_tbf_m, 0); // register methods1987        lua_pop(L, 1);19881989        luaL_newlibtable(L, mcplib_f_config);1990    }19911992    // Create magic empty value to pass as an internal backend.1993    lua_newuserdatauv(L, 1, 0);1994    luaL_getmetatable(L, "mcp.internal_be");1995    lua_setmetatable(L, -2);1996    lua_setfield(L, -2, "internal_handler");19971998    // create main library table.1999    //luaL_newlib(L, mcplib_f);2000    // TODO (v2): luaL_newlibtable() just pre-allocs the exact number of things

Code quality findings 21

Warning: Allocation result must be checked for NULL before use to prevent null pointer dereference.
warning correctness malloc-unchecked
be->logging.detail = malloc(tlen+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(be->transferred);
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(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(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(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(p->phc.ctx != NULL);
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(href->lua_ref != 0);
Info: The 'register' keyword is deprecated and generally ignored by modern compilers. It can be safely removed.
info deprecated register-deprecated
luaL_setfuncs(L, mcplib_request_m, 0); // register methods
Info: The 'register' keyword is deprecated and generally ignored by modern compilers. It can be safely removed.
info deprecated register-deprecated
luaL_setfuncs(L, mcplib_response_m, 0); // register methods
Info: The 'register' keyword is deprecated and generally ignored by modern compilers. It can be safely removed.
info deprecated register-deprecated
luaL_setfuncs(L, mcplib_pool_proxy_m, 0); // register methods
Info: The 'register' keyword is deprecated and generally ignored by modern compilers. It can be safely removed.
info deprecated register-deprecated
luaL_setfuncs(L, mcplib_ratelim_tbf_m, 0); // register methods
Info: The 'register' keyword is deprecated and generally ignored by modern compilers. It can be safely removed.
info deprecated register-deprecated
luaL_setfuncs(L, mcplib_ratelim_proxy_tbf_m, 0); // register methods
Info: The 'register' keyword is deprecated and generally ignored by modern compilers. It can be safely removed.
info deprecated register-deprecated
luaL_setfuncs(L, mcplib_inspector_m, 0); // register methods
Info: The 'register' keyword is deprecated and generally ignored by modern compilers. It can be safely removed.
info deprecated register-deprecated
luaL_setfuncs(L, mcplib_mutator_m, 0); // register methods
Info: The 'register' keyword is deprecated and generally ignored by modern compilers. It can be safely removed.
info deprecated register-deprecated
luaL_setfuncs(L, mcplib_rcontext_m, 0); // register methods
Info: The 'register' keyword is deprecated and generally ignored by modern compilers. It can be safely removed.
info deprecated register-deprecated
luaL_setfuncs(L, mcplib_funcgen_m, 0); // register methods
Info: The 'register' keyword is deprecated and generally ignored by modern compilers. It can be safely removed.
info deprecated register-deprecated
luaL_setfuncs(L, mcplib_backend_m, 0); // register methods
Info: The 'register' keyword is deprecated and generally ignored by modern compilers. It can be safely removed.
info deprecated register-deprecated
luaL_setfuncs(L, mcplib_backend_wrap_m, 0); // register methods
Info: The 'register' keyword is deprecated and generally ignored by modern compilers. It can be safely removed.
info deprecated register-deprecated
luaL_setfuncs(L, mcplib_pool_m, 0); // register methods
Info: The 'register' keyword is deprecated and generally ignored by modern compilers. It can be safely removed.
info deprecated register-deprecated
luaL_setfuncs(L, mcplib_ratelim_global_tbf_m, 0); // register methods

Security findings 16

Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(be->logging.detail, tag, tlen);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(&be->tunables, &ctx->tunables, sizeof(be->tunables));
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(be->label, label, llen);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(be->name, name, nlen);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(be->port, port, plen);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(&be->tunables, &bel->tunables, sizeof(bel->tunables));
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(&be->logging, &bel->logging, sizeof(bel->logging));
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(&bec->tunables, &bel->tunables, sizeof(bel->tunables));
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(temp, key, klen);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(&p->phc, phc, sizeof(*phc));
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(p->beprefix, bepfx, 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(p->key_filter_conf, conf, 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(&tag, stag, len);
Info: rand() produces predictable sequences unless seeded with srand(). For security-sensitive randomness, use a cryptographic RNG.
security predictable-rand
static uint32_t _mcp_nextrand(uint32_t *s) {
Info: rand() produces predictable sequences unless seeded with srand(). For security-sensitive randomness, use a cryptographic RNG.
security predictable-rand
uint32_t rnd = (uint64_t)_mcp_nextrand(t->proxy_rng) * (uint64_t)pl->rate >> 32;
Info: rand() produces predictable sequences unless seeded with srand(). For security-sensitive randomness, use a cryptographic RNG.
security predictable-rand
uint32_t rnd = (uint64_t)_mcp_nextrand(t->proxy_rng) * (uint64_t)rate >> 32;

Get this view in your editor

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