| /* | |
| Licensed to the Apache Software Foundation (ASF) under one | |
| or more contributor license agreements. See the NOTICE file | |
| distributed with this work for additional information | |
| regarding copyright ownership. The ASF licenses this file | |
| to you under the Apache License, Version 2.0 (the | |
| "License"); you may not use this file except in compliance | |
| with the License. You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| */ | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <pthread.h> | |
| #include <getopt.h> | |
| #include <inttypes.h> | |
| #include <pthread.h> | |
| #include <atomic> | |
| #include <chrono> | |
| #include <thread> | |
| #include <vector> | |
| #include "ts_lua_util.h" | |
| #include "tsutil/StringCompare.h" | |
| extern "C" { | |
| #include "luajit.h" | |
| } // extern "C" | |
| #define TS_LUA_MAX_STATE_COUNT 256 | |
| #define TS_LUA_STATS_TIMEOUT 5000 // 5s -- convert to configurable | |
| #define TS_LUA_STATS_BUFFER_SIZE 10 // stats buffer | |
| #define TS_LUA_IND_STATE 0 | |
| #define TS_LUA_IND_GC_BYTES 1 | |
| #define TS_LUA_IND_THREADS 2 | |
| #define TS_LUA_IND_SIZE 3 | |
| static uint64_t ts_lua_http_next_id = 0; | |
| static uint64_t ts_lua_g_http_next_id = 0; | |
| static ts_lua_main_ctx *ts_lua_main_ctx_array = nullptr; | |
| static ts_lua_main_ctx *ts_lua_g_main_ctx_array = nullptr; | |
| static pthread_key_t lua_g_state_key; | |
| static pthread_key_t lua_state_key; | |
| // records.yaml entry injected by plugin | |
| static char const *const ts_lua_mgmt_state_str = "proxy.config.plugin.lua.max_states"; | |
| static char const *const ts_lua_mgmt_state_regex = "^[1-9][0-9]*$"; | |
| // this is set the first time global configuration is probed. | |
| static int ts_lua_max_state_count = 0; | |
| // Set once the shutdown barrier owns every Lua state mutex and keeps them. Read by | |
| // TSRemapDeleteInstance, on the event thread that continues shutting ATS down. | |
| static std::atomic<bool> shutdown_barrier_engaged{false}; | |
| // lifecycle message tag | |
| static char const *const print_tag = "stats_print"; | |
| static char const *const reset_tag = "stats_reset"; | |
| // stat record strings | |
| static char const *const ts_lua_stat_strs[] = { | |
| "plugin.lua.remap.states", | |
| "plugin.lua.remap.gc_bytes", | |
| "plugin.lua.remap.threads", | |
| nullptr, | |
| }; | |
| static char const *const ts_lua_g_stat_strs[] = { | |
| "plugin.lua.global.states", | |
| "plugin.lua.global.gc_bytes", | |
| "plugin.lua.global.threads", | |
| nullptr, | |
| }; | |
| struct ts_lua_plugin_stats { | |
| ts_lua_main_ctx *main_ctx_array; | |
| TSMgmtInt gc_kb; // last collected gc in kb | |
| TSMgmtInt threads; // last collected number active threads | |
| int stat_inds[TS_LUA_IND_SIZE]; // stats indices | |
| }; | |
| ts_lua_plugin_stats * | |
| create_plugin_stats(ts_lua_main_ctx *const main_ctx_array, char const *const *stat_strs) | |
| { | |
| auto *const stats = TSRalloc<ts_lua_plugin_stats>(); | |
| memset(stats, 0, sizeof(ts_lua_plugin_stats)); | |
| stats->main_ctx_array = main_ctx_array; | |
| // sample buffers | |
| stats->gc_kb = 0; | |
| stats->threads = 0; | |
| int const max_state_count = ts_lua_max_state_count; | |
| for (int ind = 0; ind < TS_LUA_IND_SIZE; ++ind) { | |
| stats->stat_inds[ind] = TSStatCreate(stat_strs[ind], TS_RECORDDATATYPE_INT, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_SUM); | |
| } | |
| // initialize the number of states stat | |
| int const sid = stats->stat_inds[TS_LUA_IND_STATE]; | |
| if (TS_ERROR != sid) { | |
| TSStatIntSet(sid, max_state_count); | |
| } | |
| return stats; | |
| } | |
| ts_lua_main_ctx * | |
| create_lua_vms() | |
| { | |
| ts_lua_main_ctx *ctx_array = nullptr; | |
| // Inject the setting into records.yaml | |
| static bool ts_mgt_int_inserted = false; | |
| if (!ts_mgt_int_inserted) { | |
| if (TS_SUCCESS == TSMgmtIntCreate(TS_RECORDTYPE_CONFIG, ts_lua_mgmt_state_str, TS_LUA_MAX_STATE_COUNT, | |
| TS_RECORDUPDATE_RESTART_TS, TS_RECORDCHECK_INT, ts_lua_mgmt_state_regex, | |
| TS_RECORDACCESS_READ_ONLY)) { | |
| Dbg(dbg_ctl, "[%s] registered config string %s: with default [%d]", __FUNCTION__, ts_lua_mgmt_state_str, | |
| TS_LUA_MAX_STATE_COUNT); | |
| } else { | |
| TSError("[%s][%s] failed to register %s", TS_LUA_DEBUG_TAG, __FUNCTION__, ts_lua_mgmt_state_str); | |
| } | |
| ts_mgt_int_inserted = true; | |
| } | |
| if (0 == ts_lua_max_state_count) { | |
| TSMgmtInt mgmt_state = 0; | |
| if (TS_SUCCESS != TSMgmtIntGet(ts_lua_mgmt_state_str, &mgmt_state)) { | |
| Dbg(dbg_ctl, "[%s] setting max state to default: %d", __FUNCTION__, TS_LUA_MAX_STATE_COUNT); | |
| ts_lua_max_state_count = TS_LUA_MAX_STATE_COUNT; | |
| } else { | |
| ts_lua_max_state_count = (int)mgmt_state; | |
| Dbg(dbg_ctl, "[%s] found %s: [%d]", __FUNCTION__, ts_lua_mgmt_state_str, ts_lua_max_state_count); | |
| } | |
| if (ts_lua_max_state_count < 1) { | |
| TSError("[ts_lua][%s] invalid %s: %d", __FUNCTION__, ts_lua_mgmt_state_str, ts_lua_max_state_count); | |
| ts_lua_max_state_count = 0; | |
| return nullptr; | |
| } | |
| } | |
| ctx_array = TSRalloc<ts_lua_main_ctx>(ts_lua_max_state_count); | |
| memset(ctx_array, 0, sizeof(ts_lua_main_ctx) * ts_lua_max_state_count); | |
| int const ret = ts_lua_create_vm(ctx_array, ts_lua_max_state_count); | |
| if (ret) { | |
| ts_lua_destroy_vm(ctx_array, ts_lua_max_state_count); | |
| TSfree(ctx_array); | |
| ctx_array = nullptr; | |
| return nullptr; | |
| } | |
| // Initialize the GC numbers, no need to lock here | |
| for (int index = 0; index < ts_lua_max_state_count; ++index) { | |
| ts_lua_main_ctx *const main_ctx = (ctx_array + index); | |
| lua_State *const lstate = main_ctx->lua; | |
| ts_lua_ctx_stats *const stats = main_ctx->stats; | |
| stats->gc_kb = stats->gc_kb_max = lua_getgccount(lstate); | |
| } | |
| return ctx_array; | |
| } | |
| // dump exhaustive per state summary stats | |
| static void | |
| collectStats(ts_lua_plugin_stats *const plugin_stats) | |
| { | |
| TSMgmtInt gc_kb_total = 0; | |
| TSMgmtInt threads_total = 0; | |
| ts_lua_main_ctx *const main_ctx_array = plugin_stats->main_ctx_array; | |
| // aggregate stats on the states | |
| for (int index = 0; index < ts_lua_max_state_count; ++index) { | |
| ts_lua_main_ctx *const main_ctx = (main_ctx_array + index); | |
| if (nullptr != main_ctx) { | |
| ts_lua_ctx_stats *const stats = main_ctx->stats; | |
| TSMutexLock(stats->mutexp); | |
| gc_kb_total += stats->gc_kb; | |
| threads_total += stats->threads; | |
| TSMutexUnlock(stats->mutexp); | |
| } | |
| } | |
| // set the stats sample slot | |
| plugin_stats->gc_kb = gc_kb_total; | |
| plugin_stats->threads = threads_total; | |
| } | |
| static void | |
| publishStats(ts_lua_plugin_stats *const plugin_stats) | |
| { | |
| TSMgmtInt const gc_bytes = plugin_stats->gc_kb * 1024; | |
| TSStatIntSet(plugin_stats->stat_inds[TS_LUA_IND_GC_BYTES], gc_bytes); | |
| TSStatIntSet(plugin_stats->stat_inds[TS_LUA_IND_THREADS], plugin_stats->threads); | |
| } | |
| // dump exhaustive per state summary stats | |
| static int | |
| statsHandler(TSCont contp, TSEvent /* event ATS_UNUSED */, void * /* edata ATS_UNUSED */) | |
| { | |
| ts_lua_plugin_stats *const plugin_stats = (ts_lua_plugin_stats *)TSContDataGet(contp); | |
| collectStats(plugin_stats); | |
| publishStats(plugin_stats); | |
| TSContScheduleOnPool(contp, TS_LUA_STATS_TIMEOUT, TS_THREAD_POOL_TASK); | |
| return TS_EVENT_NONE; | |
| } | |
| static void | |
| get_time_now_str(char *const buf, size_t const buflen) | |
| { | |
| TSHRTime const timenowusec = TShrtime(); | |
| int64_t const timemsec = (int64_t)(timenowusec / 1000000); | |
| time_t const timesec = (time_t)(timemsec / 1000); | |
| int const ms = (int)(timemsec % 1000); | |
| struct tm tm; | |
| gmtime_r(×ec, &tm); | |
| size_t const dtlen = strftime(buf, buflen, "%b %e %H:%M:%S", &tm); | |
| // tack on the ms | |
| snprintf(buf + dtlen, buflen - dtlen, ".%03d", ms); | |
| } | |
| // dump exhaustive per state summary stats | |
| static int | |
| lifecycleHandler(TSCont contp, TSEvent /* event ATS_UNUSED */, void *edata) | |
| { | |
| // ensure the message is for ts_lua | |
| TSPluginMsg *const msgp = (TSPluginMsg *)edata; | |
| if (!ts::iequals(msgp->tag, TS_LUA_DEBUG_TAG)) { | |
| return TS_EVENT_NONE; | |
| } | |
| ts_lua_main_ctx *const main_ctx_array = (ts_lua_main_ctx *)TSContDataGet(contp); | |
| static char const *const remapstr = "remap"; | |
| static char const *const globalstr = "global"; | |
| char const *labelstr = nullptr; | |
| if (main_ctx_array == ts_lua_main_ctx_array) { | |
| labelstr = remapstr; | |
| } else { | |
| labelstr = globalstr; | |
| } | |
| char timebuf[128]; | |
| get_time_now_str(timebuf, 128); | |
| char const *const msgstr = (char *)msgp->data; | |
| enum State { Print, Reset } state; | |
| state = Print; | |
| size_t const reset_tag_len = strlen(reset_tag); | |
| if (reset_tag_len <= msgp->data_size && 0 == strncasecmp(reset_tag, msgstr, reset_tag_len)) { | |
| Dbg(dbg_ctl, "[%s] LIFECYCLE_MSG: %s", __FUNCTION__, reset_tag); | |
| state = Reset; | |
| fprintf(stderr, "[%s] %s (%s) resetting per state gc_kb_max and threads_max\n", timebuf, TS_LUA_DEBUG_TAG, labelstr); | |
| } else { | |
| Dbg(dbg_ctl, "[%s] LIFECYCLE_MSG: %s", __FUNCTION__, print_tag); | |
| } | |
| for (int index = 0; index < ts_lua_max_state_count; ++index) { | |
| ts_lua_main_ctx *const main_ctx = (main_ctx_array + index); | |
| if (nullptr != main_ctx) { | |
| ts_lua_ctx_stats *const stats = main_ctx->stats; | |
| if (nullptr != main_ctx) { | |
| TSMutexLock(stats->mutexp); | |
| switch (state) { | |
| case Reset: | |
| stats->threads_max = stats->threads; | |
| stats->gc_kb_max = stats->gc_kb; | |
| break; | |
| case Print: | |
| default: | |
| fprintf(stderr, "[%s] %s (%s) id: %3d gc_kb: %6d gc_kb_max: %6d threads: %4d threads_max: %4d\n", timebuf, | |
| TS_LUA_DEBUG_TAG, labelstr, index, stats->gc_kb, stats->gc_kb_max, stats->threads, stats->threads_max); | |
| break; | |
| } | |
| TSMutexUnlock(stats->mutexp); | |
| } | |
| } | |
| } | |
| return TS_EVENT_NONE; | |
| } | |
| TSReturnCode | |
| TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size) | |
| { | |
| if (!api_info || api_info->size < sizeof(TSRemapInterface)) { | |
| strncpy(errbuf, "[TSRemapInit] - Incorrect size of TSRemapInterface structure", errbuf_size - 1); | |
| errbuf[errbuf_size - 1] = '\0'; | |
| return TS_ERROR; | |
| } | |
| if (nullptr == ts_lua_main_ctx_array) { | |
| ts_lua_main_ctx_array = create_lua_vms(); | |
| if (nullptr != ts_lua_main_ctx_array) { | |
| pthread_key_create(&lua_state_key, nullptr); | |
| TSCont const lcontp = TSContCreate(lifecycleHandler, TSMutexCreate()); | |
| TSContDataSet(lcontp, ts_lua_main_ctx_array); | |
| TSLifecycleHookAdd(TS_LIFECYCLE_MSG_HOOK, lcontp); | |
| ts_lua_plugin_stats *const plugin_stats = create_plugin_stats(ts_lua_main_ctx_array, ts_lua_stat_strs); | |
| // start the stats management | |
| if (nullptr != plugin_stats) { | |
| Dbg(dbg_ctl, "Starting up stats management continuation"); | |
| TSCont const scontp = TSContCreate(statsHandler, TSMutexCreate()); | |
| TSContDataSet(scontp, plugin_stats); | |
| TSContScheduleOnPool(scontp, TS_LUA_STATS_TIMEOUT, TS_THREAD_POOL_TASK); | |
| } | |
| } else { | |
| return TS_ERROR; | |
| } | |
| } | |
| return TS_SUCCESS; | |
| } | |
| TSReturnCode | |
| TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuf, int errbuf_size) | |
| { | |
| int ret; | |
| char script[TS_LUA_MAX_SCRIPT_FNAME_LENGTH] = ""; | |
| char const *inline_script = ""; | |
| int fn = 0; | |
| int states = ts_lua_max_state_count; | |
| int ljgc = 0; | |
| int jit = 1; | |
| static const struct option longopt[] = { | |
| {"states", required_argument, 0, 's'}, | |
| {"jit", required_argument, 0, 'j'}, | |
| {"inline", required_argument, 0, 'i'}, | |
| {"ljgc", required_argument, 0, 'g'}, | |
| {0, 0, 0, 0 }, | |
| }; | |
| argc--; | |
| argv++; | |
| for (;;) { | |
| int opt; | |
| opt = getopt_long(argc, (char *const *)argv, "", longopt, nullptr); | |
| switch (opt) { | |
| case 's': | |
| states = atoi(optarg); | |
| Dbg(dbg_ctl, "[%s] setting number of lua VMs [%d]", __FUNCTION__, states); | |
| // set state | |
| break; | |
| case 'j': | |
| jit = atoi(optarg); | |
| if (jit == 0) { | |
| Dbg(dbg_ctl, "[%s] disable JIT mode for remap plugin", __FUNCTION__); | |
| for (int index = 0; index < ts_lua_max_state_count; ++index) { | |
| ts_lua_main_ctx *const main_ctx = (ts_lua_main_ctx_array + index); | |
| lua_State *const lstate = main_ctx->lua; | |
| if (luaJIT_setmode(lstate, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_OFF) == 0) { | |
| TSError("[ts_lua][%s] Failed to disable JIT mode for remap plugin", __FUNCTION__); | |
| } | |
| } | |
| } | |
| break; | |
| case 'i': | |
| inline_script = optarg; | |
| break; | |
| case 'g': | |
| ljgc = atoi(optarg); | |
| break; | |
| } | |
| if (opt == -1) { | |
| break; | |
| } | |
| } | |
| if (states < 1 || ts_lua_max_state_count < states) { | |
| snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - invalid state in option input. Must be between 1 and %d", | |
| ts_lua_max_state_count); | |
| return TS_ERROR; | |
| } | |
| if (argc - optind > 0) { | |
| fn = 1; | |
| if (argv[optind][0] == '/') { | |
| snprintf(script, sizeof(script), "%s", argv[optind]); | |
| } else { | |
| snprintf(script, sizeof(script), "%s/%s", TSConfigDirGet(), argv[optind]); | |
| } | |
| } | |
| if (strlen(inline_script) == 0 && argc - optind < 1) { | |
| strncpy(errbuf, "[TSRemapNewInstance] - lua script file or string is required !!", errbuf_size - 1); | |
| errbuf[errbuf_size - 1] = '\0'; | |
| return TS_ERROR; | |
| } | |
| if (strlen(script) >= TS_LUA_MAX_SCRIPT_FNAME_LENGTH - 16) { | |
| strncpy(errbuf, "[TSRemapNewInstance] - lua script file name too long !!", errbuf_size - 1); | |
| errbuf[errbuf_size - 1] = '\0'; | |
| return TS_ERROR; | |
| } | |
| ts_lua_instance_conf *conf = nullptr; | |
| // check to make sure it is a lua file and there is no parameter for the lua file | |
| if (fn && (argc - optind < 2)) { | |
| Dbg(dbg_ctl, "[%s] checking if script has been registered", __FUNCTION__); | |
| // we only need to check the first lua VM for script registration | |
| TSMutexLock(ts_lua_main_ctx_array[0].mutexp); | |
| conf = ts_lua_script_registered(ts_lua_main_ctx_array[0].lua, script); | |
| TSMutexUnlock(ts_lua_main_ctx_array[0].mutexp); | |
| } | |
| if (!conf) { | |
| Dbg(dbg_ctl, "[%s] creating new conf instance", __FUNCTION__); | |
| conf = TSRalloc<ts_lua_instance_conf>(); | |
| if (!conf) { | |
| strncpy(errbuf, "[TSRemapNewInstance] malloc failed!!", errbuf_size - 1); | |
| errbuf[errbuf_size - 1] = '\0'; | |
| return TS_ERROR; | |
| } | |
| memset(conf, 0, sizeof(ts_lua_instance_conf)); | |
| conf->states = states; | |
| conf->remap = 1; | |
| conf->init_func = 0; | |
| conf->ref_count = 1; | |
| conf->ljgc = ljgc; | |
| Dbg(dbg_ctl, "Reference Count = %d , creating new instance...", conf->ref_count); | |
| if (fn) { | |
| snprintf(conf->script, TS_LUA_MAX_SCRIPT_FNAME_LENGTH, "%s", script); | |
| } else { | |
| conf->content = inline_script; | |
| } | |
| ts_lua_init_instance(conf); | |
| ret = ts_lua_add_module(conf, ts_lua_main_ctx_array, conf->states, argc - optind, &argv[optind], errbuf, errbuf_size); | |
| if (ret != 0) { | |
| return TS_ERROR; | |
| } | |
| // register the script only if it is from a file and has no __init__ function | |
| if (fn && !conf->init_func) { | |
| // we only need to register the script for the first lua VM | |
| TSMutexLock(ts_lua_main_ctx_array[0].mutexp); | |
| ts_lua_script_register(ts_lua_main_ctx_array[0].lua, conf->script, conf); | |
| TSMutexUnlock(ts_lua_main_ctx_array[0].mutexp); | |
| } | |
| } else { | |
| conf->ref_count++; | |
| Dbg(dbg_ctl, "Reference Count = %d , reference existing instance...", conf->ref_count); | |
| } | |
| *ih = conf; | |
| return TS_SUCCESS; | |
| } | |
| void | |
| TSRemapDeleteInstance(void *ih) | |
| { | |
| // Once the shutdown barrier owns the Lua state mutexes it never gives them back, | |
| // so ts_lua_del_module() would block here for good, on the event thread that | |
| // still has the rest of ATS shutdown to run. ATS as it stands does not get here | |
| // during shutdown -- it leaks the remap configuration instead, deliberately, | |
| // because plugin teardown after shutdown is unsafe -- but a hung restart is a bad | |
| // enough outcome to guard against that changing. The process is exiting, so | |
| // leaving the instance alone costs nothing but a __clean__ call. | |
| if (shutdown_barrier_engaged.load(std::memory_order_acquire)) { | |
| Dbg(dbg_ctl, "shutdown barrier engaged, skipping remap instance teardown for '%s'", ((ts_lua_instance_conf *)ih)->script); | |
| return; | |
| } | |
| int states = ((ts_lua_instance_conf *)ih)->states; | |
| ts_lua_del_module((ts_lua_instance_conf *)ih, ts_lua_main_ctx_array, states); | |
| ts_lua_del_instance(static_cast<ts_lua_instance_conf *>(ih)); | |
| ((ts_lua_instance_conf *)ih)->ref_count--; | |
| if (((ts_lua_instance_conf *)ih)->ref_count == 0) { | |
| Dbg(dbg_ctl, "Reference Count = %d , freeing...", ((ts_lua_instance_conf *)ih)->ref_count); | |
| TSfree(ih); | |
| } else { | |
| Dbg(dbg_ctl, "Reference Count = %d , not freeing...", ((ts_lua_instance_conf *)ih)->ref_count); | |
| } | |
| return; | |
| } | |
| static TSRemapStatus | |
| ts_lua_remap_plugin_init(void *ih, TSHttpTxn rh, TSRemapRequestInfo *rri) | |
| { | |
| int ret; | |
| uint64_t req_id; | |
| TSCont contp; | |
| lua_State *L; | |
| ts_lua_main_ctx *main_ctx; | |
| ts_lua_http_ctx *http_ctx; | |
| ts_lua_cont_info *ci; | |
| ts_lua_instance_conf *instance_conf; | |
| int remap = (rri == nullptr ? 0 : 1); | |
| instance_conf = (ts_lua_instance_conf *)ih; | |
| main_ctx = static_cast<decltype(main_ctx)>(pthread_getspecific(lua_state_key)); | |
| if (main_ctx == nullptr || static_cast<int>(main_ctx - ts_lua_main_ctx_array) >= instance_conf->states) { | |
| req_id = __sync_fetch_and_add(&ts_lua_http_next_id, 1); | |
| main_ctx = &ts_lua_main_ctx_array[req_id % instance_conf->states]; | |
| pthread_setspecific(lua_state_key, main_ctx); | |
| } | |
| TSMutexLockGuard lock(main_ctx->mutexp); | |
| http_ctx = ts_lua_create_http_ctx(main_ctx, instance_conf); | |
| http_ctx->txnp = rh; | |
| http_ctx->has_hook = 0; | |
| http_ctx->from_remap = (rri != nullptr) ? 1 : 0; | |
| http_ctx->rri = rri; | |
| if (rri != nullptr) { | |
| http_ctx->client_request_bufp = rri->requestBufp; | |
| http_ctx->client_request_hdrp = rri->requestHdrp; | |
| http_ctx->client_request_url = rri->requestUrl; | |
| } | |
| ci = &http_ctx->cinfo; | |
| L = ci->routine.lua; | |
| contp = TSContCreate(ts_lua_http_cont_handler, nullptr); | |
| TSContDataSet(contp, http_ctx); | |
| ci->contp = contp; | |
| ci->mutex = TSContMutexGet((TSCont)rh); | |
| lua_getglobal(L, (remap ? TS_LUA_FUNCTION_REMAP : TS_LUA_FUNCTION_OS_RESPONSE)); | |
| if (lua_type(L, -1) != LUA_TFUNCTION) { | |
| lua_pop(L, 1); | |
| ts_lua_destroy_http_ctx(http_ctx); | |
| return TSREMAP_NO_REMAP; | |
| } | |
| ts_lua_set_cont_info(L, nullptr); | |
| if (lua_pcall(L, 0, 1, 0) != 0) { | |
| TSError("[ts_lua][%s] lua_pcall failed: %s", __FUNCTION__, lua_tostring(L, -1)); | |
| ret = TSREMAP_NO_REMAP; | |
| } else { | |
| ret = lua_tointeger(L, -1); | |
| } | |
| lua_pop(L, 1); | |
| // rri lives on the caller's stack; clear it so post-remap hooks | |
| // see ts.remap.* return nil per the documented do_remap-only | |
| // context. Destructors gate handle release on from_remap, not rri. | |
| http_ctx->rri = nullptr; | |
| if (http_ctx->has_hook) { | |
| Dbg(dbg_ctl, "[%s] has txn hook -> adding txn close hook handler to release resources", __FUNCTION__); | |
| TSHttpTxnHookAdd(rh, TS_HTTP_TXN_CLOSE_HOOK, contp); | |
| } else { | |
| Dbg(dbg_ctl, "[%s] no txn hook -> release resources now", __FUNCTION__); | |
| ts_lua_destroy_http_ctx(http_ctx); | |
| } | |
| return TSRemapStatus(ret); | |
| } | |
| void | |
| TSRemapOSResponse(void *ih, TSHttpTxn rh, int os_response_type) | |
| { | |
| Dbg(dbg_ctl, "[%s] os response function and type - %d", __FUNCTION__, os_response_type); | |
| ts_lua_remap_plugin_init(ih, rh, nullptr); | |
| } | |
| TSRemapStatus | |
| TSRemapDoRemap(void *ih, TSHttpTxn rh, TSRemapRequestInfo *rri) | |
| { | |
| Dbg(dbg_ctl, "[%s] remap function", __FUNCTION__); | |
| return ts_lua_remap_plugin_init(ih, rh, rri); | |
| } | |
| static int | |
| configHandler(TSCont contp, TSEvent event ATS_UNUSED, void *edata ATS_UNUSED) | |
| { | |
| Dbg(dbg_ctl, "[%s] calling configuration handler", __FUNCTION__); | |
| ts_lua_instance_conf *conf = (ts_lua_instance_conf *)TSContDataGet(contp); | |
| ts_lua_reload_module(conf, ts_lua_g_main_ctx_array, conf->states); | |
| return 0; | |
| } | |
| static int | |
| vconnHookHandler(TSCont contp, TSEvent event, void *edata) | |
| { | |
| TSVConn vconn = (TSVConn)edata; | |
| int ret; | |
| uint64_t req_id; | |
| lua_State *l; | |
| ts_lua_main_ctx *main_ctx; | |
| ts_lua_vconn_ctx *vconn_ctx; | |
| ts_lua_instance_conf *conf = (ts_lua_instance_conf *)TSContDataGet(contp); | |
| main_ctx = static_cast<decltype(main_ctx)>(pthread_getspecific(lua_g_state_key)); | |
| if (main_ctx == NULL || static_cast<int>(main_ctx - ts_lua_g_main_ctx_array) >= conf->states) { | |
| req_id = __sync_fetch_and_add(&ts_lua_g_http_next_id, 1); | |
| Dbg(dbg_ctl, "[%s] req_id for vconn handler: %" PRId64, __FUNCTION__, req_id); | |
| main_ctx = &ts_lua_g_main_ctx_array[req_id % conf->states]; | |
| pthread_setspecific(lua_g_state_key, main_ctx); | |
| } | |
| TSMutexLock(main_ctx->mutexp); | |
| vconn_ctx = ts_lua_create_vconn_ctx(main_ctx, conf); | |
| vconn_ctx->vconn = vconn; | |
| l = vconn_ctx->lua; | |
| switch (event) { | |
| case TS_EVENT_VCONN_START: | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_VCONN_START); | |
| break; | |
| default: | |
| ts_lua_destroy_vconn_ctx(vconn_ctx); | |
| TSMutexUnlock(main_ctx->mutexp); | |
| TSVConnReenable(vconn); | |
| return 0; | |
| } | |
| if (lua_type(l, -1) != LUA_TFUNCTION) { | |
| lua_pop(l, 1); | |
| ts_lua_destroy_vconn_ctx(vconn_ctx); | |
| TSMutexUnlock(main_ctx->mutexp); | |
| TSVConnReenable(vconn); | |
| return 0; | |
| } | |
| if (lua_pcall(l, 0, 1, 0) != 0) { | |
| TSError("[ts_lua][%s] lua_pcall failed: %s", __FUNCTION__, lua_tostring(l, -1)); | |
| } | |
| ret = lua_tointeger(l, -1); | |
| lua_pop(l, 1); | |
| ts_lua_destroy_vconn_ctx(vconn_ctx); | |
| TSMutexUnlock(main_ctx->mutexp); | |
| if (ret) { | |
| TSError("[ts_lua][%s] error returned", __FUNCTION__); | |
| } else { | |
| Dbg(dbg_ctl, "[%s] no error returned", __FUNCTION__); | |
| } | |
| TSVConnReenable(vconn); | |
| return 0; | |
| } | |
| static int | |
| globalHookHandler(TSCont contp, TSEvent event ATS_UNUSED, void *edata) | |
| { | |
| TSHttpTxn txnp = (TSHttpTxn)edata; | |
| TSMBuffer bufp; | |
| TSMLoc hdr_loc; | |
| TSMLoc url_loc; | |
| int ret; | |
| uint64_t req_id; | |
| TSCont txn_contp; | |
| lua_State *l; | |
| ts_lua_main_ctx *main_ctx; | |
| ts_lua_http_ctx *http_ctx; | |
| ts_lua_cont_info *ci; | |
| ts_lua_instance_conf *conf = (ts_lua_instance_conf *)TSContDataGet(contp); | |
| main_ctx = static_cast<decltype(main_ctx)>(pthread_getspecific(lua_g_state_key)); | |
| if (main_ctx == nullptr || static_cast<int>(main_ctx - ts_lua_g_main_ctx_array) >= conf->states) { | |
| req_id = __sync_fetch_and_add(&ts_lua_g_http_next_id, 1); | |
| Dbg(dbg_ctl, "[%s] req_id: %" PRId64, __FUNCTION__, req_id); | |
| main_ctx = &ts_lua_g_main_ctx_array[req_id % conf->states]; | |
| pthread_setspecific(lua_g_state_key, main_ctx); | |
| } | |
| TSMutexLock(main_ctx->mutexp); | |
| http_ctx = ts_lua_create_http_ctx(main_ctx, conf); | |
| http_ctx->txnp = txnp; | |
| http_ctx->rri = nullptr; | |
| http_ctx->has_hook = 0; | |
| if (!http_ctx->client_request_bufp) { | |
| if (TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc) == TS_SUCCESS) { | |
| http_ctx->client_request_bufp = bufp; | |
| http_ctx->client_request_hdrp = hdr_loc; | |
| if (TSHttpHdrUrlGet(bufp, hdr_loc, &url_loc) == TS_SUCCESS) { | |
| http_ctx->client_request_url = url_loc; | |
| } | |
| } | |
| } | |
| if (!http_ctx->client_request_hdrp) { | |
| ts_lua_destroy_http_ctx(http_ctx); | |
| TSMutexUnlock(main_ctx->mutexp); | |
| TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); | |
| return 0; | |
| } | |
| txn_contp = TSContCreate(ts_lua_http_cont_handler, nullptr); | |
| TSContDataSet(txn_contp, http_ctx); | |
| ci = &http_ctx->cinfo; | |
| ci->contp = txn_contp; | |
| ci->mutex = TSContMutexGet((TSCont)txnp); | |
| l = ci->routine.lua; | |
| switch (event) { | |
| case TS_EVENT_HTTP_READ_REQUEST_HDR: | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_READ_REQUEST); | |
| break; | |
| case TS_EVENT_HTTP_SEND_REQUEST_HDR: | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_SEND_REQUEST); | |
| break; | |
| case TS_EVENT_HTTP_READ_RESPONSE_HDR: | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_READ_RESPONSE); | |
| break; | |
| case TS_EVENT_HTTP_SEND_RESPONSE_HDR: | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_SEND_RESPONSE); | |
| break; | |
| case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE: | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_CACHE_LOOKUP_COMPLETE); | |
| break; | |
| case TS_EVENT_HTTP_TXN_START: | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_TXN_START); | |
| break; | |
| case TS_EVENT_HTTP_PRE_REMAP: | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_PRE_REMAP); | |
| break; | |
| case TS_EVENT_HTTP_POST_REMAP: | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_POST_REMAP); | |
| break; | |
| case TS_EVENT_HTTP_OS_DNS: | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_OS_DNS); | |
| break; | |
| case TS_EVENT_HTTP_READ_CACHE_HDR: | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_READ_CACHE); | |
| break; | |
| case TS_EVENT_HTTP_TXN_CLOSE: | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_TXN_CLOSE); | |
| break; | |
| default: | |
| ts_lua_destroy_http_ctx(http_ctx); | |
| TSMutexUnlock(main_ctx->mutexp); | |
| TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); | |
| return 0; | |
| } | |
| if (lua_type(l, -1) != LUA_TFUNCTION) { | |
| lua_pop(l, 1); | |
| ts_lua_destroy_http_ctx(http_ctx); | |
| TSMutexUnlock(main_ctx->mutexp); | |
| TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); | |
| return 0; | |
| } | |
| ts_lua_set_cont_info(l, nullptr); | |
| if (lua_pcall(l, 0, 1, 0) != 0) { | |
| TSError("[ts_lua][%s] lua_pcall failed: %s", __FUNCTION__, lua_tostring(l, -1)); | |
| } | |
| ret = lua_tointeger(l, -1); | |
| lua_pop(l, 1); | |
| ts_lua_clear_http_ctx(http_ctx); | |
| if (http_ctx->has_hook) { | |
| // add a hook to release resources for context | |
| Dbg(dbg_ctl, "[%s] has txn hook -> adding txn close hook handler to release resources", __FUNCTION__); | |
| TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, txn_contp); | |
| } else { | |
| Dbg(dbg_ctl, "[%s] no txn hook -> release resources now", __FUNCTION__); | |
| ts_lua_destroy_http_ctx(http_ctx); | |
| } | |
| TSMutexUnlock(main_ctx->mutexp); | |
| if (ret) { | |
| TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); | |
| } else { | |
| TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); | |
| } | |
| return 0; | |
| } | |
| // Longest time the shutdown barrier waits for the Lua states to go idle. A Lua | |
| // callback is expected to run for microseconds, so exceeding this means a script | |
| // is stuck rather than merely busy. | |
| constexpr std::chrono::milliseconds shutdown_barrier_timeout{5000}; | |
| constexpr std::chrono::milliseconds shutdown_barrier_poll{5}; | |
| // The scripts with a __shutdown__ function, in plugin.config order. A single | |
| // continuation runs all of them, so the barrier is taken exactly once. | |
| static std::vector<ts_lua_instance_conf *> shutdown_confs; | |
| static TSCont shutdown_contp = nullptr; | |
| static bool | |
| lockStates(ts_lua_main_ctx *const ctx_array, std::chrono::steady_clock::time_point const deadline, std::vector<TSMutex> &locked) | |
| { | |
| if (nullptr == ctx_array) { | |
| return true; | |
| } | |
| // Ascending index order, matching every other multi-state walk in the plugin. | |
| for (int index = 0; index < ts_lua_max_state_count; ++index) { | |
| TSMutex const mutexp = ctx_array[index].mutexp; | |
| while (TS_SUCCESS != TSMutexLockTry(mutexp)) { | |
| if (deadline <= std::chrono::steady_clock::now()) { | |
| return false; | |
| } | |
| std::this_thread::sleep_for(shutdown_barrier_poll); | |
| } | |
| locked.push_back(mutexp); | |
| } | |
| return true; | |
| } | |
| static void | |
| unlockStates(std::vector<TSMutex> &locked) | |
| { | |
| for (auto mutexp = locked.rbegin(); mutexp != locked.rend(); ++mutexp) { | |
| TSMutexUnlock(*mutexp); | |
| } | |
| locked.clear(); | |
| } | |
| // Invoke every __shutdown__ function with no Lua code running anywhere else in | |
| // the process. | |
| // | |
| // A __shutdown__ function typically releases process global resources, often in | |
| // a native library reached through FFI. Each Lua execution path locks only the | |
| // one main state it was assigned, so locking a single state is not enough: the | |
| // callback running in state 0 can tear those resources down while a request | |
| // callback is still using them in state 1. Holding every main state mutex is | |
| // what makes __shutdown__ exclusive with all Lua code. | |
| // | |
| // This runs on a thread of its own rather than on the event thread that | |
| // dispatched TS_LIFECYCLE_SHUTDOWN_HOOK because ProxyMutex is recursive per | |
| // thread: an event thread that holds these mutexes can still enter Lua itself, | |
| // and the dispatching event thread does exactly that when it returns to its | |
| // event loop for the last time. | |
| static void * | |
| runShutdownCallbacks(void * /* data ATS_UNUSED */) | |
| { | |
| auto const deadline = std::chrono::steady_clock::now() + shutdown_barrier_timeout; | |
| std::vector<TSMutex> global_states; | |
| std::vector<TSMutex> remap_states; | |
| global_states.reserve(ts_lua_max_state_count); | |
| remap_states.reserve(ts_lua_max_state_count); | |
| // The remap states are only in this image when a remap instance was loaded | |
| // without proxy.config.plugin.dynamic_reload_mode; with dynamic reload the | |
| // remap instance is a private copy of the plugin with states of its own, which | |
| // this barrier cannot reach. | |
| if (!lockStates(ts_lua_g_main_ctx_array, deadline, global_states) || !lockStates(ts_lua_main_ctx_array, deadline, remap_states)) { | |
| // A partial barrier excludes nothing that matters and stalls the states it | |
| // did lock, so give them all back. | |
| unlockStates(remap_states); | |
| unlockStates(global_states); | |
| TSError("[ts_lua][%s] Lua states still active after %lld ms, skipping %s", __FUNCTION__, | |
| static_cast<long long>(shutdown_barrier_timeout.count()), TS_LUA_FUNCTION_G_SHUT_DOWN); | |
| return nullptr; | |
| } | |
| Dbg(dbg_ctl, "[%s] shutdown barrier acquired for %zu Lua states", __FUNCTION__, global_states.size() + remap_states.size()); | |
| // Published while every state mutex is owned here, so TSRemapDeleteInstance | |
| // cannot see it change while it is entering Lua. | |
| shutdown_barrier_engaged.store(true, std::memory_order_release); | |
| for (ts_lua_instance_conf *const conf : shutdown_confs) { | |
| for (int index = 0; index < conf->states; ++index) { | |
| ts_lua_main_ctx *const main_ctx = &ts_lua_g_main_ctx_array[index]; | |
| lua_State *const L = main_ctx->lua; | |
| // Restore the conf-specific global table so lua_getglobal resolves | |
| // functions from the loaded script, matching ts_lua_reload_module. | |
| lua_pushlightuserdata(L, conf); | |
| lua_rawget(L, LUA_REGISTRYINDEX); | |
| lua_replace(L, LUA_GLOBALSINDEX); | |
| lua_getglobal(L, TS_LUA_FUNCTION_G_SHUT_DOWN); | |
| if (lua_type(L, -1) == LUA_TFUNCTION) { | |
| if (lua_pcall(L, 0, 0, 0) != 0) { | |
| TSError("[ts_lua][%s] lua_pcall failed for script '%s' state %d: %s", __FUNCTION__, conf->script, index, | |
| lua_tostring(L, -1)); | |
| lua_pop(L, 1); | |
| } | |
| } else { | |
| lua_pop(L, 1); | |
| } | |
| // Restore LUA_GLOBALSINDEX to an empty table, matching the resting state | |
| // established by ts_lua_add_module and ts_lua_reload_module. | |
| lua_newtable(L); | |
| lua_replace(L, LUA_GLOBALSINDEX); | |
| } | |
| } | |
| // Every state mutex is deliberately kept, by a thread that is about to exit. | |
| // TS_LIFECYCLE_SHUTDOWN_HOOK is terminal and no event thread is joined before | |
| // the process exits, so releasing any of them would only let a callback that is | |
| // already queued behind one enter Lua after __shutdown__ freed what that | |
| // callback uses. An event thread blocked on one of these mutexes does not keep | |
| // the process from exiting. TSRemapDeleteInstance, which ATS runs later in | |
| // shutdown and which would otherwise block on the remap mutexes, is what | |
| // shutdown_barrier_engaged is for. | |
| return nullptr; | |
| } | |
| static int | |
| shutdownHookHandler(TSCont /* contp ATS_UNUSED */, TSEvent /* event ATS_UNUSED */, void * /* edata ATS_UNUSED */) | |
| { | |
| TSThread const barrier = TSThreadCreate(runShutdownCallbacks, nullptr); | |
| if (nullptr == barrier) { | |
| TSError("[ts_lua][%s] could not create the shutdown barrier thread, skipping %s", __FUNCTION__, TS_LUA_FUNCTION_G_SHUT_DOWN); | |
| return 0; | |
| } | |
| // ATS continues shutting down as soon as this returns, so wait for the | |
| // callbacks. The thread itself is not destroyed: the mutexes it still holds | |
| // record it as their owner. | |
| TSThreadWait(barrier); | |
| return 0; | |
| } | |
| void | |
| TSPluginInit(int argc, const char *argv[]) | |
| { | |
| TSPluginRegistrationInfo info; | |
| info.plugin_name = "ts_lua"; | |
| info.vendor_name = "Apache Software Foundation"; | |
| info.support_email = "dev@trafficserver.apache.org"; | |
| if (TSPluginRegister(&info) != TS_SUCCESS) { | |
| TSError("[ts_lua][%s] Plugin registration failed", __FUNCTION__); | |
| } | |
| if (nullptr == ts_lua_g_main_ctx_array) { | |
| ts_lua_g_main_ctx_array = create_lua_vms(); | |
| if (nullptr != ts_lua_g_main_ctx_array) { | |
| pthread_key_create(&lua_g_state_key, nullptr); | |
| TSCont const contp = TSContCreate(lifecycleHandler, TSMutexCreate()); | |
| TSContDataSet(contp, ts_lua_g_main_ctx_array); | |
| TSLifecycleHookAdd(TS_LIFECYCLE_MSG_HOOK, contp); | |
| ts_lua_plugin_stats *const plugin_stats = create_plugin_stats(ts_lua_g_main_ctx_array, ts_lua_g_stat_strs); | |
| if (nullptr != plugin_stats) { | |
| TSCont const scontp = TSContCreate(statsHandler, TSMutexCreate()); | |
| TSContDataSet(scontp, plugin_stats); | |
| TSContScheduleOnPool(scontp, TS_LUA_STATS_TIMEOUT, TS_THREAD_POOL_TASK); | |
| } | |
| } else { | |
| return; | |
| } | |
| } | |
| int states = ts_lua_max_state_count; | |
| int jit = 1; | |
| int reload = 0; | |
| static const struct option longopt[] = { | |
| {"states", required_argument, 0, 's'}, | |
| {"jit", required_argument, 0, 'j'}, | |
| {"enable-reload", no_argument, 0, 'r'}, | |
| {0, 0, 0, 0 }, | |
| }; | |
| for (;;) { | |
| int opt; | |
| opt = getopt_long(argc, (char *const *)argv, "", longopt, nullptr); | |
| switch (opt) { | |
| case 's': | |
| states = atoi(optarg); | |
| // set state | |
| break; | |
| case 'j': | |
| jit = atoi(optarg); | |
| if (jit == 0) { | |
| Dbg(dbg_ctl, "[%s] disable JIT mode", __FUNCTION__); | |
| for (int index = 0; index < ts_lua_max_state_count; ++index) { | |
| ts_lua_main_ctx *const main_ctx = (ts_lua_g_main_ctx_array + index); | |
| lua_State *const lstate = main_ctx->lua; | |
| if (luaJIT_setmode(lstate, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_OFF) == 0) { | |
| TSError("[ts_lua][%s] Failed to disable JIT mode", __FUNCTION__); | |
| } | |
| } | |
| } | |
| break; | |
| case 'r': | |
| reload = 1; | |
| Dbg(dbg_ctl, "[%s] enable global plugin reload [%d]", __FUNCTION__, reload); | |
| break; | |
| } | |
| if (opt == -1) { | |
| break; | |
| } | |
| } | |
| if (states < 1 || ts_lua_max_state_count < states) { | |
| TSError("[ts_lua][%s] invalid # of states from option input. Must be between 1 and %d", __FUNCTION__, ts_lua_max_state_count); | |
| return; | |
| } | |
| if (argc - optind < 1) { | |
| TSError("[ts_lua][%s] lua script file required !!", __FUNCTION__); | |
| return; | |
| } | |
| if (strlen(argv[optind]) >= TS_LUA_MAX_SCRIPT_FNAME_LENGTH - 16) { | |
| TSError("[ts_lua][%s] lua script file name too long !!", __FUNCTION__); | |
| return; | |
| } | |
| auto *conf = TSRalloc<ts_lua_instance_conf>(); | |
| if (!conf) { | |
| TSError("[ts_lua][%s] malloc failed !!", __FUNCTION__); | |
| return; | |
| } | |
| memset(conf, 0, sizeof(ts_lua_instance_conf)); | |
| conf->remap = 0; | |
| conf->states = states; | |
| if (argv[optind][0] == '/') { | |
| snprintf(conf->script, TS_LUA_MAX_SCRIPT_FNAME_LENGTH, "%s", argv[optind]); | |
| } else { | |
| snprintf(conf->script, TS_LUA_MAX_SCRIPT_FNAME_LENGTH, "%s/%s", TSConfigDirGet(), argv[optind]); | |
| } | |
| ts_lua_init_instance(conf); | |
| char errbuf[TS_LUA_MAX_STR_LENGTH]; | |
| int const errbuf_len = sizeof(errbuf); | |
| int const ret = | |
| ts_lua_add_module(conf, ts_lua_g_main_ctx_array, conf->states, argc - optind, (char **)&argv[optind], errbuf, errbuf_len); | |
| if (ret != 0) { | |
| TSError("[ts_lua]%.*s", (int)errbuf_len, errbuf); | |
| TSEmergency("[ts_lua][%s] ts_lua_add_module failed", __FUNCTION__); | |
| return; | |
| } | |
| TSCont global_contp = TSContCreate(globalHookHandler, nullptr); | |
| if (!global_contp) { | |
| TSError("[ts_lua][%s] could not create transaction start continuation", __FUNCTION__); | |
| return; | |
| } | |
| TSContDataSet(global_contp, conf); | |
| // adding hook based on whether the lua global function exists. | |
| ts_lua_main_ctx *main_ctx = &ts_lua_g_main_ctx_array[0]; | |
| ts_lua_http_ctx *http_ctx = ts_lua_create_http_ctx(main_ctx, conf); | |
| lua_State *l = http_ctx->cinfo.routine.lua; | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_SEND_REQUEST); | |
| if (lua_type(l, -1) == LUA_TFUNCTION) { | |
| TSHttpHookAdd(TS_HTTP_SEND_REQUEST_HDR_HOOK, global_contp); | |
| Dbg(dbg_ctl, "send_request_hdr_hook added"); | |
| } | |
| lua_pop(l, 1); | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_READ_RESPONSE); | |
| if (lua_type(l, -1) == LUA_TFUNCTION) { | |
| TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, global_contp); | |
| Dbg(dbg_ctl, "read_response_hdr_hook added"); | |
| } | |
| lua_pop(l, 1); | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_SEND_RESPONSE); | |
| if (lua_type(l, -1) == LUA_TFUNCTION) { | |
| TSHttpHookAdd(TS_HTTP_SEND_RESPONSE_HDR_HOOK, global_contp); | |
| Dbg(dbg_ctl, "send_response_hdr_hook added"); | |
| } | |
| lua_pop(l, 1); | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_CACHE_LOOKUP_COMPLETE); | |
| if (lua_type(l, -1) == LUA_TFUNCTION) { | |
| TSHttpHookAdd(TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, global_contp); | |
| Dbg(dbg_ctl, "cache_lookup_complete_hook added"); | |
| } | |
| lua_pop(l, 1); | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_READ_REQUEST); | |
| if (lua_type(l, -1) == LUA_TFUNCTION) { | |
| TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, global_contp); | |
| Dbg(dbg_ctl, "read_request_hdr_hook added"); | |
| } | |
| lua_pop(l, 1); | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_TXN_START); | |
| if (lua_type(l, -1) == LUA_TFUNCTION) { | |
| TSHttpHookAdd(TS_HTTP_TXN_START_HOOK, global_contp); | |
| Dbg(dbg_ctl, "txn_start_hook added"); | |
| } | |
| lua_pop(l, 1); | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_PRE_REMAP); | |
| if (lua_type(l, -1) == LUA_TFUNCTION) { | |
| TSHttpHookAdd(TS_HTTP_PRE_REMAP_HOOK, global_contp); | |
| Dbg(dbg_ctl, "pre_remap_hook added"); | |
| } | |
| lua_pop(l, 1); | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_POST_REMAP); | |
| if (lua_type(l, -1) == LUA_TFUNCTION) { | |
| TSHttpHookAdd(TS_HTTP_POST_REMAP_HOOK, global_contp); | |
| Dbg(dbg_ctl, "post_remap_hook added"); | |
| } | |
| lua_pop(l, 1); | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_OS_DNS); | |
| if (lua_type(l, -1) == LUA_TFUNCTION) { | |
| TSHttpHookAdd(TS_HTTP_OS_DNS_HOOK, global_contp); | |
| Dbg(dbg_ctl, "os_dns_hook added"); | |
| } | |
| lua_pop(l, 1); | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_READ_CACHE); | |
| if (lua_type(l, -1) == LUA_TFUNCTION) { | |
| TSHttpHookAdd(TS_HTTP_READ_CACHE_HDR_HOOK, global_contp); | |
| Dbg(dbg_ctl, "read_cache_hdr_hook added"); | |
| } | |
| lua_pop(l, 1); | |
| lua_getglobal(l, TS_LUA_FUNCTION_G_TXN_CLOSE); | |
| if (lua_type(l, -1) == LUA_TFUNCTION) { | |
| TSHttpHookAdd(TS_HTTP_TXN_CLOSE_HOOK, global_contp); | |
| Dbg(dbg_ctl, "txn_close_hook added"); | |
| } | |
| lua_pop(l, 1); | |
| ts_lua_destroy_http_ctx(http_ctx); | |
| TSCont vconn_contp = TSContCreate(vconnHookHandler, nullptr); | |
| if (!vconn_contp) { | |
| TSError("[ts_lua][%s] could not create vconn continuation", __FUNCTION__); | |
| return; | |
| } | |
| TSContDataSet(vconn_contp, conf); | |
| // adding hook based on whether the lua global vconn function exists | |
| ts_lua_vconn_ctx *vconn_ctx = ts_lua_create_vconn_ctx(main_ctx, conf); | |
| lua_State *vl = vconn_ctx->lua; | |
| lua_getglobal(vl, TS_LUA_FUNCTION_G_VCONN_START); | |
| if (lua_type(vl, -1) == LUA_TFUNCTION) { | |
| TSHttpHookAdd(TS_VCONN_START_HOOK, vconn_contp); | |
| Dbg(dbg_ctl, "vconn_start_hook added"); | |
| } | |
| lua_pop(vl, 1); | |
| ts_lua_destroy_vconn_ctx(vconn_ctx); | |
| // adding shutdown hook if the lua global shutdown function exists | |
| ts_lua_main_ctx *shutdown_main_ctx = &ts_lua_g_main_ctx_array[0]; | |
| ts_lua_http_ctx *shutdown_http_ctx = ts_lua_create_http_ctx(shutdown_main_ctx, conf); | |
| lua_State *sl = shutdown_http_ctx->cinfo.routine.lua; | |
| lua_getglobal(sl, TS_LUA_FUNCTION_G_SHUT_DOWN); | |
| if (lua_type(sl, -1) == LUA_TFUNCTION) { | |
| shutdown_confs.push_back(conf); | |
| // One continuation invokes every script's __shutdown__, so that the shutdown | |
| // barrier is taken once for the whole process. | |
| if (nullptr == shutdown_contp) { | |
| TSMutex shutdown_mutex = TSMutexCreate(); | |
| shutdown_contp = TSContCreate(shutdownHookHandler, shutdown_mutex); | |
| if (!shutdown_contp) { | |
| TSError("[ts_lua][%s] could not create shutdown continuation", __FUNCTION__); | |
| if (shutdown_mutex) { | |
| TSMutexDestroy(shutdown_mutex); | |
| } | |
| } else { | |
| TSLifecycleHookAdd(TS_LIFECYCLE_SHUTDOWN_HOOK, shutdown_contp); | |
| Dbg(dbg_ctl, "shutdown_hook added"); | |
| } | |
| } | |
| } | |
| lua_pop(sl, 1); | |
| ts_lua_destroy_http_ctx(shutdown_http_ctx); | |
| // support for reload as global plugin | |
| if (reload) { | |
| TSCont config_contp = TSContCreate(configHandler, nullptr); | |
| if (!config_contp) { | |
| TSError("[ts_lua][%s] could not create configuration continuation", __FUNCTION__); | |
| return; | |
| } | |
| TSContDataSet(config_contp, conf); | |
| TSMgmtUpdateRegister(config_contp, "ts_lua"); | |
| } | |
| } |