Warning: Allocation result must be checked for NULL before use to prevent null pointer dereference.
char *namebuf = calloc(1, namelen);
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}
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