You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by ki...@apache.org on 2026/04/16 19:15:56 UTC

(trafficserver) branch master updated: Add shutdown hook function for lua plugin (#13045)

This is an automated email from the ASF dual-hosted git repository.

kichan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new cb2b336994 Add shutdown hook function for lua plugin (#13045)
cb2b336994 is described below

commit cb2b336994e5665aeaf8700765fe7de733f16b74
Author: Kit Chan <ki...@apache.org>
AuthorDate: Thu Apr 16 12:15:50 2026 -0700

    Add shutdown hook function for lua plugin (#13045)
    
    * Add shutdown hook function for lua plugin
    
    * Update lua function name
    
    * update comments
    
    * Fix problem with calling the lua function
    
    * Update plugins/lua/ts_lua.cc
    
    Co-authored-by: Copilot <17...@users.noreply.github.com>
    
    * Apply suggestion from @Copilot
    
    Co-authored-by: Copilot <17...@users.noreply.github.com>
    
    * Fix format
    
    * Change lua function name
    
    * Fix test formatting
    
    ---------
    
    Co-authored-by: Copilot <17...@users.noreply.github.com>
---
 doc/admin-guide/plugins/lua.en.rst                 | 11 ++++
 plugins/lua/ts_lua.cc                              | 67 +++++++++++++++++++++-
 plugins/lua/ts_lua_common.h                        |  3 +
 .../gold_tests/pluginTest/lua/global_shutdown.lua  | 23 ++++++++
 .../pluginTest/lua/lua_global_shutdown.test.py     | 64 +++++++++++++++++++++
 5 files changed, 167 insertions(+), 1 deletion(-)

diff --git a/doc/admin-guide/plugins/lua.en.rst b/doc/admin-guide/plugins/lua.en.rst
index d62a2f4e43..c5e7f784ce 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'**
+- **'__shutdown__'**
+
+The ``__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 __shutdown__()
+        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 741da1893f..02b0781711 100644
--- a/plugins/lua/ts_lua.cc
+++ b/plugins/lua/ts_lua.cc
@@ -827,6 +827,47 @@ 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;
+
+    // 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);
+
+    TSMutexUnlock(main_ctx->mutexp);
+  }
+
+  return 0;
+}
+
 void
 TSPluginInit(int argc, const char *argv[])
 {
@@ -1046,7 +1087,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 +1100,30 @@ 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) {
+    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);
+      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 17e554893f..4f203fe0c1 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 "__shutdown__"
+
 #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 0000000000..ab30007876
--- /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 __shutdown__()
+  ts.debug('__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
new file mode 100644
index 0000000000..ca2bb95046
--- /dev/null
+++ b/tests/gold_tests/pluginTest/lua/lua_global_shutdown.test.py
@@ -0,0 +1,64 @@
+'''
+Test __shutdown__ 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 __shutdown__ 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 __shutdown__ once per Lua state (2 states configured).
+ts.Disk.traffic_out.Content += Testers.ContainsExpression(r'__shutdown__ called', '__shutdown__ should be called on ATS shutdown')