proto_proxy.c C 1,634 lines View on github.com → Search inside
1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */2/*3 * Functions for handling the proxy layer. wraps text protocols4 *5 * NOTE: many lua functions generate pointers via "lua_newuserdatauv" or6 * similar. Normal memory checking isn't done as lua will throw a high level7 * error if malloc fails. Must keep this in mind while allocating data so any8 * manually malloc'ed information gets freed properly.9 */1011#include "proxy.h"1213#define PROCESS_MULTIGET true14#define PROCESS_NORMAL false15#define PROXY_GC_BACKGROUND_SECONDS 416#define PROXY_GC_DEFAULT_RATIO 2.017static void proxy_process_command(conn *c, char *command, size_t cmdlen, bool multiget);18static void *mcp_profile_alloc(void *ud, void *ptr, size_t osize, size_t nsize);1920/******** EXTERNAL FUNCTIONS ******/21// functions starting with _ are breakouts for the public functions.2223static inline void _proxy_advance_lastkb(lua_State *L, LIBEVENT_THREAD *t) {24    int new_kb = lua_gc(L, LUA_GCCOUNT);25    // We need to slew the increase in "gc pause" because the lua GC actually26    // needs to run twice to free a userdata: once to run the _gc's and again27    // to actually clean up the object.28    // Meaning we will continually increase in size.29    if (new_kb > t->proxy_vm_last_kb) {30        new_kb = t->proxy_vm_last_kb + (new_kb - t->proxy_vm_last_kb) * 0.50;31    }3233    // remove the memory freed during this cycle so we can kick off the GC34    // early if we're very aggressively making garbage.35    // carry our negative delta forward so a huge reclaim can push for a36    // couple cycles.37    if (t->proxy_vm_negative_delta >= new_kb) {38        t->proxy_vm_negative_delta -= new_kb;39        new_kb = 1;40    } else {41        new_kb -= t->proxy_vm_negative_delta;42        t->proxy_vm_negative_delta = 0;43    }4445    t->proxy_vm_last_kb = new_kb;46}4748// The lua GC is paused while running requests. Run it manually inbetween49// processing network events.50void proxy_gc_poke(LIBEVENT_THREAD *t) {51    lua_State *L = t->L;52    proxy_ctx_t *ctx = t->proxy_ctx;53    float ratio = ctx->tunables.gc_ratio;54    struct proxy_int_stats *is = t->proxy_int_stats;55    int vm_kb = lua_gc(L, LUA_GCCOUNT) + t->proxy_vm_extra_kb;56    WSTAT_L(t);57    is->vm_memory_kb = vm_kb;58    WSTAT_UL(t);5960    // equivalent of luagc "pause" value61    int last = t->proxy_vm_last_kb;62    if (t->proxy_vm_gcrunning <= 0 && vm_kb > last * ratio) {63        t->proxy_vm_gcrunning = 1;64        //fprintf(stderr, "PROXYGC: proxy_gc_poke START [cur: %d - last: %d - ratio: %f]\n", vm_kb, last, ratio);65    }6667    // We configure small GC "steps" then increase the number of times we run68    // a step based on current memory usage.69    if (t->proxy_vm_gcrunning > 0) {70        t->proxy_vm_gcpokemem = 0;71        int loops = t->proxy_vm_gcrunning;72        int done = 0;73        /*fprintf(stderr, "PROXYGC: proxy_gc_poke [cur: %d - last: %d - loops: %d]\n",74            vm_kb,75            t->proxy_vm_last_kb,76            loops);*/77        while (loops-- && !done) {78            // reset counters once full GC cycle has completed79            done = lua_gc(L, LUA_GCSTEP, 0);80        }8182        int vm_kb_after = lua_gc(L, LUA_GCCOUNT);83        int vm_kb_clean = vm_kb - t->proxy_vm_extra_kb;84        if (vm_kb_clean > vm_kb_after) {85            // track the amount of memory freed during the GC cycle.86            t->proxy_vm_negative_delta += vm_kb_clean - vm_kb_after;87        }8889        if (done) {90            _proxy_advance_lastkb(L, t);91            t->proxy_vm_extra_kb = 0;92            t->proxy_vm_gcrunning = 0;93            WSTAT_L(t);94            is->vm_gc_runs++;95            WSTAT_UL(t);96            //fprintf(stderr, "PROXYGC: proxy_gc_poke COMPLETE [cur: %d next: %d]\n", lua_gc(L, LUA_GCCOUNT), t->proxy_vm_last_kb);97        } else if ((last*ratio) * (1 + t->proxy_vm_gcrunning*0.20) < vm_kb) {98            // increase the aggressiveness by memory bloat level.99            t->proxy_vm_gcrunning++;100            //fprintf(stderr, "PROXYGC: proxy_gc_poke INCREASING AGGRESSIVENESS [cur: %d - aggro: %d]\n", t->proxy_vm_last_kb, t->proxy_vm_gcrunning);101        }102    }103}104105// every couple seconds we force-run one GC step.106// this is needed until after API1 is retired and pool objects are no longer107// managed by the GC.108// We use a negative value so a "timer poke" GC run doesn't cause requests to109// suddenly aggressively run the GC.110static void proxy_gc_timerpoke(evutil_socket_t fd, short event, void *arg) {111    LIBEVENT_THREAD *t = arg;112    struct timeval next = { PROXY_GC_BACKGROUND_SECONDS, 0 };113    evtimer_add(t->proxy_gc_timer, &next);114    // if GC ran recently, don't do anything.115    // also if memory changed recently, don't do anything.116    int curmem = lua_gc(t->L, LUA_GCCOUNT);117    if (t->proxy_vm_gcpokemem == 0 || curmem != t->proxy_vm_gcpokemem) {118        t->proxy_vm_gcpokemem = curmem;119        return;120    }121122    // if we weren't told to skip and there's otherwise no GC running, start a123    // GC run.124    if (t->proxy_vm_gcrunning == 0) {125        t->proxy_vm_gcrunning = -1;126    }127128    // only advance GC if we're doing our own timer run.129    if (t->proxy_vm_gcrunning == -1) {130        if (lua_gc(t->L, LUA_GCSTEP, 0)) {131            // don't advance last_kb: let the main algo decide when to run.132            t->proxy_vm_gcrunning = 0;133            t->proxy_vm_gcpokemem = 0;134        } else {135            // only continue running if memory stays where we expect it.136            t->proxy_vm_gcpokemem = lua_gc(t->L, LUA_GCCOUNT);137        }138    }139}140141bool proxy_bufmem_checkadd(LIBEVENT_THREAD *t, int len) {142    bool oom = false;143    if (t->proxy_buffer_memory_used > t->proxy_buffer_memory_limit) {144        oom = true;145    } else {146        t->proxy_buffer_memory_used += len;147    }148    return oom;149}150151// see also: process_extstore_stats()152void proxy_stats(void *arg, ADD_STAT add_stats, void *c) {153    if (arg == NULL) {154       return;155    }156    proxy_ctx_t *ctx = arg;157158    STAT_L(ctx);159    APPEND_STAT("proxy_config_reloads", "%llu", (unsigned long long)ctx->global_stats.config_reloads);160    APPEND_STAT("proxy_config_reload_fails", "%llu", (unsigned long long)ctx->global_stats.config_reload_fails);161    APPEND_STAT("proxy_config_cron_runs", "%llu", (unsigned long long)ctx->global_stats.config_cron_runs);162    APPEND_STAT("proxy_config_cron_fails", "%llu", (unsigned long long)ctx->global_stats.config_cron_fails);163    APPEND_STAT("proxy_backend_total", "%llu", (unsigned long long)ctx->global_stats.backend_total);164    APPEND_STAT("proxy_backend_marked_bad", "%llu", (unsigned long long)ctx->global_stats.backend_marked_bad);165    APPEND_STAT("proxy_backend_failed", "%llu", (unsigned long long)ctx->global_stats.backend_failed);166    APPEND_STAT("proxy_request_failed_depth", "%llu", (unsigned long long)ctx->global_stats.request_failed_depth);167    STAT_UL(ctx);168}169170void process_proxy_stats(void *arg, ADD_STAT add_stats, void *c) {171    char key_str[STAT_KEY_LEN];172    struct proxy_int_stats istats = {0};173    int64_t req_limit = 0;174    uint64_t buffer_memory_limit = 0;175    uint64_t buffer_memory_used = 0;176177    if (!arg) {178        return;179    }180    proxy_ctx_t *ctx = arg;181    STAT_L(ctx);182    req_limit = ctx->active_req_limit;183    buffer_memory_limit = ctx->buffer_memory_limit;184185    // prepare aggregated counters.186    struct proxy_user_stats_entry *us = ctx->user_stats;187    int stats_num = ctx->user_stats_num;188    uint64_t counters[stats_num];189    memset(counters, 0, sizeof(counters));190191    // TODO (v3): more globals to remove and/or change API method.192    // aggregate worker thread counters.193    for (int x = 0; x < settings.num_threads; x++) {194        LIBEVENT_THREAD *t = get_worker_thread(x);195        struct proxy_user_stats *tus = t->proxy_user_stats;196        struct proxy_int_stats *is = t->proxy_int_stats;197        WSTAT_L(t);198        for (int i = 0; i < CMD_FINAL; i++) {199            istats.counters[i] += is->counters[i];200        }201        istats.vm_gc_runs += is->vm_gc_runs;202        istats.vm_memory_kb += is->vm_memory_kb;203        if (tus && tus->num_stats >= stats_num) {204            for (int i = 0; i < stats_num; i++) {205                counters[i] += tus->counters[i];206            }207        }208        WSTAT_UL(t);209        buffer_memory_used += t->proxy_buffer_memory_used;210    }211212    // return all of the user generated stats213    if (ctx->user_stats_namebuf) {214        char vbuf[INCR_MAX_STORAGE_LEN];215        char *e = NULL; // ptr into vbuf216        const char *pfx = "user_";217        const size_t pfxlen = strlen(pfx);218        for (int x = 0; x < stats_num; x++) {219            if (us[x].cname) {220                char *name = ctx->user_stats_namebuf + us[x].cname;221                size_t nlen = strlen(name);222                if (nlen > STAT_KEY_LEN-6) {223                    // impossible, but for paranoia.224                    nlen = STAT_KEY_LEN-6;225                }226                // avoiding an snprintf call for some performance ("user_%s")227                memcpy(key_str, pfx, pfxlen);228                memcpy(key_str+pfxlen, name, nlen);229                key_str[pfxlen+nlen] = '\0';230231                // APPEND_STAT() calls another snprintf, which calls our232                // add_stats argument. Lets skip yet another snprintf with233                // some unrolling.234                e = itoa_u64(counters[x], vbuf);235                *(e+1) = '\0';236                add_stats(key_str, pfxlen+nlen, vbuf, e-vbuf, c);237            }238        }239    }240241    STAT_UL(ctx);242243    if (buffer_memory_limit == UINT64_MAX) {244        buffer_memory_limit = 0;245    } else {246        buffer_memory_limit *= settings.num_threads;247    }248    if (req_limit == INT64_MAX) {249        req_limit = 0;250    } else {251        req_limit *= settings.num_threads;252    }253254    // return proxy counters255    APPEND_STAT("active_req_limit", "%lld", (long long int)req_limit);256    APPEND_STAT("buffer_memory_limit", "%llu", (unsigned long long)buffer_memory_limit);257    APPEND_STAT("buffer_memory_used", "%llu", (unsigned long long)buffer_memory_used);258    APPEND_STAT("vm_gc_runs", "%llu", (unsigned long long)istats.vm_gc_runs);259    APPEND_STAT("vm_memory_kb", "%llu", (unsigned long long)istats.vm_memory_kb);260    APPEND_STAT("cmd_mg", "%llu", (unsigned long long)istats.counters[CMD_MG]);261    APPEND_STAT("cmd_ms", "%llu", (unsigned long long)istats.counters[CMD_MS]);262    APPEND_STAT("cmd_md", "%llu", (unsigned long long)istats.counters[CMD_MD]);263    APPEND_STAT("cmd_mn", "%llu", (unsigned long long)istats.counters[CMD_MN]);264    APPEND_STAT("cmd_ma", "%llu", (unsigned long long)istats.counters[CMD_MA]);265    APPEND_STAT("cmd_me", "%llu", (unsigned long long)istats.counters[CMD_ME]);266    APPEND_STAT("cmd_get", "%llu", (unsigned long long)istats.counters[CMD_GET]);267    APPEND_STAT("cmd_gat", "%llu", (unsigned long long)istats.counters[CMD_GAT]);268    APPEND_STAT("cmd_set", "%llu", (unsigned long long)istats.counters[CMD_SET]);269    APPEND_STAT("cmd_add", "%llu", (unsigned long long)istats.counters[CMD_ADD]);270    APPEND_STAT("cmd_cas", "%llu", (unsigned long long)istats.counters[CMD_CAS]);271    APPEND_STAT("cmd_gets", "%llu", (unsigned long long)istats.counters[CMD_GETS]);272    APPEND_STAT("cmd_gats", "%llu", (unsigned long long)istats.counters[CMD_GATS]);273    APPEND_STAT("cmd_incr", "%llu", (unsigned long long)istats.counters[CMD_INCR]);274    APPEND_STAT("cmd_decr", "%llu", (unsigned long long)istats.counters[CMD_DECR]);275    APPEND_STAT("cmd_touch", "%llu", (unsigned long long)istats.counters[CMD_TOUCH]);276    APPEND_STAT("cmd_append", "%llu", (unsigned long long)istats.counters[CMD_APPEND]);277    APPEND_STAT("cmd_prepend", "%llu", (unsigned long long)istats.counters[CMD_PREPEND]);278    APPEND_STAT("cmd_delete", "%llu", (unsigned long long)istats.counters[CMD_DELETE]);279    APPEND_STAT("cmd_replace", "%llu", (unsigned long long)istats.counters[CMD_REPLACE]);280}281282void process_proxy_funcstats(void *arg, ADD_STAT add_stats, void *c) {283    char key_str[STAT_KEY_LEN];284    if (!arg) {285        return;286    }287    proxy_ctx_t *ctx = arg;288    lua_State *L = ctx->proxy_sharedvm;289    pthread_mutex_lock(&ctx->sharedvm_lock);290291    // iterate all of the named function slots292    lua_pushnil(L);293    while (lua_next(L, SHAREDVM_FGEN_IDX) != 0) {294        int n = lua_tointeger(L, -1);295        lua_pop(L, 1); // drop the value, leave the key.296        if (n != 0) {297            // reuse the key. make a copy since rawget will pop it.298            lua_pushvalue(L, -1);299            lua_rawget(L, SHAREDVM_FGENSLOT_IDX);300            int slots = lua_tointeger(L, -1);301            lua_pop(L, 1); // drop the slot count.302303            // now grab the name key.304            const char *name = lua_tostring(L, -1);305            snprintf(key_str, STAT_KEY_LEN-1, "funcs_%s", name);306            APPEND_STAT(key_str, "%d", n);307            snprintf(key_str, STAT_KEY_LEN-1, "slots_%s", name);308            APPEND_STAT(key_str, "%d", slots);309        } else {310            // TODO: It is safe to delete keys here. Slightly complex so low311            // priority.312        }313    }314315    pthread_mutex_unlock(&ctx->sharedvm_lock);316}317318void process_proxy_bestats(void *arg, ADD_STAT add_stats, void *c) {319    char key_str[STAT_KEY_LEN];320    if (!arg) {321        return;322    }323    proxy_ctx_t *ctx = arg;324    lua_State *L = ctx->proxy_sharedvm;325    pthread_mutex_lock(&ctx->sharedvm_lock);326327    // iterate all of the listed backends328    lua_pushnil(L);329    while (lua_next(L, SHAREDVM_BACKEND_IDX) != 0) {330        int n = lua_tointeger(L, -1);331        lua_pop(L, 1); // drop the value, leave the key.332        if (n != 0) {333            // now grab the name key.334            const char *name = lua_tostring(L, -1);335            snprintf(key_str, STAT_KEY_LEN-1, "bad_%s", name);336            APPEND_STAT(key_str, "%d", n);337        } else {338            // delete keys of backends that are no longer bad or no longer339            // exist to keep the table small.340            const char *name = lua_tostring(L, -1);341            lua_pushnil(L);342            lua_setfield(L, SHAREDVM_BACKEND_IDX, name);343        }344    }345346    pthread_mutex_unlock(&ctx->sharedvm_lock);347}348349// start the centralized lua state and config thread.350void *proxy_init(bool use_uring, bool proxy_memprofile) {351    proxy_ctx_t *ctx = calloc(1, sizeof(proxy_ctx_t));352    ctx->use_uring = use_uring;353    ctx->memprofile = proxy_memprofile;354355    pthread_mutex_init(&ctx->config_lock, NULL);356    pthread_cond_init(&ctx->config_cond, NULL);357    pthread_mutex_init(&ctx->worker_lock, NULL);358    pthread_cond_init(&ctx->worker_cond, NULL);359    pthread_mutex_init(&ctx->manager_lock, NULL);360    pthread_cond_init(&ctx->manager_cond, NULL);361    pthread_mutex_init(&ctx->stats_lock, NULL);362363    ctx->active_req_limit = INT64_MAX;364    ctx->buffer_memory_limit = UINT64_MAX;365366    // FIXME (v2): default defines.367    ctx->tunables.tcp_keepalive = false;368    ctx->tunables.backend_failure_limit = 3;369    ctx->tunables.connect.tv_sec = 5;370    ctx->tunables.retry.tv_sec = 3;371    ctx->tunables.read.tv_sec = 3;372    ctx->tunables.flap_backoff_ramp = 1.5;373    ctx->tunables.flap_backoff_max = 3600;374    ctx->tunables.gc_ratio = PROXY_GC_DEFAULT_RATIO;375    ctx->tunables.backend_depth_limit = 0;376    ctx->tunables.max_ustats = MAX_USTATS_DEFAULT;377    ctx->tunables.use_iothread = false;378    ctx->tunables.use_tls = false;379380    STAILQ_INIT(&ctx->manager_head);381    lua_State *L = NULL;382    if (ctx->memprofile) {383        struct mcp_memprofile *prof = calloc(1, sizeof(struct mcp_memprofile));384        prof->id = ctx->memprofile_thread_counter++;385        L = lua_newstate(mcp_profile_alloc, prof);386    } else {387        L = luaL_newstate();388    }389    ctx->proxy_state = L;390    luaL_openlibs(L);391    // NOTE: might need to differentiate the libs yes?392    proxy_register_libs(ctx, NULL, L);393    // Create the cron table.394    lua_newtable(L);395    ctx->cron_ref = luaL_ref(L, LUA_REGISTRYINDEX);396    ctx->cron_next = INT_MAX;397398    // set up the shared state VM. Used by short-lock events (counters/state)399    // for global visibility.400    pthread_mutex_init(&ctx->sharedvm_lock, NULL);401    ctx->proxy_sharedvm = luaL_newstate();402    luaL_openlibs(ctx->proxy_sharedvm);403    // we keep info tables in the top level stack so we don't have to404    // constantly fetch them from registry.405    lua_newtable(ctx->proxy_sharedvm); // fgen count406    lua_newtable(ctx->proxy_sharedvm); // fgen slot count407    lua_newtable(ctx->proxy_sharedvm); // backend down status408409    // Create/start the IO thread, which we need before servers410    // start getting created.411    proxy_event_thread_t *t = calloc(1, sizeof(proxy_event_thread_t));412    ctx->proxy_io_thread = t;413    proxy_init_event_thread(t, ctx, NULL);414415    pthread_create(&t->thread_id, NULL, proxy_event_thread, t);416    thread_setname(t->thread_id, "mc-prx-io");417418    _start_proxy_config_threads(ctx);419    return ctx;420}421422// Initialize the VM for an individual worker thread.423void proxy_thread_init(void *ctx, LIBEVENT_THREAD *thr) {424    assert(ctx != NULL);425    assert(thr != NULL);426427    // Create the hook table.428    thr->proxy_hooks = calloc(CMD_SIZE, sizeof(struct proxy_hook));429    if (thr->proxy_hooks == NULL) {430        fprintf(stderr, "Failed to allocate proxy hooks\n");431        exit(EXIT_FAILURE);432    }433    thr->proxy_int_stats = calloc(1, sizeof(struct proxy_int_stats));434    if (thr->proxy_int_stats == NULL) {435        fprintf(stderr, "Failed to allocate proxy thread stats\n");436        exit(EXIT_FAILURE);437    }438    thr->proxy_ctx = ctx;439440    // Initialize the lua state.441    proxy_ctx_t *pctx = ctx;442    lua_State *L = NULL;443    if (pctx->memprofile) {444        struct mcp_memprofile *prof = calloc(1, sizeof(struct mcp_memprofile));445        prof->id = pctx->memprofile_thread_counter++;446        L = lua_newstate(mcp_profile_alloc, prof);447    } else {448        L = luaL_newstate();449    }450451    // With smaller requests the default incremental collector appears to452    // never complete. With this simple tuning (def-1, def, def) it seems453    // fine.454    // We can't use GCGEN until we manage pools with reference counting, as455    // they may never hit GC and thus never release their connection456    // resources.457    lua_gc(L, LUA_GCINC, 199, 100, 12);458    lua_gc(L, LUA_GCSTOP); // handle GC on our own schedule.459    thr->L = L;460    luaL_openlibs(L);461    proxy_register_libs(ctx, thr, L);462    // TODO: srand on time? do we need to bother?463    for (int x = 0; x < 3; x++) {464        thr->proxy_rng[x] = rand();465    }466467    // init our internal GC checker.468    thr->proxy_vm_last_kb = lua_gc(L, LUA_GCCOUNT);469    assert(thr->proxy_vm_last_kb != 0);470    thr->proxy_gc_timer = evtimer_new(thr->base, proxy_gc_timerpoke, thr);471    // kick off the timer loop.472    proxy_gc_timerpoke(0, 0, thr);473474    // Create a proxy event thread structure to piggyback on the worker.475    proxy_event_thread_t *t = calloc(1, sizeof(proxy_event_thread_t));476    thr->proxy_event_thread = t;477    proxy_init_event_thread(t, ctx, thr->base);478}479480void proxy_submit_cb(io_queue_t *q) {481    proxy_event_thread_t *e = ((proxy_ctx_t *)q->ctx)->proxy_io_thread;482    iop_head_t head;483    be_head_t w_head; // worker local stack.484    STAILQ_INIT(&head);485    STAILQ_INIT(&w_head);486487    while (!STAILQ_EMPTY(&q->stack)) {488        mcp_backend_t *be;489        io_pending_proxy_t *p = (io_pending_proxy_t *)STAILQ_FIRST(&q->stack);490        STAILQ_REMOVE_HEAD(&q->stack, iop_next);491        P_DEBUG("%s: queueing req for backend: %p\n", __func__, (void *)p);492493        if (p->background) {494            P_DEBUG("%s: fast-returning background object: %p\n", __func__, (void *)p);495            assert(p->backend == NULL);496            // must not resume requests inline here but they can be scheduled497            // to run drive_machine() later.498            conn_io_queue_return((io_pending_t *)p);499            continue;500        }501        be = p->backend;502503        if (be->use_io_thread) {504            STAILQ_INSERT_TAIL(&head, (io_pending_t *)p, iop_next);505        } else {506            // emulate some of handler_dequeue()507            STAILQ_INSERT_TAIL(&be->iop_head, (io_pending_t *)p, iop_next);508            assert(be->depth > -1);509            be->depth++;510            if (!be->stacked) {511                be->stacked = true;512                STAILQ_INSERT_TAIL(&w_head, be, be_next);513            }514        }515    }516517    // q->stack must now be empty, so we can submit new IO's while handling518    // the existing ones.519520    if (!STAILQ_EMPTY(&head)) {521        bool do_notify = false;522        P_DEBUG("%s: submitting queue to IO thread\n", __func__);523        // Transfer request stack to event thread.524        pthread_mutex_lock(&e->mutex);525        if (STAILQ_EMPTY(&e->iop_head_in)) {526            do_notify = true;527        }528        STAILQ_CONCAT(&e->iop_head_in, &head);529        // No point in holding the lock since we're not doing a cond signal.530        pthread_mutex_unlock(&e->mutex);531532        if (do_notify) {533        // Signal to check queue.534#ifdef USE_EVENTFD535        uint64_t u = 1;536        // TODO (v2): check result? is it ever possible to get a short write/failure537        // for an eventfd?538        if (write(e->event_fd, &u, sizeof(uint64_t)) != sizeof(uint64_t)) {539            assert(1 == 0);540        }541#else542        if (write(e->notify_send_fd, "w", 1) <= 0) {543            assert(1 == 0);544        }545#endif546        }547    }548549    if (!STAILQ_EMPTY(&w_head)) {550        P_DEBUG("%s: running inline worker queue\n", __func__);551        // emulating proxy_event_handler552        proxy_run_backend_queue(&w_head);553    }554    return;555}556557// This function handles return processing for the "old style" API:558// currently just `mcp.internal()`559void proxy_return_rctx_cb(io_pending_t *pending) {560    io_pending_proxy_t *p = (io_pending_proxy_t *)pending;561    if (p->client_resp && p->client_resp->blen) {562        // FIXME: workaround for buffer memory being external to objects.563        // can't run 0 since that means something special (run the GC)564        unsigned int kb = p->client_resp->blen / 1000;565        p->thread->proxy_vm_extra_kb += kb > 0 ? kb : 1;566    }567568    mcp_rcontext_t *rctx = p->rctx;569    lua_rotate(rctx->Lc, 1, 1);570    lua_settop(rctx->Lc, 1);571    // hold the resp for a minute.572    mc_resp *resp = rctx->resp;573574    proxy_run_rcontext(rctx);575576    if (p->io_sub_type != IO_PENDING_TYPE_EXTSTORE) {577        // if we're doing an extstore subrequest, the iop needs to live until578        // resp's ->finish_cb is called.579        resp->io_pending = NULL;580        do_cache_free(p->thread->io_cache, p);581    }582}583584// This is called if resp_finish is called while an iop exists on the585// resp.586// so we need to release our iop and rctx.587// - This can't happen unless we're doing extstore fetches.588// - the request context is freed before connection processing resumes.589void proxy_finalize_rctx_cb(io_pending_t *pending) {590    io_pending_proxy_t *p = (io_pending_proxy_t *)pending;591    // TODO: need to remove from stack if subtype is p->active592593    if (p->io_sub_type == IO_PENDING_TYPE_EXTSTORE) {594        assert(p->active == false);595        if (p->hdr_it) {596            // TODO: lock once, worst case this hashes/locks twice.597            if (p->miss) {598                item_unlink(p->hdr_it);599            }600            item_remove(p->hdr_it);601        }602    }603}604605int try_read_command_proxy(conn *c) {606    char *el, *cont;607608    if (c->rbytes == 0)609        return 0;610611    el = memchr(c->rcurr, '\n', c->rbytes);612    if (!el) {613        if (c->rbytes > 1024) {614            /*615             * We didn't have a '\n' in the first k. This _has_ to be a616             * large multiget, if not we should just nuke the connection.617             */618            char *ptr = c->rcurr;619            char *end = c->rcurr + c->rbytes-6;620            while (*ptr == ' ' && ptr != end) { /* ignore leading whitespaces */621                ++ptr;622            }623624            if (ptr - c->rcurr > 100 ||625                (strncmp(ptr, "get ", 4) && strncmp(ptr, "gets ", 5))) {626627                conn_set_state(c, conn_closing);628                return 1;629            }630631            // ASCII multigets are unbound, so our fixed size rbuf may not632            // work for this particular workload... For backcompat we'll use a633            // malloc/realloc/free routine just for this.634            if (!c->rbuf_malloced) {635                if (!rbuf_switch_to_malloc(c)) {636                    conn_set_state(c, conn_closing);637                    return 1;638                }639            }640        }641642        return 0;643    }644    cont = el + 1;645646    assert(cont <= (c->rcurr + c->rbytes));647648    c->last_cmd_time = current_time;649    proxy_process_command(c, c->rcurr, cont - c->rcurr, PROCESS_NORMAL);650651    c->rbytes -= (cont - c->rcurr);652    c->rcurr = cont;653654    assert(c->rcurr <= (c->rbuf + c->rsize));655656    return 1;657658}659660// Called when a connection is closed while in nread state reading a set661// Must only be called with an active coroutine.662void proxy_cleanup_conn(conn *c) {663    assert(c->proxy_rctx);664    mcp_rcontext_t *rctx = c->proxy_rctx;665    assert(rctx->pending_reqs == 1);666    rctx->pending_reqs = 0;667668    mcp_funcgen_return_rctx(rctx);669    c->proxy_rctx = NULL;670}671672// we buffered a SET of some kind.673void complete_nread_proxy(conn *c) {674    assert(c != NULL);675676    LIBEVENT_THREAD *thr = c->thread;677    lua_State *L = thr->L;678679    if (c->proxy_rctx == NULL) {680        complete_nread_ascii(c);681        return;682    }683684    conn_set_state(c, conn_new_cmd);685686    assert(c->proxy_rctx);687    mcp_rcontext_t *rctx = c->proxy_rctx;688    c->proxy_rctx = NULL;689    mcp_request_t *rq = rctx->request;690691    if (strncmp((char *)c->item + rq->pr.vlen - 2, "\r\n", 2) != 0) {692        lua_settop(L, 0); // clear anything remaining on the main thread.693        // FIXME (v2): need to set noreply false if mset_res, but that's kind694        // of a weird hack to begin with. Evaluate how to best do that here.695        out_string(c, "CLIENT_ERROR bad data chunk");696        rctx->pending_reqs--;697        mcp_funcgen_return_rctx(rctx);698        return;699    }700701    // We move ownership of the c->item buffer from the connection to the702    // request object here. Else we can double free if the conn closes while703    // inside nread.704    rq->pr.vbuf = c->item;705    c->item = NULL;706    c->item_malloced = false;707    thr->proxy_buffer_memory_used += rq->pr.vlen;708709    conn_resp_suspend(rctx->c, rctx->resp);710    proxy_run_rcontext(rctx);711712    lua_settop(L, 0); // clear anything remaining on the main thread.713714    return;715}716717// Simple error wrapper for common failures.718// lua_error() is a jump so this function never returns719// for clarity add a 'return' after calls to this.720void proxy_lua_error(lua_State *L, const char *s) {721    lua_pushstring(L, s);722    lua_error(L);723}724725// Need a custom function so we can prefix lua strings easily.726void proxy_out_errstring(mc_resp *resp, char *type, const char *str) {727    size_t len;728    size_t prefix_len = strlen(type);729730    assert(resp != NULL);731732    resp_reset(resp);733    // avoid noreply since we're throwing important errors.734735    // Fill response object with static string.736    len = strlen(str);737    if ((len + prefix_len + 2) > WRITE_BUFFER_SIZE) {738        /* ought to be always enough. just fail for simplicity */739        str = "SERVER_ERROR output line too long";740        len = strlen(str);741    }742743    char *w = resp->wbuf;744    memcpy(w, type, prefix_len);745    w += prefix_len;746747    memcpy(w, str, len);748    w += len;749750    memcpy(w, "\r\n", 2);751    resp_add_iov(resp, resp->wbuf, len + prefix_len + 2);752    return;753}754755// NOTE: See notes in mcp_queue_io; the secondary problem with setting the756// noreply mode from the response object is that the proxy can return strings757// manually, so we have no way to obey what the original request wanted in758// that case.759static void _set_noreply_mode(mc_resp *resp, mcp_request_t *rq, mcp_resp_t *r) {760    if (rq->pr.cmd_type == CMD_TYPE_META761        && mcmc_token_has_flag_bit(&rq->pr.tok, (uint64_t)1<<48) == MCMC_OK) {762        if (r->resp.code == MCMC_CODE_END) {763            resp->skip = true;764        } else if (r->cmd != CMD_MG && r->resp.code == MCMC_CODE_OK) {765            // FIXME (v2): mcmc's parser needs to help us out a bit more766            // by maybe flagging a response as quietable.767            // This is a broken case in the protocol though; quiet mode768            // ignores HD for mutations but not get.769            resp->skip = true;770        }771    } else if (rq->pr.noreply && r->status == MCMC_OK) {772        // ascii noreply only threw egregious errors to client773        resp->skip = true;774    }775}776777static void _proxy_run_rcontext_queues(mcp_rcontext_t *rctx) {778    for (int x = 0; x < rctx->fgen->max_queues; x++) {779        mcp_run_rcontext_handle(rctx, x);780    }781}782783static void _proxy_run_tresp_to_resp(mc_resp *tresp, mc_resp *resp) {784    // The internal cache handler has created a resp we want to swap in785    // here. It would be fastest to swap *resp's position in the786    // link but if the set is deep this would instead be slow, so787    // we copy over details from this temporary resp instead.788789    // So far all we fill is the wbuf and some iov's? so just copy790    // that + the UDP info?791    memcpy(resp->wbuf, tresp->wbuf, tresp->iov[0].iov_len);792    resp->tosend = 0;793    for (int x = 0; x < tresp->iovcnt; x++) {794        resp->iov[x] = tresp->iov[x];795        resp->tosend += tresp->iov[x].iov_len;796    }797    // resp->iov[x].iov_base needs to be updated if it's798    // pointing within its wbuf.799    // FIXME: This is too fragile. we need to be able to800    // inherit details and swap resp objects around.801    if (tresp->iov[0].iov_base == tresp->wbuf) {802        resp->iov[0].iov_base = resp->wbuf;803    }804    resp->iovcnt = tresp->iovcnt;805    resp->chunked_total = tresp->chunked_total;806    resp->chunked_data_iov = tresp->chunked_data_iov;807    // copy UDP headers...808    resp->request_id = tresp->request_id;809    resp->udp_sequence = tresp->udp_sequence;810    resp->udp_total = tresp->udp_total;811    resp->request_addr = tresp->request_addr;812    resp->request_addr_size = tresp->request_addr_size;813    resp->item = tresp->item; // will be populated if not extstore fetch814    tresp->item = NULL; // move ownership of the item to resp from tresp815    resp->skip = tresp->skip;816}817818int proxy_run_rcontext(mcp_rcontext_t *rctx) {819    int nresults = 0;820    lua_State *Lc = rctx->Lc;821    assert(rctx->lua_narg != 0);822    int cores = lua_resume(Lc, NULL, rctx->lua_narg, &nresults);823    rctx->lua_narg = 1; // reset to default since not-default is uncommon.824    size_t rlen = 0;825    mc_resp *resp = rctx->resp;826827    if (cores == LUA_OK) {828        // don't touch the result object if we were a sub-context.829        if (!rctx->parent) {830            int type = lua_type(Lc, 1);831            mcp_resp_t *r = NULL;832            P_DEBUG("%s: coroutine completed. return type: %d\n", __func__, type);833            if (type == LUA_TUSERDATA && (r = luaL_testudata(Lc, 1, "mcp.response")) != NULL) {834                _set_noreply_mode(resp, rctx->request, r);835                if (r->status != MCMC_OK && r->resp.type != MCMC_RESP_ERRMSG) {836                    proxy_out_errstring(resp, PROXY_SERVER_ERROR, "backend failure");837                } else if (r->cresp) {838                    mc_resp *tresp = r->cresp;839840                    _proxy_run_tresp_to_resp(tresp, resp);841                    // hand off ownership of the result buffer if we were an842                    // extstore fetch.843                    if (!resp->item) {844                        resp->write_and_free = r->buf;845                        r->buf = NULL;846                    }847                    // we let the mcp_resp gc handler free up tresp and any848                    // associated io_pending's of its own later.849                } else if (r->buf) {850                    // response set from C.851                    resp->write_and_free = r->buf;852                    resp_add_iov(resp, r->buf, r->blen);853                    // stash the length to later remove from memory tracking854                    resp->wbytes = r->blen;855                    resp->proxy_res = true;856                    r->buf = NULL;857                } else {858                    // Empty response: used for ascii multiget emulation.859                }860861            } else if (type == LUA_TSTRING) {862                // response is a raw string from lua.863                const char *s = lua_tolstring(Lc, 1, &rlen);864                size_t l = rlen > WRITE_BUFFER_SIZE ? WRITE_BUFFER_SIZE : rlen;865                memcpy(resp->wbuf, s, l);866                resp_add_iov(resp, resp->wbuf, l);867                lua_pop(Lc, 1);868            } else {869                proxy_out_errstring(resp, PROXY_SERVER_ERROR, "bad response");870            }871872            if (rctx->ascii_multiget && !resp->skip) {873                struct iovec *iov = &resp->iov[resp->iovcnt-1];874                // look for END at end of final iov and blank it.875                // Bad hack, but centralized here now.876                if (iov->iov_len >= ENDLEN &&877                        memcmp(((char *)iov->iov_base)+(iov->iov_len-ENDLEN), ENDSTR, ENDLEN) == 0) {878                    iov->iov_len -= ENDLEN;879                    resp->tosend -= ENDLEN;880                    if (resp->tosend == 0) {881                        // some platforms don't like 0 byte iov's.882                        // may be misremembering, double check? mac os?883                        resp->skip = true;884                    }885                }886            }887            conn_resp_unsuspend(rctx->c, resp);888            rctx->c = NULL; // *conn cannot be used past this point!889            rctx->pending_reqs--;890            mcp_funcgen_return_rctx(rctx);891        } else {892            rctx->pending_reqs--;893        }894    } else if (cores == LUA_YIELD) {895        int yield_type = lua_tointeger(Lc, -1);896        P_DEBUG("%s: coroutine yielded. return type: %d\n", __func__, yield_type);897        assert(yield_type != 0);898        lua_pop(Lc, 1);899900        int res = 0;901        switch (yield_type) {902            case MCP_YIELD_INTERNAL:903                // stack should be: rq, res904                if (rctx->parent) {905                    LOGGER_LOG(NULL, LOG_PROXYEVENTS, LOGGER_PROXY_ERROR, NULL, "cannot run mcp.internal from a sub request");906                    rctx->pending_reqs--;907                    return LUA_ERRRUN;908                } else {909                    res = mcplib_internal_run(rctx);910                    if (res == 0) {911                        // stack should still be: rq, res912                        // TODO: turn this function into a for loop that re-runs on913                        // certain status codes, to avoid recursive depth here.914                        // or maybe... a goto? :P915                        proxy_run_rcontext(rctx);916                    } else if (res > 0) {917                        // internal run queued for extstore.918                    } else {919                        assert(res < 0);920                        proxy_out_errstring(resp, PROXY_SERVER_ERROR, "bad request");921                    }922                }923                break;924            case MCP_YIELD_WAITCOND:925            case MCP_YIELD_WAITHANDLE:926                // Even if we're in WAITHANDLE, we want to dispatch any queued927                // requests, so we still need to iterate the full set of qslots.928                _proxy_run_rcontext_queues(rctx);929                break;930            case MCP_YIELD_SLEEP:931                // Pause coroutine and do nothing. Alarm will resume.932                break;933            default:934                abort();935        }936937    } else {938        // Log the error where it happens, then the parent will handle a939        // result object normally.940        P_DEBUG("%s: Failed to run coroutine: %s\n", __func__, lua_tostring(Lc, -1));941        LOGGER_LOG(NULL, LOG_PROXYEVENTS, LOGGER_PROXY_ERROR, NULL, lua_tostring(Lc, -1));942        if (settings.verbose) {943            luaL_traceback(Lc, Lc, lua_tostring(Lc, -1), 1);944            fprintf(stderr, "proxy_lua_error: %s\n", lua_tostring(Lc, -1));945        }946        if (!rctx->parent) {947            proxy_out_errstring(resp, PROXY_SERVER_ERROR, "lua failure");948            conn_resp_unsuspend(rctx->c, resp);949            rctx->c = NULL; // *conn cannot be used past this point!950            rctx->pending_reqs--;951            mcp_funcgen_return_rctx(rctx);952        } else {953            rctx->pending_reqs--;954        }955    }956957    return cores;958}959960// basically any data before the first key.961// max is like 15ish plus spaces. we can be more strict about how many spaces962// to expect because any client spamming space is being deliberately stupid963// anyway.964#define MAX_CMD_PREFIX 20965966static void proxy_process_command(conn *c, char *command, size_t cmdlen, bool multiget) {967    assert(c != NULL);968    LIBEVENT_THREAD *thr = c->thread;969    struct proxy_hook *hooks = thr->proxy_hooks;970    lua_State *L = thr->L;971    proxy_ctx_t *ctx = thr->proxy_ctx;972    mcp_parser_t pr = {0};973974    // Avoid doing resp_start() here, instead do it a bit later or as-needed.975    // This allows us to hop over to the internal text protocol parser, which976    // also calls resp_start().977    // Tighter integration later should obviate the need for this, it is not a978    // permanent solution.979    int ret = process_request(&pr, command, cmdlen);980    if (ret != 0) {981        WSTAT_INCR(c->thread, proxy_conn_errors, 1);982        if (!resp_start(c)) {983            conn_set_state(c, conn_closing);984            return;985        }986        proxy_out_errstring(c->resp, PROXY_CLIENT_ERROR, "parsing request");987        if (ret == -2) {988            // Kill connection on more critical parse failure.989            conn_set_state(c, conn_closing);990        }991        return;992    }993994    struct proxy_hook *hook = &hooks[pr.command];995    struct proxy_hook_ref hook_ref = hook->ref;996    // if client came from a tagged listener, scan for a more specific hook.997    // TODO: (v2) avoiding a hash table lookup here, but maybe some other998    // datastructure would suffice. for 4-8 tags this is perfectly fast.999    if (c->tag && hook->tagged) {1000        struct proxy_hook_tagged *pht = hook->tagged;1001        while (pht->ref.lua_ref) {1002            if (c->tag == pht->tag) {1003                hook_ref = pht->ref;1004                break;1005            }1006            pht++;1007        }1008    }10091010    if (!hook_ref.lua_ref) {1011        // lets nread_proxy know we're in ascii mode.1012        c->proxy_rctx = NULL;1013        process_command_ascii(c, command, cmdlen);1014        return;1015    }10161017    // If ascii multiget, we turn this into a self-calling loop :(1018    // create new request with next key, call this func again, then advance1019    // original string.1020    // might be better to split this function; the below bits turn into a1021    // function call, then we don't re-process the above bits in the same way?1022    // The way this is detected/passed on is very fragile.1023    if (!multiget && pr.cmd_type == CMD_TYPE_GET && pr.tok.ntokens > pr.keytoken+1) {1024        uint32_t keyoff = pr.tok.tokens[pr.keytoken];1025        const char *curkey = pr.request + keyoff;1026        int klen = pr.klen;1027        int cmd_prefix_len = pr.tok.tokens[pr.keytoken];1028        size_t endlen = 0;10291030        // TODO: TEST THIS CASE1031        // check once if our prefix is too long.1032        if (cmd_prefix_len > MAX_CMD_PREFIX) {1033            if (!resp_start(c)) {1034                conn_set_state(c, conn_closing);1035                return;1036            }1037            proxy_out_errstring(c->resp, PROXY_CLIENT_ERROR, "malformed request");1038            return;1039        }10401041        assert(pr.request[pr.reqlen-1] == '\n');1042        if (pr.request[pr.reqlen-2] == '\r') {1043            endlen = pr.reqlen - 2;1044        } else {1045            endlen = pr.reqlen - 1;1046        }10471048        while (klen != 0) {1049            char temp[KEY_MAX_LENGTH + MAX_CMD_PREFIX + 30];1050            char *cur = temp;1051            // Core daemon can abort the entire command if one key is bad, but1052            // we cannot from the proxy. Instead we have to inject errors into1053            // the stream. This should, thankfully, be rare at least.1054            if (klen > KEY_MAX_LENGTH) {1055                if (!resp_start(c)) {1056                    conn_set_state(c, conn_closing);1057                    return;1058                }1059                proxy_out_errstring(c->resp, PROXY_CLIENT_ERROR, "key too long");1060            } else {1061                // copy original request up until the original key token.1062                memcpy(cur, pr.request, cmd_prefix_len);1063                cur += cmd_prefix_len;10641065                // now copy in our "current" key.1066                memcpy(cur, curkey, klen);1067                cur += klen;10681069                memcpy(cur, "\r\n", 2);1070                cur += 2;10711072                *cur = '\0';1073                P_DEBUG("%s: new multiget sub request: %s [%u/%u]\n", __func__, temp, keyoff, pr.klen);1074                proxy_process_command(c, temp, cur - temp, PROCESS_MULTIGET);1075            }10761077            // FIXME: test multigets with excess space at the end.1078            // Find the next key manually. This keeps the special cased code1079            // here instead of adding extra junk to the parser structure.1080            curkey += klen;1081            int remain = endlen - (curkey - pr.request);1082            while (remain) {1083                if (*curkey == ' ') {1084                    remain--;1085                    curkey++;1086                } else {1087                    break;1088                }1089            }10901091            if (remain) {1092                const char *s = memchr(curkey, ' ', remain);1093                if (s != NULL) {1094                    klen = s - curkey;1095                } else {1096                    klen = remain;1097                }1098            } else {1099                // no more keys1100                break;1101            }1102        }11031104        if (!resp_start(c)) {1105            conn_set_state(c, conn_closing);1106            return;1107        }11081109        // The above recursions should have created c->resp's in dispatch1110        // order.1111        // So now we add another one at the end to create the capping END1112        // string.1113        memcpy(c->resp->wbuf, ENDSTR, ENDLEN);1114        resp_add_iov(c->resp, c->resp->wbuf, ENDLEN);11151116        return;1117    }11181119    // We test the command length all the way down here because multigets can1120    // be very long, and they're chopped up by now.1121    if (cmdlen >= MCP_REQUEST_MAXLEN) {1122        WSTAT_INCR(c->thread, proxy_conn_errors, 1);1123        if (!resp_start(c)) {1124            conn_set_state(c, conn_closing);1125            return;1126        }1127        proxy_out_errstring(c->resp, PROXY_CLIENT_ERROR, "request too long");1128        conn_set_state(c, conn_closing);1129        return;1130    }11311132    if (!resp_start(c)) {1133        conn_set_state(c, conn_closing);1134        return;1135    }11361137    // Count requests handled by proxy vs local.1138    // Also batch the counts down this far so we can lock once for the active1139    // counter instead of twice.1140    struct proxy_int_stats *istats = c->thread->proxy_int_stats;1141    int64_t active_reqs = 0;1142    WSTAT_L(c->thread);1143    istats->counters[pr.command]++;1144    c->thread->stats.proxy_conn_requests++;1145    active_reqs = c->thread->stats.proxy_req_active;1146    WSTAT_UL(c->thread);11471148    if (active_reqs >= ctx->active_req_limit) {1149        proxy_out_errstring(c->resp, PROXY_SERVER_ERROR, "active request limit reached");1150        if (pr.vlen != 0) {1151            c->sbytes = pr.vlen;1152            conn_set_state(c, conn_swallow);1153        }1154        return;1155    }11561157    // hook is owned by a function generator.1158    mcp_rcontext_t *rctx = mcp_funcgen_start(L, hook_ref.ctx, &pr);1159    if (rctx == NULL) {1160        proxy_out_errstring(c->resp, PROXY_SERVER_ERROR, "lua start failure");1161        if (pr.vlen != 0) {1162            c->sbytes = pr.vlen;1163            conn_set_state(c, conn_swallow);1164        }1165        return;1166    }11671168    mcp_set_request(&pr, rctx->request, command, cmdlen);1169    rctx->ascii_multiget = multiget;1170    rctx->c = c;1171    rctx->conn_fd = c->sfd;1172    rctx->pending_reqs++; // seed counter with the "main" request1173    // remember the top level mc_resp, because further requests on the1174    // same connection will replace c->resp.1175    rctx->resp = c->resp;11761177    // for the very first call we need to place:1178    // - rctx->function_ref + rctx->request_ref1179    // I _think_ here is the right place to do that?1180    lua_rawgeti(rctx->Lc, LUA_REGISTRYINDEX, rctx->function_ref);1181    lua_rawgeti(rctx->Lc, LUA_REGISTRYINDEX, rctx->request_ref);11821183    if (pr.vlen != 0) {1184        c->item = NULL;1185        // Need to add the used memory later due to needing an extra callback1186        // handler on error during nread.1187        bool oom = proxy_bufmem_checkadd(c->thread, 0);11881189        // relying on temporary malloc's not having fragmentation1190        if (!oom) {1191            c->item = malloc(pr.vlen);1192        }1193        if (c->item == NULL) {1194            // return the RCTX1195            rctx->pending_reqs--;1196            mcp_funcgen_return_rctx(rctx);1197            // normal cleanup1198            lua_settop(L, 0);1199            proxy_out_errstring(c->resp, PROXY_SERVER_ERROR, "out of memory");1200            c->sbytes = pr.vlen;1201            conn_set_state(c, conn_swallow);1202            return;1203        }1204        c->item_malloced = true;1205        c->ritem = c->item;1206        c->rlbytes = pr.vlen;12071208        // remember the request context for later.1209        c->proxy_rctx = rctx;12101211        conn_set_state(c, conn_nread);1212        return;1213    }12141215    conn_resp_suspend(rctx->c, rctx->resp);1216    proxy_run_rcontext(rctx);12171218    lua_settop(L, 0); // clear any junk from the main thread.1219}12201221mcp_resp_t *mcp_prep_bare_resobj(lua_State *L, LIBEVENT_THREAD *t) {1222    mcp_resp_t *r = lua_newuserdatauv(L, sizeof(mcp_resp_t), 0);1223    // FIXME (v2): is this memset still necessary? I was using it for1224    // debugging.1225    memset(r, 0, sizeof(mcp_resp_t));1226    r->thread = t;1227    assert(r->thread != NULL);1228    gettimeofday(&r->start, NULL);12291230    luaL_getmetatable(L, "mcp.response");1231    lua_setmetatable(L, -2);12321233    return r;1234}12351236void mcp_set_resobj(mcp_resp_t *r, mcp_request_t *rq, mcp_backend_t *be, LIBEVENT_THREAD *t) {1237    memset(r, 0, sizeof(mcp_resp_t));1238    r->thread = t;1239    assert(r->thread != NULL);1240    gettimeofday(&r->start, NULL);1241    // Set noreply mode.1242    // TODO (v2): the response "inherits" the request's noreply mode, which isn't1243    // strictly correct; we should inherit based on the request that spawned1244    // the coroutine but the structure doesn't allow that yet.1245    // Should also be able to settle this exact mode from the parser so we1246    // don't have to re-branch here.1247    if (rq->pr.cmd_type == CMD_TYPE_META) {1248        if (mcmc_token_has_flag_bit(&rq->pr.tok, (uint64_t)1<<48) == MCMC_OK) {1249            // FIXME: should this use the new interface? re-evaluate?1250            for (int x = 2; x < rq->pr.tok.ntokens; x++) {1251                if (rq->request[rq->pr.tok.tokens[x]] == 'q') {1252                    rq->request[rq->pr.tok.tokens[x]] = ' ';1253                }1254            }1255        }1256    } else {1257        if (rq->pr.noreply) {1258            rq->request[rq->pr.reqlen - 3] = 'Y';1259        }1260    }12611262    r->cmd = rq->pr.command;1263    r->be = be;1264}12651266void mcp_resp_set_elapsed(mcp_resp_t *r) {1267    struct timeval end;1268    // stamp the elapsed time into the response object.1269    gettimeofday(&end, NULL);1270    r->elapsed = (end.tv_sec - r->start.tv_sec) * 1000000 +1271        (end.tv_usec - r->start.tv_usec);1272}12731274// Used for any cases where we're queueing requests to the IO subsystem.1275// NOTE: it's not currently possible to limit the memory used by the IO1276// object cache. So this check is redundant, and any callers may proceed1277// as though it is successful.1278io_pending_proxy_t *mcp_queue_rctx_io(mcp_rcontext_t *rctx, mcp_request_t *rq, mcp_backend_t *be, mcp_resp_t *r) {1279    conn *c = rctx->c;1280    io_queue_t *q = thread_io_queue_get(rctx->fgen->thread, IO_QUEUE_PROXY);1281    io_pending_proxy_t *p = do_cache_alloc(c->thread->io_cache);1282    if (p == NULL) {1283        WSTAT_INCR(c->thread, proxy_conn_oom, 1);1284        proxy_lua_error(rctx->Lc, "out of memory allocating from IO cache");1285        // NOTE: the error call above jumps to an error handler, so this does1286        // not actually return.1287        return NULL;1288    }12891290    // this is a re-cast structure, so assert that we never outsize it.1291    assert(sizeof(io_pending_t) >= sizeof(io_pending_proxy_t));1292    memset(p, 0, sizeof(io_pending_proxy_t));1293    // set up back references.1294    p->io_queue_type = IO_QUEUE_PROXY;1295    p->thread = c->thread;1296    p->c = c;1297    p->client_resp = r;1298    p->flushed = false;1299    p->return_cb = NULL;1300    p->finalize_cb = proxy_finalize_rctx_cb;13011302    // pass along the request context for resumption.1303    p->rctx = rctx;13041305    if (rq) {1306        // The direct backend object. Lc is holding the reference in the stack1307        p->backend = be;13081309        mcp_request_attach(rq, p);1310    }13111312    // link into the batch chain.1313    STAILQ_INSERT_TAIL(&q->stack, (io_pending_t *)p, iop_next);1314    P_DEBUG("%s: queued\n", __func__);13151316    return p;1317}13181319// DO NOT call this method frequently! globally locked!1320void mcp_sharedvm_delta(proxy_ctx_t *ctx, int tidx, const char *name, int delta) {1321    lua_State *L = ctx->proxy_sharedvm;1322    pthread_mutex_lock(&ctx->sharedvm_lock);13231324    if (lua_getfield(L, tidx, name) == LUA_TNIL) {1325        lua_pop(L, 1);1326        lua_pushinteger(L, delta);1327        lua_setfield(L, tidx, name);1328    } else {1329        lua_pushinteger(L, delta);1330        lua_arith(L, LUA_OPADD);1331        lua_setfield(L, tidx, name);1332    }13331334    pthread_mutex_unlock(&ctx->sharedvm_lock);1335}13361337void mcp_sharedvm_remove(proxy_ctx_t *ctx, int tidx, const char *name) {1338    lua_State *L = ctx->proxy_sharedvm;1339    pthread_mutex_lock(&ctx->sharedvm_lock);13401341    lua_pushnil(L);1342    lua_setfield(L, tidx, name);13431344    pthread_mutex_unlock(&ctx->sharedvm_lock);1345}13461347// Global object support code.1348// Global objects are created in the configuration VM, and referenced in1349// worker VMs via proxy objects that refer back to memory in the1350// configuration VM.1351// We manage reference counts: once all remote proxy objects are collected, we1352// signal the config thread to remove a final reference and collect garbage to1353// remove the global object.13541355static void mcp_gobj_enqueue(proxy_ctx_t *ctx, struct mcp_globalobj_s *g) {1356    pthread_mutex_lock(&ctx->manager_lock);1357    STAILQ_INSERT_TAIL(&ctx->manager_head, g, next);1358    pthread_cond_signal(&ctx->manager_cond);1359    pthread_mutex_unlock(&ctx->manager_lock);1360}13611362// References the object, initializing the self-reference if necessary.1363// Call from config thread, with global object on top of stack.1364void mcp_gobj_ref(lua_State *L, struct mcp_globalobj_s *g) {1365    pthread_mutex_lock(&g->lock);1366    if (g->self_ref == 0) {1367        // Initialization requires a small dance:1368        // - store a negative of our ref, increase refcount an extra time1369        // - then link and signal the manager thread as though we were GC'ing1370        // the object.1371        // - the manager thread will later acknowledge the initialization of1372        // this global object and negate the self_ref again1373        // - this prevents an unused proxy object from causing the global1374        // object to be reaped early while we are still copying it to worker1375        // threads, as the manager thread will block waiting for the config1376        // thread to finish its reload work.1377        g->self_ref = -luaL_ref(L, LUA_REGISTRYINDEX);1378        g->refcount++;1379        proxy_ctx_t *ctx = PROXY_GET_CTX(L);1380        mcp_gobj_enqueue(ctx, g);1381    } else {1382        lua_pop(L, 1); // drop the reference we didn't end up using.1383    }1384    g->refcount++;1385    pthread_mutex_unlock(&g->lock);1386}13871388void mcp_gobj_unref(proxy_ctx_t *ctx, struct mcp_globalobj_s *g) {1389    pthread_mutex_lock(&g->lock);1390    g->refcount--;1391    if (g->refcount == 0) {1392        mcp_gobj_enqueue(ctx, g);1393    }1394    pthread_mutex_unlock(&g->lock);1395}13961397void mcp_gobj_finalize(struct mcp_globalobj_s *g) {1398    pthread_mutex_destroy(&g->lock);1399}14001401static void *mcp_profile_alloc(void *ud, void *ptr, size_t osize,1402                                            size_t nsize) {1403    struct mcp_memprofile *prof = ud;1404    struct timespec now;1405    clock_gettime(CLOCK_MONOTONIC, &now);1406    enum mcp_memprofile_types t = mcp_memp_free;1407    if (ptr == NULL) {1408        switch (osize) {1409            case LUA_TSTRING:1410                t = mcp_memp_string;1411                //fprintf(stderr, "alloc string: %ld\n", nsize);1412                break;1413            case LUA_TTABLE:1414                t = mcp_memp_table;1415                //fprintf(stderr, "alloc table: %ld\n", nsize);1416                break;1417            case LUA_TFUNCTION:1418                t = mcp_memp_func;1419                //fprintf(stderr, "alloc func: %ld\n", nsize);1420                break;1421            case LUA_TUSERDATA:1422                t = mcp_memp_userdata;1423                //fprintf(stderr, "alloc userdata: %ld\n", nsize);1424                break;1425            case LUA_TTHREAD:1426                t = mcp_memp_thread;1427                //fprintf(stderr, "alloc thread: %ld\n", nsize);1428                break;1429            default:1430                t = mcp_memp_default;1431                //fprintf(stderr, "alloc osize: %ld nsize: %ld\n", osize, nsize);1432        }1433        prof->allocs[t]++;1434        prof->alloc_bytes[t] += nsize;1435    } else {1436        if (nsize != 0) {1437            prof->allocs[mcp_memp_realloc]++;1438            prof->alloc_bytes[mcp_memp_realloc] += nsize;1439        } else {1440            prof->allocs[mcp_memp_free]++;1441            prof->alloc_bytes[mcp_memp_free] += osize;1442        }1443        //fprintf(stderr, "realloc: osize: %ld nsize: %ld\n", osize, nsize);1444    }14451446    if (now.tv_sec != prof->last_status.tv_sec) {1447        prof->last_status.tv_sec = now.tv_sec;1448        fprintf(stderr, "MEMPROF[%d]:\tstring[%llu][%llu] table[%llu][%llu] func[%llu][%llu] udata[%llu][%llu] thr[%llu][%llu] def[%llu][%llu] realloc[%llu][%llu] free[%llu][%llu]\n",1449                prof->id,1450                (unsigned long long)prof->allocs[1],1451                (unsigned long long)prof->alloc_bytes[1],1452                (unsigned long long)prof->allocs[2],1453                (unsigned long long)prof->alloc_bytes[2],1454                (unsigned long long)prof->allocs[3],1455                (unsigned long long)prof->alloc_bytes[3],1456                (unsigned long long)prof->allocs[4],1457                (unsigned long long)prof->alloc_bytes[4],1458                (unsigned long long)prof->allocs[5],1459                (unsigned long long)prof->alloc_bytes[5],1460                (unsigned long long)prof->allocs[6],1461                (unsigned long long)prof->alloc_bytes[6],1462                (unsigned long long)prof->allocs[7],1463                (unsigned long long)prof->alloc_bytes[7],1464                (unsigned long long)prof->allocs[0],1465                (unsigned long long)prof->alloc_bytes[0]);1466        for (int x = 0; x < 8; x++) {1467            prof->allocs[x] = 0;1468            prof->alloc_bytes[x] = 0;1469        }1470    }14711472    if (nsize == 0) {1473        free(ptr);1474        return NULL;1475    } else {1476        return realloc(ptr, nsize);1477    }1478}14791480// Common lua debug command.1481__attribute__((unused)) void dump_stack(lua_State *L, const char *msg) {1482    int top = lua_gettop(L);1483    int i = 1;1484    fprintf(stderr, "--TOP OF STACK [%d] | %s\n", top, msg);1485    for (; i < top + 1; i++) {1486        int type = lua_type(L, i);1487        void *udata = NULL;1488        // lets find the metatable of this userdata to identify it.1489        if (lua_getmetatable(L, i) != 0) {1490            lua_pushstring(L, "__name");1491            if (lua_rawget(L, -2) != LUA_TNIL) {1492                if (type == LUA_TUSERDATA) {1493                    udata = lua_touserdata(L, i);1494                }1495                fprintf(stderr, "--|%d| [%s] (%s) [ptr: %p]\n", i, lua_typename(L, type), lua_tostring(L, -1), udata);1496                lua_pop(L, 2);1497                continue;1498            }1499            lua_pop(L, 2);1500        }1501        if (type == LUA_TSTRING) {1502            fprintf(stderr, "--|%d| [%s] | %s\n", i, lua_typename(L, type), lua_tostring(L, i));1503        } else {1504            if (type == LUA_TUSERDATA) {1505                udata = lua_touserdata(L, i);1506            }1507            fprintf(stderr, "--|%d| [%s] [ptr: %p]\n", i, lua_typename(L, type), udata);1508        }1509    }1510    fprintf(stderr, "-----------------\n");1511}15121513// Not very pretty, but helped.1514// Nice to haves:1515// - summarize counts for each metatable (easy enough to do from logging)1516// - use a less noisy stack dump instead of calling dump_stack()1517__attribute__((unused)) void dump_registry(lua_State *L, const char *msg) {1518    int ref_size = lua_rawlen(L, LUA_REGISTRYINDEX);1519    fprintf(stderr, "--LUA REGISTRY TABLE [%d] | %s\n", ref_size, msg);1520    // walk registry1521    int ridx = lua_absindex(L, LUA_REGISTRYINDEX);1522    int udata = 0;1523    int number = 0;1524    int string = 0;1525    int function = 0;1526    int table = 0;1527    lua_pushnil(L);1528    while (lua_next(L, ridx) != 0) {1529        dump_stack(L, "===registry entry===");1530        int type = lua_type(L, -1);1531        if (type == LUA_TUSERDATA) {1532            udata++;1533        } else if (type == LUA_TNUMBER) {1534            number++;1535        } else if (type == LUA_TSTRING) {1536            string++;1537        } else if (type == LUA_TFUNCTION) {1538            function++;1539        } else if (type == LUA_TTABLE) {1540            table++;1541        }1542        lua_pop(L, 1); // drop value1543    }1544    fprintf(stderr, "SUMMARY:\n\n");1545    fprintf(stderr, "### UDATA\t[%d]\n", udata);1546    fprintf(stderr, "### NUMBER\t[%d]\n", number);1547    fprintf(stderr, "### STRING\t[%d]\n", string);1548    fprintf(stderr, "### FUNCTION\t[%d]\n", function );1549    fprintf(stderr, "### TABLE\t[%d]\n", table);1550    fprintf(stderr, "-----------------\n");1551}15521553// Searches for a function generator with a specific name attached.1554// Adding breakpoints on the print lines lets you inspect the fgen and its1555// slots.1556__attribute__((unused)) void dump_funcgen(lua_State *L, const char *name, const char *msg) {1557    int ref_size = lua_rawlen(L, LUA_REGISTRYINDEX);1558    fprintf(stderr, "--LUA FUNCGEN FINDER [%d] | %s\n", ref_size, msg);1559    // walk registry1560    int ridx = lua_absindex(L, LUA_REGISTRYINDEX);1561    lua_pushnil(L);1562    while (lua_next(L, ridx) != 0) {1563        int type = lua_type(L, -1);1564        if (type == LUA_TUSERDATA) {1565            mcp_funcgen_t *f = luaL_testudata(L, -1, "mcp.funcgen");1566            if (f != NULL && strcmp(name, f->name) == 0) {1567                fprintf(stderr, "===found funcgen [%s] [%p]===\n", f->name, (void *)f);1568                lua_getiuservalue(L, -1, 1);1569                int tidx = lua_absindex(L, -1);1570                lua_pushnil(L);1571                while (lua_next(L, tidx) != 0) {1572                    mcp_rcontext_t *rctx = lua_touserdata(L, -1);1573                    if (rctx != NULL) {1574                        fprintf(stderr, "-- slot: [%p]\n", (void *)rctx);1575                    }1576                    lua_pop(L, 1); // drop value1577                }1578                lua_pop(L, 1); // drop slot table1579            }1580        }1581        lua_pop(L, 1); // drop value1582    }1583    fprintf(stderr, "-----------------\n");1584}15851586static void dump_pool_info(mcp_pool_t *p) {1587    fprintf(stderr, "--pool: [%s] size: [%d] be_total: [%d] rc: [%d] io: [%d]\n",1588            p->beprefix, p->pool_size, p->pool_be_total, p->g.refcount, p->use_iothread);15891590    for (int x = 0; x < p->pool_be_total; x++) {1591        mcp_backend_t *be = p->pool[x].be;1592        // Dumb: pool_be_total is wrong if pool is using iothread. Why?1593        if (be != NULL) {1594            fprintf(stderr, "  --be[%d] label: [%s] name: [%s] conns: [%d] depth: [%d]\n",1595                    x, be->label, be->name, be->conncount, be->depth);1596            for (int i = 0; i < be->conncount; i++) {1597                struct mcp_backendconn_s *bec = &be->be[i];1598                fprintf(stderr, "    --bec[%d] bad: [%d] failcnt: [%d] depth: [%d] pendread: [%d] state: [%d] can_write[%d] write_event[%d]\n",1599                        i, bec->bad, bec->failed_count, bec->depth, bec->pending_read, bec->state, bec->can_write, event_pending(&bec->timeout_event, EV_WRITE, NULL));1600            }1601        }1602    }1603    fprintf(stderr, "=======\n");1604}16051606// Dumps some info about pools.1607// If given the config thread, it should find the main pools1608// If given a worker thread, it will look for the pool proxy objects and find1609// the main pools that way.1610__attribute__((unused)) void dump_pools(lua_State *L, const char *msg) {1611    int ref_size = lua_rawlen(L, LUA_REGISTRYINDEX);1612    fprintf(stderr, "--LUA POOL DUMPER [%d] | %s\n", ref_size, msg);1613    // walk registry1614    int ridx = lua_absindex(L, LUA_REGISTRYINDEX);1615    lua_pushnil(L);1616    while (lua_next(L, ridx) != 0) {1617        int type = lua_type(L, -1);1618        if (type == LUA_TUSERDATA) {1619            mcp_pool_t *p = luaL_testudata(L, -1, "mcp.pool");1620            if (p != NULL) {1621                dump_pool_info(p);1622            } else {1623                mcp_pool_proxy_t *pp = luaL_testudata(L, -1, "mcp.pool_proxy");1624                if (pp != NULL) {1625                    dump_pool_info(pp->main);1626                }1627            }1628        }1629        lua_pop(L, 1); // drop value1630    }1631    fprintf(stderr, "-----------------\n");16321633}

