proxy_config.c C 756 lines View on github.com → Search inside
1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */2// Functions related to the configuration management threads and VM3// TODO (v2): move worker thread related code back out of here.45#include "proxy.h"6#include "vendor/routelib/routelib.h"78// not using queue.h becuase those require specific storage for HEAD.9// it's not possible to have the HEAD simply be in the proxy context because10// it would need to know the offset into this private structure.11// This might be doable but the problem is too trivial to spend time on it.12#define MCP_LUAFILE_SIZE 1638413struct _mcp_luafile {14    size_t size;15    size_t used;16    bool loaded; // flip this to false before each load use17    char *buf;18    char *fname; // filename to load19    struct _mcp_luafile *next;20};2122static int _msghandler(lua_State *L) {23    const char *msg = lua_tostring(L, 1);24    luaL_traceback(L, L, msg, 1);25    return 1;26}2728static int _run_pcall(lua_State *L, int narg, int nres) {29    int msgi = lua_gettop(L) - narg;30    lua_pushcfunction(L, _msghandler);31    lua_insert(L, msgi);32    int res = lua_pcall(L, narg, nres, msgi);33    lua_remove(L, msgi); // need to drop the msg handler func.3435    return res;36}3738static int _dump_helper(lua_State *L, const void *p, size_t sz, void *ud) {39    (void)L;40    struct _mcp_luafile *db = ud;41    if (db->used + sz > db->size) {42        // increase by blocks instead of doubling to avoid memory waste43        db->size += MCP_LUAFILE_SIZE;44        char *nb = realloc(db->buf, db->size);45        if (nb == NULL) {46            return -1;47        }48        db->buf = nb;49    }50    memcpy(db->buf + db->used, (const char *)p, sz);51    db->used += sz;52    return 0;53}5455static const char * _load_helper(lua_State *L, void *data, size_t *size) {56    (void)L;57    struct _mcp_luafile *db = data;58    if (db->loaded) {59        *size = 0;60        return NULL;61    }62    *size = db->used;63    db->loaded = true;64    return db->buf;65}6667void proxy_start_reload(void *arg) {68    proxy_ctx_t *ctx = arg;69    if (pthread_mutex_trylock(&ctx->config_lock) == 0) {70        ctx->loading = true;71        pthread_cond_signal(&ctx->config_cond);72        pthread_mutex_unlock(&ctx->config_lock);73    }74}7576int proxy_first_confload(void *arg) {77    proxy_ctx_t *ctx = arg;78    pthread_mutex_lock(&ctx->config_lock);79    ctx->loading = true;80    pthread_cond_signal(&ctx->config_cond);81    pthread_mutex_unlock(&ctx->config_lock);8283    while (1) {84        bool stop = false;85        pthread_mutex_lock(&ctx->config_lock);86        if (!ctx->loading) {87            stop = true;88        }89        pthread_mutex_unlock(&ctx->config_lock);90        if (stop)91            break;92    }93    int fails = 0;94    STAT_L(ctx);95    fails = ctx->global_stats.config_reload_fails;96    STAT_UL(ctx);97    if (fails) {98        return -1;99    }100101    return 0;102}103104// Manages a queue of inbound objects destined to be deallocated.105static void *_proxy_manager_thread(void *arg) {106    proxy_ctx_t *ctx = arg;107    globalobj_head_t head;108109    pthread_mutex_lock(&ctx->manager_lock);110    while (1) {111        STAILQ_INIT(&head);112        while (STAILQ_EMPTY(&ctx->manager_head)) {113            pthread_cond_wait(&ctx->manager_cond, &ctx->manager_lock);114        }115116        // pull dealloc queue into local queue.117        STAILQ_CONCAT(&head, &ctx->manager_head);118        pthread_mutex_unlock(&ctx->manager_lock);119120        // Config lock is required for using config VM.121        pthread_mutex_lock(&ctx->config_lock);122        lua_State *L = ctx->proxy_state;123        struct mcp_globalobj_s *g;124        STAILQ_FOREACH(g, &head, next) {125            // we let the object _gc() handle backend/etc references126            pthread_mutex_lock(&g->lock);127            assert(g->self_ref != -1);128            // See comment on mcp_gobj_ref()129            if (g->self_ref < -1) {130                g->refcount--;131                g->self_ref = -g->self_ref;132            }133            assert(g->self_ref > 0 || g->refcount == 0);134            if (g->refcount == 0) {135                luaL_unref(L, LUA_REGISTRYINDEX, g->self_ref);136                g->self_ref = -1;137            }138            pthread_mutex_unlock(&g->lock);139        }140        // force lua garbage collection so any resources close out quickly.141        lua_gc(L, LUA_GCCOLLECT);142        // twice because objects with garbage collector handlers are only143        // marked on the first collection cycle.144        lua_gc(L, LUA_GCCOLLECT);145        // must hold this lock while interacting with the config VM.146        pthread_mutex_unlock(&ctx->config_lock);147148        // done.149        pthread_mutex_lock(&ctx->manager_lock);150    }151152    return NULL;153}154155// TODO: only run routine if something changed.156// This compacts all of the names for proxy user stats into a linear buffer,157// which can save considerable CPU when emitting a large number of stats. It158// also saves some total memory by having one linear buffer instead of many159// potentially small aligned allocations.160static void proxy_config_stats_prep(proxy_ctx_t *ctx) {161    char *oldnamebuf = ctx->user_stats_namebuf;162    struct proxy_user_stats_entry *entries = ctx->user_stats;163    size_t namelen = 0;164165    STAT_L(ctx);166    // find size of new compact name buffer167    for (int x = 0; x < ctx->user_stats_num; x++) {168        if (entries[x].name) {169            namelen += strlen(entries[x].name) + 1; // null byte170        } else if (entries[x].cname) {171            char *name = oldnamebuf + entries[x].cname;172            namelen += strlen(name) + 1;173        }174    }175    // start one byte into the cname buffer so we can do faster checks on if a176    // name exists or not. so extend the buffer by one byte.177    namelen++;178179    char *namebuf = calloc(1, namelen);180    // copy names into the compact buffer181    char *p = namebuf + 1;182    for (int x = 0; x < ctx->user_stats_num; x++) {183        struct proxy_user_stats_entry *e = &entries[x];184        char *newname = NULL;185        if (e->name) {186            // skip blank names.187            if (e->name[0]) {188                newname = e->name;189            }190        } else if (e->cname) {191            // else re-copy from old buffer192            newname = oldnamebuf + e->cname;193        }194195        if (newname) {196            // set the buffer offset for this name197            e->cname = p - namebuf;198            // copy in the name199            size_t nlen = strlen(newname);200            memcpy(p, newname, nlen);201            p += nlen;202            *p = '\0'; // add null byte203            p++;204        } else {205            // name is blank or doesn't exist, ensure we skip it.206            e->cname = 0;207        }208209        if (e->name) {210            // now get rid of the name buffer.211            free(e->name);212            e->name = NULL;213        }214    }215216    ctx->user_stats_namebuf = namebuf;217    if (oldnamebuf) {218        free(oldnamebuf);219    }220    STAT_UL(ctx);221}222223static void proxy_config_reload(proxy_ctx_t *ctx) {224    LOGGER_LOG(NULL, LOG_PROXYEVENTS, LOGGER_PROXY_CONFIG, NULL, "start");225    STAT_INCR(ctx, config_reloads, 1);226    // gen. used for tracking object lifecycles over time.227    // ie: ensuring old things are unloaded.228    ctx->config_generation++;229    lua_State *L = ctx->proxy_state;230    lua_settop(L, 0); // clear off any crud that could have been left on the stack.231232    // The main stages of config reload are:233    // - load and execute the config file234    // - run mcp_config_pools()235    // - for each worker:236    //   - copy and execute new lua code237    //   - copy selector table238    //   - run mcp_config_routes()239240    if (proxy_load_config(ctx) != 0) {241        // Failed to load. log and wait for a retry.242        STAT_INCR(ctx, config_reload_fails, 1);243        LOGGER_LOG(NULL, LOG_PROXYEVENTS, LOGGER_PROXY_CONFIG, NULL, "failed");244        return;245    }246247    proxy_config_stats_prep(ctx);248249    // TODO (v2): create a temporary VM to test-load the worker code into.250    // failing to load partway through the worker VM reloads can be251    // critically bad if we're not careful about references.252    // IE: the config VM _must_ hold references to selectors and backends253    // as long as they exist in any worker for any reason.254255    for (int x = 0; x < settings.num_threads; x++) {256        LIBEVENT_THREAD *thr = get_worker_thread(x);257258        pthread_mutex_lock(&ctx->worker_lock);259        ctx->worker_done = false;260        ctx->worker_failed = false;261        proxy_reload_notify(thr);262        while (!ctx->worker_done) {263            // in case of spurious wakeup.264            pthread_cond_wait(&ctx->worker_cond, &ctx->worker_lock);265        }266        pthread_mutex_unlock(&ctx->worker_lock);267268        // Code load bailed.269        if (ctx->worker_failed) {270            STAT_INCR(ctx, config_reload_fails, 1);271            LOGGER_LOG(NULL, LOG_PROXYEVENTS, LOGGER_PROXY_CONFIG, NULL, "failed");272            return;273        }274    }275276    // Need to clear the reset flag for the stats system after pushing the new277    // config to each worker.278    STAT_L(ctx);279    for (int x = 0; x < ctx->user_stats_num; x++) {280        ctx->user_stats[x].reset = false;281    }282    STAT_UL(ctx);283284    lua_pop(ctx->proxy_state, 1); // drop config_pools return value285    LOGGER_LOG(NULL, LOG_PROXYEVENTS, LOGGER_PROXY_CONFIG, NULL, "done");286}287288// Very basic scheduler. Unsorted because we don't expect a huge list of289// functions to run.290static void proxy_run_crons(proxy_ctx_t *ctx) {291    lua_State *L = ctx->proxy_state;292    assert(lua_gettop(L) == 0);293    assert(ctx->cron_ref);294    struct timespec now;295296    // Fetch the cron table. Created on startup so must exist.297    lua_rawgeti(L, LUA_REGISTRYINDEX, ctx->cron_ref);298299    clock_gettime(CLOCK_REALTIME, &now);300    if (ctx->cron_next <= now.tv_sec) {301        ctx->cron_next = INT_MAX;302    } else {303        // no crons ready.304        return;305    }306307    // Loop the cron entries.308    lua_pushnil(L);309    while (lua_next(L, 1) != 0) {310        const char *key = lua_tostring(L, -2);311        mcp_cron_t *ce = lua_touserdata(L, -1);312        int idx = lua_absindex(L, -1);313314        // check generation.315        if (ctx->config_generation != ce->gen) {316            // remove entry.317            lua_pushnil(L);318            lua_setfield(L, 1, key);319        } else if (ce->next <= now.tv_sec) {320            // grab func and execute it321            lua_getiuservalue(L, idx, 1);322            // no arguments or return values323            int res = lua_pcall(L, 0, 0, 0);324            STAT_INCR(ctx, config_cron_runs, 1);325            if (res != LUA_OK) {326                LOGGER_LOG(NULL, LOG_PROXYEVENTS, LOGGER_PROXY_ERROR, NULL, lua_tostring(L, -1));327                STAT_INCR(ctx, config_cron_fails, 1);328                lua_pop(L, 1); // drop error.329            }330331            if (ce->repeat) {332                ce->next = now.tv_sec + ce->every;333                // if rescheduled, check next against ctx. update if sooner334                if (ctx->cron_next > ce->next) {335                    ctx->cron_next = ce->next;336                }337            } else {338                // non-repeating cron. delete entry.339                lua_pushnil(L);340                lua_setfield(L, 1, key);341            }342        } else {343            // not scheduled to run now, but check if we're next.344            if (ctx->cron_next > ce->next) {345                ctx->cron_next = ce->next;346            }347        }348349        lua_pop(L, 1); // drop value so we can loop.350    }351352    lua_pop(L, 1); // drop cron table.353}354355// Thread handling the configuration reload sequence.356// TODO (v2): get a logger instance.357// TODO (v2): making this "safer" will require a few phases of work.358// 1) JFDI359// 2) "test VM" -> from config thread, test the worker reload portion.360// 3) "unit testing" -> from same temporary worker VM, execute set of361// integration tests that must pass.362// 4) run update on each worker, collecting new mcp.attach() hooks.363//    Once every worker has successfully executed and set new hooks, roll364//    through a _second_ time to actually swap the hook structures and unref365//    the old structures where marked dirty.366static void *_proxy_config_thread(void *arg) {367    proxy_ctx_t *ctx = arg;368    struct timespec wait = {0};369370    logger_create();371    pthread_mutex_lock(&ctx->config_lock);372    pthread_cond_signal(&ctx->config_cond);373    while (1) {374        ctx->loading = false;375376        // cron only thinks in whole seconds.377        wait.tv_sec = ctx->cron_next;378        pthread_cond_timedwait(&ctx->config_cond, &ctx->config_lock, &wait);379380        proxy_run_crons(ctx);381382        if (ctx->loading) {383            proxy_config_reload(ctx);384        }385    }386387    return NULL;388}389390int _start_proxy_config_threads(proxy_ctx_t *ctx) {391    int ret;392393    pthread_mutex_lock(&ctx->config_lock);394    if ((ret = pthread_create(&ctx->config_tid, NULL,395                    _proxy_config_thread, ctx)) != 0) {396        fprintf(stderr, "Failed to start proxy configuration thread: %s\n",397                strerror(ret));398        pthread_mutex_unlock(&ctx->config_lock);399        return -1;400    }401    thread_setname(ctx->config_tid, "mc-prx-config");402    // Avoid returning until the config thread has actually started.403    pthread_cond_wait(&ctx->config_cond, &ctx->config_lock);404    pthread_mutex_unlock(&ctx->config_lock);405406    pthread_mutex_lock(&ctx->manager_lock);407    if ((ret = pthread_create(&ctx->manager_tid, NULL,408                    _proxy_manager_thread, ctx)) != 0) {409        fprintf(stderr, "Failed to start proxy manager thread: %s\n",410                strerror(ret));411        pthread_mutex_unlock(&ctx->manager_lock);412        return -1;413    }414    thread_setname(ctx->manager_tid, "mc-prx-manager");415    pthread_mutex_unlock(&ctx->manager_lock);416417    return 0;418}419420// this splits a list of lua startfiles into independent data chunk buffers421// we call this once the first time we start so we can use mallocs without422// having to armor against runtime malloc failures... as much.423static int proxy_init_startfiles(proxy_ctx_t *ctx, const char *files) {424    char *flist = strdup(settings.proxy_startfile);425    if (flist == NULL) {426        fprintf(stderr, "ERROR: failed to allocate memory for parsing proxy_startfile\n");427        return -1;428    }429430    char *b;431    for (const char *p = strtok_r(flist, ":", &b);432            p != NULL;433            p = strtok_r(NULL, ":", &b)) {434        struct _mcp_luafile *db = calloc(sizeof(struct _mcp_luafile), 1);435        if (db == NULL) {436            fprintf(stderr, "ERROR: failed to allocate memory for parsing proxy_startfile\n");437            return -1;438        }439        db->size = MCP_LUAFILE_SIZE;440        db->buf = calloc(db->size, 1);441        db->fname = strdup(p);442        if (db->buf == NULL || db->fname == NULL) {443            fprintf(stderr, "ERROR: failed to allocate memory while parsing proxy_startfile\n");444            return -1;445        }446447        // put new file at tail448        if (ctx->proxy_code == NULL) {449            ctx->proxy_code = db;450        } else {451            struct _mcp_luafile *list = ctx->proxy_code;452            while (list->next) {453                list = list->next;454            }455            assert(list->next == NULL);456            list->next = db;457        }458    }459460    free(flist);461    return 0;462}463464static int proxy_load_files(proxy_ctx_t *ctx) {465    lua_State *L = ctx->proxy_state;466    struct _mcp_luafile *db = ctx->proxy_code;467    assert(db);468469    while (db) {470        int res;471        // clear the buffer for reuse.472        memset(db->buf, 0, db->size);473        db->used = 0;474475        if (strcmp(db->fname, "routelib") == 0) {476            res = luaL_loadbuffer(L, routelib_lua, routelib_lua_len, "routelib");477        } else {478            res = luaL_loadfile(L, db->fname);479        }480        if (res != LUA_OK) {481            fprintf(stderr, "ERROR: Failed to load proxy_startfile: %s\n", lua_tostring(L, -1));482            return -1;483        }484        // LUA_OK, LUA_ERRSYNTAX, LUA_ERRMEM, LUA_ERRFILE485486        // Now we need to dump the compiled code into bytecode.487        // This will then get loaded into worker threads.488        lua_dump(L, _dump_helper, db, 0);489        // 0 means no error.490491        // now we complete the data load by calling the function.492        res = _run_pcall(L, 0, LUA_MULTRET);493        if (res != LUA_OK) {494            fprintf(stderr, "ERROR: Failed to load data into lua config state: %s\n", lua_tostring(L, -1));495            exit(EXIT_FAILURE);496        }497498        db = db->next;499    }500501    return 0;502}503504int proxy_load_config(void *arg) {505    proxy_ctx_t *ctx = arg;506    lua_State *L = ctx->proxy_state;507    int res = 0;508509    if (ctx->proxy_code == NULL) {510        res = proxy_init_startfiles(ctx, settings.proxy_startfile);511        if (res != 0) {512            return res;513        }514    }515516    // load each of the data files in order.517    res = proxy_load_files(ctx);518519    // call the mcp_config_pools function to get the central backends.520    lua_getglobal(L, "mcp_config_pools");521522    if (lua_isnil(L, -1)) {523        fprintf(stderr, "ERROR: Configuration file missing 'mcp_config_pools' function\n");524        exit(EXIT_FAILURE);525    }526    lua_pushnil(L); // no "old" config yet.527    if (_run_pcall(L, 1, 1) != LUA_OK) {528        fprintf(stderr, "ERROR: Failed to execute mcp_config_pools: %s\n", lua_tostring(L, -1));529        exit(EXIT_FAILURE);530    }531532    // result is our main config.533    return 0;534}535536static int _copy_pool(lua_State *from, lua_State *to, LIBEVENT_THREAD *thr) {537    // from, -3 should have the userdata.538    mcp_pool_t *p = luaL_checkudata(from, -3, "mcp.pool");539    size_t size = sizeof(mcp_pool_proxy_t);540    mcp_pool_proxy_t *pp = lua_newuserdatauv(to, size, 0);541    luaL_setmetatable(to, "mcp.pool_proxy");542543    pp->main = p;544    if (p->use_iothread) {545        pp->pool = p->pool;546    } else {547        // allow 0 indexing for backends when unique to each worker thread548        pp->pool = &p->pool[thr->thread_baseid * p->pool_size];549    }550    lua_pushvalue(from, -3); // dupe pool for referencing551    mcp_gobj_ref(from, &p->g); // pops obj copy552    return 0;553}554555static void _copy_config_table(lua_State *from, lua_State *to, LIBEVENT_THREAD *thr);556// (from, -1) is the source value557// should end with (to, -1) being the new value.558static void _copy_config_table(lua_State *from, lua_State *to, LIBEVENT_THREAD *thr) {559    int type = lua_type(from, -1);560    bool found = false;561    luaL_checkstack(from, 4, "configuration error: table recursion too deep");562    luaL_checkstack(to, 4, "configuration error: table recursion too deep");563    switch (type) {564        case LUA_TNIL:565            lua_pushnil(to);566            break;567        case LUA_TUSERDATA:568            // see dump_stack() - check if it's something we handle.569            if (lua_getmetatable(from, -1) != 0) {570                lua_pushstring(from, "__name");571                if (lua_rawget(from, -2) != LUA_TNIL) {572                    const char *name = lua_tostring(from, -1);573                    if (strcmp(name, "mcp.pool") == 0) {574                        _copy_pool(from, to, thr);575                        found = true;576                    } else if (strcmp(name, "mcp.ratelim_global_tbf") == 0) {577                        mcp_ratelim_proxy_tbf(from, to);578                        found = true;579                    }580                }581                lua_pop(from, 2);582            }583            if (!found) {584                proxy_lua_error(from, "unhandled userdata type in configuration table\n");585            }586            break;587        case LUA_TNUMBER:588            if (lua_isinteger(from, -1)) {589                lua_pushinteger(to, lua_tointeger(from, -1));590            } else {591                lua_pushnumber(to, lua_tonumber(from, -1));592            }593            break;594        case LUA_TSTRING:595            lua_pushlstring(to, lua_tostring(from, -1), lua_rawlen(from, -1));596            break;597        case LUA_TBOOLEAN:598            lua_pushboolean(to, lua_toboolean(from, -1));599            break;600        case LUA_TTABLE:601            // TODO (v2): copy the metatable first?602            // TODO (v2): size narr/nrec from old table and use createtable to603            // pre-allocate.604            lua_newtable(to); // throw new table on worker605            int t = lua_absindex(from, -1); // static index of table to copy.606            int nt = lua_absindex(to, -1); // static index of new table.607            lua_pushnil(from); // start iterator for main608            while (lua_next(from, t) != 0) {609                // (key, -2), (val, -1)610                int keytype = lua_type(from, -2);611                // to intentionally limit complexity and allow for future612                // optimizations we restrict what types may be used as keys613                // for sub-tables.614                switch (keytype) {615                    case LUA_TSTRING:616                        // to[l]string converts the actual key in the table617                        // into a string, so we must not do that unless it618                        // already is one.619                        lua_pushlstring(to, lua_tostring(from, -2), lua_rawlen(from, -2));620                        break;621                    case LUA_TNUMBER:622                        if (lua_isinteger(from, -2)) {623                            lua_pushinteger(to, lua_tointeger(from, -2));624                        } else {625                            lua_pushnumber(to, lua_tonumber(from, -2));626                        }627                        break;628                    default:629                        proxy_lua_error(from, "configuration table keys must be strings or numbers");630                }631                // lua_settable(to, n) - n being the table632                // takes -2 key -1 value, pops both.633                // use lua_absindex(L, -1) and so to convert easier?634                _copy_config_table(from, to, thr); // push next value.635                lua_settable(to, nt);636                lua_pop(from, 1); // drop value, keep key.637            }638            // top of from is now the original table.639            // top of to should be the new table.640            break;641        default:642            proxy_lua_error(from, "unhandled data type in configuration table\n");643    }644}645646// Run from proxy worker to coordinate code reload.647// config_lock must be held first.648void proxy_worker_reload(void *arg, LIBEVENT_THREAD *thr) {649    proxy_ctx_t *ctx = arg;650    pthread_mutex_lock(&ctx->worker_lock);651    if (proxy_thread_loadconf(ctx, thr) != 0) {652        ctx->worker_failed = true;653    }654    ctx->worker_done = true;655    pthread_cond_signal(&ctx->worker_cond);656    pthread_mutex_unlock(&ctx->worker_lock);657}658659// FIXME (v2): need to test how to recover from an actual error here. error message660// needs to go somewhere useful, counters added, etc.661int proxy_thread_loadconf(proxy_ctx_t *ctx, LIBEVENT_THREAD *thr) {662    lua_State *L = thr->L;663    // load the precompiled config functions.664665    struct _mcp_luafile *db = ctx->proxy_code;666    while (db) {667        db->loaded = false;668        int res = lua_load(L, _load_helper, db, "config", NULL);669        if (res != LUA_OK) {670            fprintf(stderr, "Failed to load data into worker thread: %s\n", lua_tostring(L, -1));671            return -1;672        }673674        res = _run_pcall(L, 0, LUA_MULTRET);675        if (res != LUA_OK) {676            // FIXME (v2): don't exit here!677            fprintf(stderr, "Failed to load data into worker thread: %s\n", lua_tostring(L, -1));678            return -1;679        }680681        db = db->next;682    }683684    lua_getglobal(L, "mcp_config_routes");685    // create deepcopy of argument to pass into mcp_config_routes.686    // FIXME (v2): to avoid lua SIGABRT'ing on errors we need to protect the call687    // normal pattern:688    // lua_pushcfunction(L, &_copy_config_table);689    // lua_pushlightuserdata(L, &L2);690    // res = la_pcall(L, etc);691    // ... but since this is cross-VM we could get errors from not the692    // protected VM, breaking setjmp/etc.693    // for this part of the code we should override lua_atpanic(),694    // allowing us to specifically recover and bail.695    // However, again, this will require the next version of the config reload696    // code since we are re-using the VM's and a panic can leave us in a697    // broken state.698    // If the setjump/longjump combos are compatible a pcall for from and699    // atpanic for to might work best, since the config VM is/should be long700    // running and worker VM's should be rotated.701    _copy_config_table(ctx->proxy_state, L, thr);702703    // copied value is in front of route function, now call it.704    if (_run_pcall(L, 1, 0) != LUA_OK) {705        fprintf(stderr, "Failed to execute mcp_config_routes: %s\n", lua_tostring(L, -1));706        return -1;707    }708709    // update user stats710    STAT_L(ctx);711    struct proxy_user_stats_entry *us = ctx->user_stats;712    int stats_num = ctx->user_stats_num;713    struct proxy_user_stats *tus = NULL;714    if (stats_num != 0) {715        pthread_mutex_lock(&thr->stats.mutex);716        if (thr->proxy_user_stats == NULL) {717            tus = calloc(1, sizeof(struct proxy_user_stats));718            thr->proxy_user_stats = tus;719        } else {720            tus = thr->proxy_user_stats;721        }722723        // originally this was a realloc routine but it felt fragile.724        // that might still be a better idea; still need to zero out the end.725        uint64_t *counters = calloc(stats_num, sizeof(uint64_t));726727        // note that num_stats can _only_ grow in size.728        if (tus->counters) {729            // pull in old counters, if the names didn't change.730            for (int x = 0; x < tus->num_stats; x++) {731                if (us[x].reset) {732                    counters[x] = 0;733                } else {734                    counters[x] = tus->counters[x];735                }736            }737            assert(tus->num_stats <= stats_num);738            free(tus->counters);739        }740        tus->counters = counters;741        tus->num_stats = stats_num;742743        pthread_mutex_unlock(&thr->stats.mutex);744    }745    // also grab the concurrent request limit746    thr->proxy_active_req_limit = ctx->active_req_limit;747    STAT_UL(ctx);748749    // update limit counter(s)750    thr->proxy_buffer_memory_limit = ctx->buffer_memory_limit;751752    return 0;753}

Code quality findings 4

Warning: Allocation result must be checked for NULL before use to prevent null pointer dereference.
warning correctness malloc-unchecked
char *namebuf = calloc(1, namelen);
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
switch (type) {
Warning: Allocation result must be checked for NULL before use to prevent null pointer dereference.
warning correctness malloc-unchecked
tus = calloc(1, sizeof(struct proxy_user_stats));
Warning: Allocation result must be checked for NULL before use to prevent null pointer dereference.
warning correctness malloc-unchecked
uint64_t *counters = calloc(stats_num, sizeof(uint64_t));

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