From 481bdffad521f932f5668a0f87d4a05d39e4fd62 Mon Sep 17 00:00:00 2001 From: Kit Chan Date: Tue, 31 Mar 2026 09:49:50 -0700 Subject: [PATCH 1/7] Add shutdown hook function for lua plugin --- doc/admin-guide/plugins/lua.en.rst | 11 ++++ plugins/lua/ts_lua.cc | 50 +++++++++++++- plugins/lua/ts_lua_common.h | 3 + .../pluginTest/lua/global_shutdown.lua | 23 +++++++ .../lua/lua_global_shutdown.test.py | 65 +++++++++++++++++++ 5 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 tests/gold_tests/pluginTest/lua/global_shutdown.lua create mode 100644 tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py diff --git a/doc/admin-guide/plugins/lua.en.rst b/doc/admin-guide/plugins/lua.en.rst index d62a2f4e435..0362c22bd01 100644 --- a/doc/admin-guide/plugins/lua.en.rst +++ b/doc/admin-guide/plugins/lua.en.rst @@ -125,6 +125,17 @@ each lua script: - **'do_global_send_response'** - **'do_global_cache_lookup_complete'** - **'do_global_read_cache'** +- **'do_global_shut_down'** + +The ``do_global_shut_down`` function is invoked once per Lua state when |ATS| is +shutting down. It can be used to perform cleanup tasks such as flushing state or +releasing resources. It takes no arguments and its return value is ignored. + +Example:: + + function do_global_shut_down() + ts.debug('ATS shutting down, cleaning up resources') + end We can write this in plugin.config: diff --git a/plugins/lua/ts_lua.cc b/plugins/lua/ts_lua.cc index 741da1893f7..9bb934ab39e 100644 --- a/plugins/lua/ts_lua.cc +++ b/plugins/lua/ts_lua.cc @@ -827,6 +827,34 @@ globalHookHandler(TSCont contp, TSEvent event ATS_UNUSED, void *edata) return 0; } +static int +shutdownHookHandler(TSCont contp, TSEvent /* event ATS_UNUSED */, void * /* edata ATS_UNUSED */) +{ + ts_lua_instance_conf *const conf = (ts_lua_instance_conf *)TSContDataGet(contp); + + for (int index = 0; index < conf->states; ++index) { + ts_lua_main_ctx *const main_ctx = &ts_lua_g_main_ctx_array[index]; + + TSMutexLock(main_ctx->mutexp); + + lua_State *const l = main_ctx->lua; + + 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: %s", __FUNCTION__, lua_tostring(l, -1)); + lua_pop(l, 1); + } + } else { + lua_pop(l, 1); + } + + TSMutexUnlock(main_ctx->mutexp); + } + + return 0; +} + void TSPluginInit(int argc, const char *argv[]) { @@ -1046,7 +1074,7 @@ TSPluginInit(int argc, const char *argv[]) } TSContDataSet(vconn_contp, conf); - // adding hook based on whther the lua global vconn function exists + // 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; @@ -1059,6 +1087,26 @@ TSPluginInit(int argc, const char *argv[]) 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) { + TSCont shutdown_contp = TSContCreate(shutdownHookHandler, TSMutexCreate()); + if (!shutdown_contp) { + TSError("[ts_lua][%s] could not create shutdown continuation", __FUNCTION__); + } else { + TSContDataSet(shutdown_contp, conf); + 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); diff --git a/plugins/lua/ts_lua_common.h b/plugins/lua/ts_lua_common.h index 17e554893f5..605d2316e8a 100644 --- a/plugins/lua/ts_lua_common.h +++ b/plugins/lua/ts_lua_common.h @@ -62,6 +62,9 @@ extern "C" { // TLS hooks can only be global #define TS_LUA_FUNCTION_G_VCONN_START "do_global_vconn_start" +// Lifecycle hooks +#define TS_LUA_FUNCTION_G_SHUT_DOWN "do_global_shut_down" + #define TS_LUA_DEBUG_TAG "ts_lua" #define TS_LUA_EVENT_COROUTINE_CONT 20000 diff --git a/tests/gold_tests/pluginTest/lua/global_shutdown.lua b/tests/gold_tests/pluginTest/lua/global_shutdown.lua new file mode 100644 index 00000000000..5a5f5fa6d6a --- /dev/null +++ b/tests/gold_tests/pluginTest/lua/global_shutdown.lua @@ -0,0 +1,23 @@ +-- 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. + +function do_global_read_request() + ts.debug('do_global_read_request called') +end + +function do_global_shut_down() + ts.debug('do_global_shut_down called') +end diff --git a/tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py b/tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py new file mode 100644 index 00000000000..6fde567fbf4 --- /dev/null +++ b/tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py @@ -0,0 +1,65 @@ +''' +Test do_global_shut_down lua global plugin hook. +''' +# 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. + +Test.Summary = ''' +Test do_global_shut_down lua global plugin hook +''' + +Test.SkipUnless(Condition.PluginExists('tslua.so'),) + +Test.ContinueOnFail = True + +server = Test.MakeOriginServer("server") +ts = Test.MakeATSProcess("ts") + +Test.Setup.Copy("global_shutdown.lua") + +request_header = {"headers": "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} +response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""} +server.addResponse("sessionfile.log", request_header, response_header) + +ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{}/'.format(server.Variables.Port)) + +# Use 2 states so the shutdown handler is called a predictable number of times. +ts.Disk.plugin_config.AddLine('tslua.so --states=2 {}/global_shutdown.lua'.format(Test.RunDirectory)) + +ts.Disk.records_config.update({ + 'proxy.config.diags.debug.enabled': 1, + 'proxy.config.diags.debug.tags': 'ts_lua', +}) + +curl_and_args = '-s -D /dev/stdout -o /dev/stderr -x localhost:{} '.format(ts.Variables.port) + +# 0 Test - Send a request to confirm the global plugin is active. +tr = Test.AddTestRun("Lua global read request hook fires for HTTP requests") +ps = tr.Processes.Default +ps.StartBefore(server, ready=When.PortOpen(server.Variables.Port)) +ps.StartBefore(Test.Processes.ts) +tr.MakeCurlCommand(curl_and_args + 'http://www.example.com/', ts=ts) +ps.ReturnCode = 0 +tr.StillRunningAfter = ts + +# Verify do_global_read_request was invoked for the HTTP request above. +ts.Disk.traffic_out.Content = Testers.ContainsExpression( + r'do_global_read_request called', 'do_global_read_request should be called for HTTP requests') + +# After all test runs complete AuTest stops ATS, which fires TS_LIFECYCLE_SHUTDOWN_HOOK. +# The shutdown handler calls do_global_shut_down once per Lua state (2 states configured). +ts.Disk.traffic_out.Content += Testers.ContainsExpression( + r'do_global_shut_down called', 'do_global_shut_down should be called on ATS shutdown') From b5593b66a9baabe34985dfdd6a62364d6c70343c Mon Sep 17 00:00:00 2001 From: Kit Chan Date: Tue, 31 Mar 2026 14:45:20 -0700 Subject: [PATCH 2/7] Update lua function name --- doc/admin-guide/plugins/lua.en.rst | 6 +++--- plugins/lua/ts_lua_common.h | 2 +- tests/gold_tests/pluginTest/lua/global_shutdown.lua | 4 ++-- tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/admin-guide/plugins/lua.en.rst b/doc/admin-guide/plugins/lua.en.rst index 0362c22bd01..4717226bb80 100644 --- a/doc/admin-guide/plugins/lua.en.rst +++ b/doc/admin-guide/plugins/lua.en.rst @@ -125,15 +125,15 @@ each lua script: - **'do_global_send_response'** - **'do_global_cache_lookup_complete'** - **'do_global_read_cache'** -- **'do_global_shut_down'** +- **'do_global_shutdown'** -The ``do_global_shut_down`` function is invoked once per Lua state when |ATS| is +The ``do_global_shutdown`` function is invoked once per Lua state when |ATS| is shutting down. It can be used to perform cleanup tasks such as flushing state or releasing resources. It takes no arguments and its return value is ignored. Example:: - function do_global_shut_down() + function do_global_shutdown() ts.debug('ATS shutting down, cleaning up resources') end diff --git a/plugins/lua/ts_lua_common.h b/plugins/lua/ts_lua_common.h index 605d2316e8a..969aa5e8ba0 100644 --- a/plugins/lua/ts_lua_common.h +++ b/plugins/lua/ts_lua_common.h @@ -63,7 +63,7 @@ extern "C" { #define TS_LUA_FUNCTION_G_VCONN_START "do_global_vconn_start" // Lifecycle hooks -#define TS_LUA_FUNCTION_G_SHUT_DOWN "do_global_shut_down" +#define TS_LUA_FUNCTION_G_SHUT_DOWN "do_global_shutdown" #define TS_LUA_DEBUG_TAG "ts_lua" diff --git a/tests/gold_tests/pluginTest/lua/global_shutdown.lua b/tests/gold_tests/pluginTest/lua/global_shutdown.lua index 5a5f5fa6d6a..8c8dcdbdaf4 100644 --- a/tests/gold_tests/pluginTest/lua/global_shutdown.lua +++ b/tests/gold_tests/pluginTest/lua/global_shutdown.lua @@ -18,6 +18,6 @@ function do_global_read_request() ts.debug('do_global_read_request called') end -function do_global_shut_down() - ts.debug('do_global_shut_down called') +function do_global_shutdown() + ts.debug('do_global_shutdown called') end diff --git a/tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py b/tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py index 6fde567fbf4..134c10df481 100644 --- a/tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py +++ b/tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py @@ -62,4 +62,4 @@ # After all test runs complete AuTest stops ATS, which fires TS_LIFECYCLE_SHUTDOWN_HOOK. # The shutdown handler calls do_global_shut_down once per Lua state (2 states configured). ts.Disk.traffic_out.Content += Testers.ContainsExpression( - r'do_global_shut_down called', 'do_global_shut_down should be called on ATS shutdown') + r'do_global_shutdown called', 'do_global_shutdown should be called on ATS shutdown') From 56d1d7fc39496b8044c99f9c4283a947ea288f01 Mon Sep 17 00:00:00 2001 From: Kit Chan Date: Tue, 31 Mar 2026 15:15:46 -0700 Subject: [PATCH 3/7] update comments --- tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py b/tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py index 134c10df481..bad03fb28b2 100644 --- a/tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py +++ b/tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py @@ -1,5 +1,5 @@ ''' -Test do_global_shut_down lua global plugin hook. +Test do_global_shutdown lua global plugin hook. ''' # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -18,7 +18,7 @@ # limitations under the License. Test.Summary = ''' -Test do_global_shut_down lua global plugin hook +Test do_global_shutdown lua global plugin hook ''' Test.SkipUnless(Condition.PluginExists('tslua.so'),) @@ -60,6 +60,6 @@ r'do_global_read_request called', 'do_global_read_request should be called for HTTP requests') # After all test runs complete AuTest stops ATS, which fires TS_LIFECYCLE_SHUTDOWN_HOOK. -# The shutdown handler calls do_global_shut_down once per Lua state (2 states configured). +# The shutdown handler calls do_global_shutdown once per Lua state (2 states configured). ts.Disk.traffic_out.Content += Testers.ContainsExpression( r'do_global_shutdown called', 'do_global_shutdown should be called on ATS shutdown') From 605321382ec00bd370cf6af560d56d2680f64317 Mon Sep 17 00:00:00 2001 From: Kit Chan Date: Tue, 31 Mar 2026 16:35:59 -0700 Subject: [PATCH 4/7] Fix problem with calling the lua function --- plugins/lua/ts_lua.cc | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/plugins/lua/ts_lua.cc b/plugins/lua/ts_lua.cc index 9bb934ab39e..54a5a33044d 100644 --- a/plugins/lua/ts_lua.cc +++ b/plugins/lua/ts_lua.cc @@ -837,18 +837,30 @@ shutdownHookHandler(TSCont contp, TSEvent /* event ATS_UNUSED */, void * /* edat TSMutexLock(main_ctx->mutexp); - lua_State *const l = main_ctx->lua; + lua_State *const L = main_ctx->lua; - 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: %s", __FUNCTION__, lua_tostring(l, -1)); - lua_pop(l, 1); + // 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: %s", __FUNCTION__, lua_tostring(L, -1)); + lua_pop(L, 1); } } else { - lua_pop(l, 1); + 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); + TSMutexUnlock(main_ctx->mutexp); } From 8919b134c2b872d7aca3e040294f73e07238dc96 Mon Sep 17 00:00:00 2001 From: Kit Chan Date: Tue, 31 Mar 2026 17:19:08 -0700 Subject: [PATCH 5/7] Update plugins/lua/ts_lua.cc Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- plugins/lua/ts_lua.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/lua/ts_lua.cc b/plugins/lua/ts_lua.cc index 54a5a33044d..0812aafd310 100644 --- a/plugins/lua/ts_lua.cc +++ b/plugins/lua/ts_lua.cc @@ -849,7 +849,8 @@ shutdownHookHandler(TSCont contp, TSEvent /* event ATS_UNUSED */, void * /* edat if (lua_type(L, -1) == LUA_TFUNCTION) { if (lua_pcall(L, 0, 0, 0) != 0) { - TSError("[ts_lua][%s] lua_pcall failed: %s", __FUNCTION__, lua_tostring(L, -1)); + 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 { From eb01f44a64421bdeeafc5a1a40eead9db7c57242 Mon Sep 17 00:00:00 2001 From: Kit Chan Date: Tue, 31 Mar 2026 17:20:18 -0700 Subject: [PATCH 6/7] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- plugins/lua/ts_lua.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/lua/ts_lua.cc b/plugins/lua/ts_lua.cc index 0812aafd310..fe2878ee0bc 100644 --- a/plugins/lua/ts_lua.cc +++ b/plugins/lua/ts_lua.cc @@ -1107,9 +1107,13 @@ TSPluginInit(int argc, const char *argv[]) lua_getglobal(sl, TS_LUA_FUNCTION_G_SHUT_DOWN); if (lua_type(sl, -1) == LUA_TFUNCTION) { - TSCont shutdown_contp = TSContCreate(shutdownHookHandler, TSMutexCreate()); + TSMutex shutdown_mutex = TSMutexCreate(); + TSCont 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 { TSContDataSet(shutdown_contp, conf); TSLifecycleHookAdd(TS_LIFECYCLE_SHUTDOWN_HOOK, shutdown_contp); From 571fea0c1505912c4a8a90f5002bf7522569f99f Mon Sep 17 00:00:00 2001 From: Kit Chan Date: Tue, 31 Mar 2026 19:57:59 -0700 Subject: [PATCH 7/7] Fix format --- plugins/lua/ts_lua.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/ts_lua.cc b/plugins/lua/ts_lua.cc index fe2878ee0bc..02b07817118 100644 --- a/plugins/lua/ts_lua.cc +++ b/plugins/lua/ts_lua.cc @@ -1108,7 +1108,7 @@ TSPluginInit(int argc, const char *argv[]) lua_getglobal(sl, TS_LUA_FUNCTION_G_SHUT_DOWN); if (lua_type(sl, -1) == LUA_TFUNCTION) { TSMutex shutdown_mutex = TSMutexCreate(); - TSCont shutdown_contp = TSContCreate(shutdownHookHandler, shutdown_mutex); + TSCont shutdown_contp = TSContCreate(shutdownHookHandler, shutdown_mutex); if (!shutdown_contp) { TSError("[ts_lua][%s] could not create shutdown continuation", __FUNCTION__); if (shutdown_mutex) {