Code quality findings 21

Warning: Allocation result must be checked for NULL before use to prevent null pointer dereference.
warning correctness malloc-unchecked
proxy_ctx_t *ctx = calloc(1, sizeof(proxy_ctx_t));
Warning: Allocation result must be checked for NULL before use to prevent null pointer dereference.
warning correctness malloc-unchecked
struct mcp_memprofile *prof = calloc(1, sizeof(struct mcp_memprofile));
Warning: Allocation result must be checked for NULL before use to prevent null pointer dereference.
warning correctness malloc-unchecked
proxy_event_thread_t *t = calloc(1, sizeof(proxy_event_thread_t));
Warning: Allocation result must be checked for NULL before use to prevent null pointer dereference.
warning correctness malloc-unchecked
struct mcp_memprofile *prof = calloc(1, sizeof(struct mcp_memprofile));
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
if (cores == LUA_OK) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
if (type == LUA_TUSERDATA && (r = luaL_testudata(Lc, 1, "mcp.response")) != NULL) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
if (r->status != MCMC_OK && r->resp.type != MCMC_RESP_ERRMSG) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
} else if (r->cresp) {
Warning: realloc() result must be stored in a temporary variable. Using 'ptr = realloc(ptr, ...)' directly causes a memory leak if realloc fails and returns NULL.
warning correctness realloc-unchecked
return realloc(ptr, nsize);
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
for (; i < top + 1; i++) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
if (lua_getmetatable(L, i) != 0) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
if (lua_rawget(L, -2) != LUA_TNIL) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
while (lua_next(L, ridx) != 0) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
if (type == LUA_TUSERDATA) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
if (f != NULL && strcmp(name, f->name) == 0) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
while (lua_next(L, tidx) != 0) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
while (lua_next(L, ridx) != 0) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
if (type == LUA_TUSERDATA) {
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
if (p != NULL) {
Info: Magic number detected in condition or control flow. Use named constants via #define or const for better readability and maintainability.
info maintainability magic-number
if (c->rbytes > 1024) {
Info: Magic number detected in condition or control flow. Use named constants via #define or const for better readability and maintainability.
info maintainability magic-number
if (ptr - c->rcurr > 100 ||

Security findings 1

Info: rand() produces predictable sequences unless seeded with srand(). For security-sensitive randomness, use a cryptographic RNG.
info security predictable-rand
thr->proxy_rng[x] = rand();

Get this view in your editor

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

Generated by searchcode.com · powered by code_get_file + code_get_findings