int ts_http_fetcher_callback_sm(http_fetcher *fch, TSEvent event) { if (fch->deleted && !fch->ref) { ts_http_fetcher_release(fch); return -1; } if (event == TS_FETCH_EVENT_BODY_QUIET) return 0; fch->ref++; if (fch->flags & TS_FETCH_FLAG_USE_NEW_LOCK) TSMutexLock(TSContMutexGet(fch->contp)); TSContCall(fch->contp, event, fch); if (fch->flags & TS_FETCH_FLAG_USE_NEW_LOCK) TSMutexUnlock(TSContMutexGet(fch->contp)); fch->ref--; if (fch->deleted && !fch->ref) { ts_http_fetcher_release(fch); return -1; } return 0; }
http_fetcher * ts_http_fetcher_create(TSCont contp, struct sockaddr *addr, int flags) { http_fetcher *fch; fch = (http_fetcher*)TSmalloc(sizeof(http_fetcher)); fch->flags = flags; fch->contp = contp; fch->aip = *addr; if (flags & TS_FLAG_FETCH_USE_NEW_LOCK) { fch->mutexp = TSMutexCreate(); } else { fch->mutexp = TSContMutexGet(contp); } fch->req_buffer = TSIOBufferCreate(); fch->req_reader = TSIOBufferReaderAlloc(fch->req_buffer); /* initialize */ fch->http_vc = NULL; fch->read_vio = NULL; fch->write_vio = NULL; fch->method = 0; fch->http_parser = NULL; fch->hdr_bufp = NULL; fch->hdr_loc = NULL; fch->hdr_buffer = NULL; fch->hdr_reader = NULL; fch->resp_buffer = NULL; fch->resp_reader = NULL; fch->body_buffer = NULL; fch->body_reader = NULL; fch->flow_buffer = NULL; fch->flow_reader = NULL; fch->fetch_contp = NULL; fch->action = NULL; fch->resp_cl = -1; fch->resp_already = 0; fch->post_cl = 0; fch->post_already = 0; fch->ctx = NULL; fch->status_code = 0; fch->ref = 0; fch->header_done = 0; fch->body_done = 0; fch->deleted = 0; fch->chunked = 0; fch->error = 0; fch->launched = 0; fch->stopped = 0; return fch; }
TSRemapStatus TSRemapDoRemap(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; instance_conf = (ts_lua_instance_conf*)ih; req_id = __sync_fetch_and_add(&ts_lua_http_next_id, 1); main_ctx = &ts_lua_main_ctx_array[req_id%TS_LUA_MAX_STATE_COUNT]; TSMutexLock(main_ctx->mutexp); http_ctx = ts_lua_create_http_ctx(main_ctx, instance_conf); http_ctx->txnp = rh; http_ctx->client_request_bufp = rri->requestBufp; http_ctx->client_request_hdrp = rri->requestHdrp; http_ctx->client_request_url = rri->requestUrl; http_ctx->rri = rri; ci = &http_ctx->cinfo; L = ci->routine.lua; contp = TSContCreate(ts_lua_http_cont_handler, NULL); TSContDataSet(contp, http_ctx); ci->contp = contp; ci->mutex = TSContMutexGet((TSCont)rh); // push do_remap function on the stack, and no async operation should exist here. lua_getglobal(L, TS_LUA_FUNCTION_REMAP); if (lua_pcall(L, 0, 1, 0) != 0) { ee("lua_pcall failed: %s", lua_tostring(L, -1)); ret = TSREMAP_NO_REMAP; } else { ret = lua_tointeger(L, -1); } lua_pop(L, 1); // pop the result if (http_ctx->hooks > 0) { TSMutexUnlock(main_ctx->mutexp); TSHttpTxnHookAdd(rh, TS_HTTP_TXN_CLOSE_HOOK, contp); } else { ts_lua_destroy_http_ctx(http_ctx); TSMutexUnlock(main_ctx->mutexp); } return ret; }
int ts_http_fetcher_callback_sm(http_fetcher *fch, TSEvent event) { if (fch->deleted && !fch->ref) { ts_http_fetcher_release(fch); return -1; } if (event == TS_EVENT_FETCH_BODY_QUIET || fch->stopped) { return 0; } else if (event != TS_EVENT_FETCH_HEADER_DONE && event != TS_EVENT_FETCH_BODY_READY && event != TS_EVENT_FETCH_BODY_QUIET) { fch->stopped = 1; } fch->ref++; if (fch->flags & TS_FLAG_FETCH_USE_NEW_LOCK) TSMutexLock(TSContMutexGet(fch->contp)); TSContCall(fch->contp, event, fch); if (fch->flags & TS_FLAG_FETCH_USE_NEW_LOCK) TSMutexUnlock(TSContMutexGet(fch->contp)); fch->ref--; if (fch->deleted && !fch->ref) { ts_http_fetcher_release(fch); return -1; } return 0; }
static int config_handler(TSCont cont, TSEvent event, void *edata) { plugin_state_t *pstate; invalidate_t *i, *iptr; TSCont free_cont; bool updated; TSMutex mutex; mutex = TSContMutexGet(cont); TSMutexLock(mutex); TSDebug(LOG_PREFIX, "In config Handler"); pstate = (plugin_state_t *)TSContDataGet(cont); i = copy_config(pstate->invalidate_list); updated = prune_config(&i); updated = load_config(pstate, &i) || updated; if (updated) { list_config(pstate, i); iptr = __sync_val_compare_and_swap(&(pstate->invalidate_list), pstate->invalidate_list, i); if (iptr) { free_cont = TSContCreate(free_handler, TSMutexCreate()); TSContDataSet(free_cont, (void *)iptr); TSContScheduleOnPool(free_cont, FREE_TMOUT, TS_THREAD_POOL_TASK); } } else { TSDebug(LOG_PREFIX, "No Changes"); if (i) { free_invalidate_t_list(i); } } TSMutexUnlock(mutex); // Don't reschedule for TS_EVENT_MGMT_UPDATE if (event == TS_EVENT_TIMEOUT) { TSContScheduleOnPool(cont, CONFIG_TMOUT, TS_THREAD_POOL_TASK); } return 0; }
static int ts_lua_sleep(lua_State * L) { int sec; TSAction action; TSCont contp; ts_lua_http_intercept_item *node; ts_lua_http_intercept_ctx *ictx; ictx = ts_lua_get_http_intercept_ctx(L); sec = luaL_checknumber(L, 1); contp = TSContCreate(ts_lua_sleep_handler, TSContMutexGet(ictx->contp)); action = TSContSchedule(contp, sec * 1000, TS_THREAD_POOL_DEFAULT); node = (ts_lua_http_intercept_item *) TSmalloc(sizeof(ts_lua_http_intercept_item)); TS_LUA_ADD_INTERCEPT_ITEM(ictx, node, contp, ts_lua_sleep_cleanup, action); TSContDataSet(contp, node); return lua_yield(L, 0); }
/*------------------------------------------------------------------------- transform_handler Handler for all events received during the transformation process Input: contp continuation for the current transaction event event received data pointer on optional data Output : Return Value: -------------------------------------------------------------------------*/ static int transform_handler(TSCont contp, TSEvent event, void *edata) { TSVIO input_vio; ContData *data; int state, retval; /* This section will be called by both TS internal and the thread. Protect it with a mutex to avoid concurrent calls. */ /* Handle TryLock result */ if (TSMutexLockTry(TSContMutexGet(contp)) != TS_SUCCESS) { TSCont c = TSContCreate(trylock_handler, NULL); TryLockData *d = TSmalloc(sizeof(TryLockData)); d->contp = contp; d->event = event; TSContDataSet(c, d); TSContSchedule(c, 10, TS_THREAD_POOL_DEFAULT); return 1; } data = TSContDataGet(contp); TSAssert(data->magic == MAGIC_ALIVE); state = data->state; /* Check to see if the transformation has been closed */ retval = TSVConnClosedGet(contp); if (retval) { /* If the thread is still executing its job, we don't want to destroy the continuation right away as the thread will call us back on this continuation. */ if (state == STATE_READ_PSI) { TSContSchedule(contp, 10, TS_THREAD_POOL_DEFAULT); } else { TSMutexUnlock(TSContMutexGet(contp)); cont_data_destroy(TSContDataGet(contp)); TSContDestroy(contp); return 1; } } else { switch (event) { case TS_EVENT_ERROR: input_vio = TSVConnWriteVIOGet(contp); TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio); break; case TS_EVENT_VCONN_WRITE_COMPLETE: TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1); break; case TS_EVENT_VCONN_WRITE_READY: /* downstream vconnection is done reading data we've write into it. let's read some more data from upstream if we're in read state. */ if (state == STATE_READ_DATA) { handle_transform(contp); } break; case TS_EVENT_IMMEDIATE: if (state == STATE_READ_DATA) { /* upstream vconnection signals some more data ready to be read let's try to transform some more data */ handle_transform(contp); } else if (state == STATE_DUMP_PSI) { /* The thread scheduled an event on our continuation to let us know it has completed its job Let's dump the include content to the output vconnection */ dump_psi(contp); wake_up_streams(contp); } break; default: TSAssert(!"Unexpected event"); break; } } TSMutexUnlock(TSContMutexGet(contp)); return 1; }
/*------------------------------------------------------------------------- psi_include Read file to include. Copy its content into an iobuffer. This is the function doing blocking calls and called by the plugin's threads Input: data continuation for the current transaction Output : data->psi_buffer contains the file content data->psi_sucess 0 if include failed, 1 if success Return Value: 0 if failure 1 if success -------------------------------------------------------------------------*/ static int psi_include(TSCont contp, void *edata) { #define BUFFER_SIZE 1024 ContData *data; TSFile filep; char buf[BUFFER_SIZE]; char inc_file[PSI_PATH_MAX_SIZE + PSI_FILENAME_MAX_SIZE]; /* We manipulate plugin continuation data from a separate thread. Grab mutex to avoid concurrent access */ TSMutexLock(TSContMutexGet(contp)); data = TSContDataGet(contp); TSAssert(data->magic == MAGIC_ALIVE); if (!data->psi_buffer) { data->psi_buffer = TSIOBufferCreate(); data->psi_reader = TSIOBufferReaderAlloc(data->psi_buffer); } /* For security reason, we do not allow to include files that are not in the directory <plugin_path>/include. Also include file cannot contain any path. */ sprintf(inc_file, "%s/%s", psi_directory, _basename(data->psi_filename)); /* Read the include file and copy content into iobuffer */ if ((filep = TSfopen(inc_file, "r")) != NULL) { TSDebug(DBG_TAG, "Reading include file %s", inc_file); while (TSfgets(filep, buf, BUFFER_SIZE) != NULL) { TSIOBufferBlock block; int64_t len, avail, ndone, ntodo, towrite; char *ptr_block; len = strlen(buf); ndone = 0; ntodo = len; while (ntodo > 0) { /* TSIOBufferStart allocates more blocks if required */ block = TSIOBufferStart(data->psi_buffer); ptr_block = TSIOBufferBlockWriteStart(block, &avail); towrite = MIN(ntodo, avail); memcpy(ptr_block, buf + ndone, towrite); TSIOBufferProduce(data->psi_buffer, towrite); ntodo -= towrite; ndone += towrite; } } TSfclose(filep); data->psi_success = 1; if (log) { TSTextLogObjectWrite(log, "Successfully included file: %s", inc_file); } } else { data->psi_success = 0; if (log) { TSTextLogObjectWrite(log, "Failed to include file: %s", inc_file); } } /* Change state and schedule an event EVENT_IMMEDIATE on the plugin continuation to let it know we're done. */ /* Note: if the blocking call was not in the transformation state (i.e. in TS_HTTP_READ_REQUEST_HDR, TS_HTTP_OS_DNS and so on...) we could use TSHttpTxnReenable to wake up the transaction instead of sending an event. */ TSContSchedule(contp, 0, TS_THREAD_POOL_DEFAULT); data->psi_success = 0; data->state = STATE_READ_DATA; TSMutexUnlock(TSContMutexGet(contp)); return 0; }
static void ironbee_plugin_txn_start(TSCont contp, TSHttpTxn txnp) { assert(contp != NULL); assert(txnp != NULL); /* start of Request */ /* First req on a connection, we set up conn stuff */ ib_status_t rc; ib_engine_t *ib = NULL; TSCont mycont; tsib_ssn_ctx *ssndata; tsib_txn_ctx *txndata; ssndata = TSContDataGet(contp); if (ssndata->iconn == NULL) { rc = tsib_manager_engine_acquire(&ib); if (rc == IB_DECLINED) { /* OK, this means the manager is disabled deliberately, * but otherwise all's well. So this TXN * gets processed without intervention from Ironbee * and is invisble when our SSN_CLOSE hook runs. */ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); TSDebug("ironbee", "Decline from engine manager"); return; } else if (rc != IB_OK) { TSError("[ironbee] Failed to acquire engine: %s", ib_status_to_string(rc)); goto noib_error; } if (ib != NULL) { rc = ib_conn_create(ib, &ssndata->iconn, contp); if (rc != IB_OK) { TSError("[ironbee] ib_conn_create: %s", ib_status_to_string(rc)); tsib_manager_engine_release(ib); goto noib_error; } /* In the normal case, release the engine when the * connection's memory pool is destroyed */ rc = ib_mm_register_cleanup(ssndata->iconn->mm, cleanup_ib_connection, ib); if (rc != IB_OK) { TSError("[ironbee] ib_mm_register_cleanup: %s", ib_status_to_string(rc)); tsib_manager_engine_release(ib); goto noib_error; } TSDebug("ironbee", "CONN CREATE: conn=%p", ssndata->iconn); ssndata->txnp = txnp; ssndata->txn_count = ssndata->closing = 0; rc = ironbee_conn_init(ssndata); if (rc != IB_OK) { TSError("[ironbee] ironbee_conn_init: %s", ib_status_to_string(rc)); goto noib_error; } TSContDataSet(contp, ssndata); TSDebug("ironbee", "ironbee_plugin: ib_state_notify_conn_opened()"); rc = ib_state_notify_conn_opened(ib, ssndata->iconn); if (rc != IB_OK) { TSError("[ironbee] Failed to notify connection opened: %s", ib_status_to_string(rc)); } } else { /* Use TSError where there's no ib or tx */ TSError("Ironbee: No ironbee engine!"); goto noib_error; } } /* create a txn cont (request ctx) and tx */ txndata = TSmalloc(sizeof(*txndata)); memset(txndata, 0, sizeof(*txndata)); txndata->ssn = ssndata; txndata->txnp = txnp; rc = ib_tx_create(&txndata->tx, ssndata->iconn, txndata); if (rc != IB_OK) { TSError("[ironbee] Failed to create tx: %d", rc); tsib_manager_engine_release(ib); TSfree(txndata); goto noib_error; } ++ssndata->txn_count; ib_log_debug_tx(txndata->tx, "TX CREATE: conn=%p tx=%p id=%s txn_count=%d", ssndata->iconn, txndata->tx, txndata->tx->id, txndata->ssn->txn_count); mycont = TSContCreate(ironbee_plugin, TSContMutexGet(contp)); TSContDataSet(mycont, txndata); TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, mycont); /* Hook to process responses */ TSHttpTxnHookAdd(txnp, TS_HTTP_READ_RESPONSE_HDR_HOOK, mycont); /* Hook to process requests */ TSHttpTxnHookAdd(txnp, TS_HTTP_PRE_REMAP_HOOK, mycont); /* Hook to process request headers when sent to the server. */ TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_REQUEST_HDR_HOOK, mycont); /* Create continuations for input and output filtering * to give them txn lifetime. */ txndata->in_data_cont = TSTransformCreate(in_data_event, txnp); TSContDataSet(txndata->in_data_cont, txndata); txndata->out_data_cont = TSTransformCreate(out_data_event, txnp); TSContDataSet(txndata->out_data_cont, txndata); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); return; noib_error: /* NULL txndata signals this to SEND_RESPONSE */ TSContDataSet(contp, NULL); TSError("[ironbee] Internal error initialising for transaction"); TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp); /* FIXME: check this. * Purpose is to ensure contp doesn't leak, but may not be right */ TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, contp); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); return; }
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); req_id = __sync_fetch_and_add(&ts_lua_g_http_next_id, 1); main_ctx = &ts_lua_g_main_ctx_array[req_id % TS_LUA_MAX_STATE_COUNT]; TSDebug(TS_LUA_DEBUG_TAG, "[%s] req_id: %" PRId64, __FUNCTION__, req_id); TSMutexLock(main_ctx->mutexp); http_ctx = ts_lua_create_http_ctx(main_ctx, conf); http_ctx->txnp = txnp; http_ctx->rri = NULL; 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, NULL); 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: // client response can be changed within a transaction // (e.g. due to the follow redirect feature). So, clearing the pointers // to allow API(s) to fetch the pointers again when it re-enters the hook if (http_ctx->client_response_hdrp != NULL) { TSHandleMLocRelease(http_ctx->client_response_bufp, TS_NULL_MLOC, http_ctx->client_response_hdrp); http_ctx->client_response_hdrp = NULL; } 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_SELECT_ALT: lua_getglobal(l, TS_LUA_FUNCTION_G_SELECT_ALT); 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, NULL); if (lua_pcall(l, 0, 1, 0) != 0) { TSError("[ts_lua] lua_pcall failed: %s", lua_tostring(l, -1)); } ret = lua_tointeger(l, -1); lua_pop(l, 1); if (http_ctx->has_hook) { // add a hook to release resources for context TSDebug(TS_LUA_DEBUG_TAG, "[%s] has txn hook -> adding txn close hook handler to release resources", __FUNCTION__); TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, txn_contp); } else { TSDebug(TS_LUA_DEBUG_TAG, "[%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; }
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 == NULL ? 0 : 1); instance_conf = (ts_lua_instance_conf *)ih; req_id = __sync_fetch_and_add(&ts_lua_http_next_id, 1); main_ctx = &ts_lua_main_ctx_array[req_id % TS_LUA_MAX_STATE_COUNT]; TSMutexLock(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->rri = rri; if (rri != NULL) { 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, NULL); 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) { TSMutexUnlock(main_ctx->mutexp); return TSREMAP_NO_REMAP; } ts_lua_set_cont_info(L, NULL); if (lua_pcall(L, 0, 1, 0) != 0) { TSError("[ts_lua] lua_pcall failed: %s", lua_tostring(L, -1)); ret = TSREMAP_NO_REMAP; } else { ret = lua_tointeger(L, -1); } lua_pop(L, 1); if (http_ctx->has_hook) { TSDebug(TS_LUA_DEBUG_TAG, "[%s] has txn hook -> adding txn close hook handler to release resources", __FUNCTION__); TSHttpTxnHookAdd(rh, TS_HTTP_TXN_CLOSE_HOOK, contp); } else { TSDebug(TS_LUA_DEBUG_TAG, "[%s] no txn hook -> release resources now", __FUNCTION__); ts_lua_destroy_http_ctx(http_ctx); } TSMutexUnlock(main_ctx->mutexp); return ret; }