Skip to content

Commit cb2b336

Browse files
shukitchanCopilot
andauthored
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 <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix format * Change lua function name * Fix test formatting --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent e7e30bb commit cb2b336

5 files changed

Lines changed: 167 additions & 1 deletion

File tree

doc/admin-guide/plugins/lua.en.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,17 @@ each lua script:
125125
- **'do_global_send_response'**
126126
- **'do_global_cache_lookup_complete'**
127127
- **'do_global_read_cache'**
128+
- **'__shutdown__'**
129+
130+
The ``__shutdown__`` function is invoked once per Lua state when |ATS| is
131+
shutting down. It can be used to perform cleanup tasks such as flushing state or
132+
releasing resources. It takes no arguments and its return value is ignored.
133+
134+
Example::
135+
136+
function __shutdown__()
137+
ts.debug('ATS shutting down, cleaning up resources')
138+
end
128139

129140
We can write this in plugin.config:
130141

plugins/lua/ts_lua.cc

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,47 @@ globalHookHandler(TSCont contp, TSEvent event ATS_UNUSED, void *edata)
827827
return 0;
828828
}
829829

830+
static int
831+
shutdownHookHandler(TSCont contp, TSEvent /* event ATS_UNUSED */, void * /* edata ATS_UNUSED */)
832+
{
833+
ts_lua_instance_conf *const conf = (ts_lua_instance_conf *)TSContDataGet(contp);
834+
835+
for (int index = 0; index < conf->states; ++index) {
836+
ts_lua_main_ctx *const main_ctx = &ts_lua_g_main_ctx_array[index];
837+
838+
TSMutexLock(main_ctx->mutexp);
839+
840+
lua_State *const L = main_ctx->lua;
841+
842+
// Restore the conf-specific global table so lua_getglobal resolves
843+
// functions from the loaded script, matching ts_lua_reload_module.
844+
lua_pushlightuserdata(L, conf);
845+
lua_rawget(L, LUA_REGISTRYINDEX);
846+
lua_replace(L, LUA_GLOBALSINDEX);
847+
848+
lua_getglobal(L, TS_LUA_FUNCTION_G_SHUT_DOWN);
849+
850+
if (lua_type(L, -1) == LUA_TFUNCTION) {
851+
if (lua_pcall(L, 0, 0, 0) != 0) {
852+
TSError("[ts_lua][%s] lua_pcall failed for script '%s' state %d: %s", __FUNCTION__, conf->script, index,
853+
lua_tostring(L, -1));
854+
lua_pop(L, 1);
855+
}
856+
} else {
857+
lua_pop(L, 1);
858+
}
859+
860+
// Restore LUA_GLOBALSINDEX to an empty table, matching the resting state
861+
// established by ts_lua_add_module and ts_lua_reload_module.
862+
lua_newtable(L);
863+
lua_replace(L, LUA_GLOBALSINDEX);
864+
865+
TSMutexUnlock(main_ctx->mutexp);
866+
}
867+
868+
return 0;
869+
}
870+
830871
void
831872
TSPluginInit(int argc, const char *argv[])
832873
{
@@ -1046,7 +1087,7 @@ TSPluginInit(int argc, const char *argv[])
10461087
}
10471088
TSContDataSet(vconn_contp, conf);
10481089

1049-
// adding hook based on whther the lua global vconn function exists
1090+
// adding hook based on whether the lua global vconn function exists
10501091
ts_lua_vconn_ctx *vconn_ctx = ts_lua_create_vconn_ctx(main_ctx, conf);
10511092
lua_State *vl = vconn_ctx->lua;
10521093

@@ -1059,6 +1100,30 @@ TSPluginInit(int argc, const char *argv[])
10591100

10601101
ts_lua_destroy_vconn_ctx(vconn_ctx);
10611102

1103+
// adding shutdown hook if the lua global shutdown function exists
1104+
ts_lua_main_ctx *shutdown_main_ctx = &ts_lua_g_main_ctx_array[0];
1105+
ts_lua_http_ctx *shutdown_http_ctx = ts_lua_create_http_ctx(shutdown_main_ctx, conf);
1106+
lua_State *sl = shutdown_http_ctx->cinfo.routine.lua;
1107+
1108+
lua_getglobal(sl, TS_LUA_FUNCTION_G_SHUT_DOWN);
1109+
if (lua_type(sl, -1) == LUA_TFUNCTION) {
1110+
TSMutex shutdown_mutex = TSMutexCreate();
1111+
TSCont shutdown_contp = TSContCreate(shutdownHookHandler, shutdown_mutex);
1112+
if (!shutdown_contp) {
1113+
TSError("[ts_lua][%s] could not create shutdown continuation", __FUNCTION__);
1114+
if (shutdown_mutex) {
1115+
TSMutexDestroy(shutdown_mutex);
1116+
}
1117+
} else {
1118+
TSContDataSet(shutdown_contp, conf);
1119+
TSLifecycleHookAdd(TS_LIFECYCLE_SHUTDOWN_HOOK, shutdown_contp);
1120+
Dbg(dbg_ctl, "shutdown_hook added");
1121+
}
1122+
}
1123+
lua_pop(sl, 1);
1124+
1125+
ts_lua_destroy_http_ctx(shutdown_http_ctx);
1126+
10621127
// support for reload as global plugin
10631128
if (reload) {
10641129
TSCont config_contp = TSContCreate(configHandler, nullptr);

plugins/lua/ts_lua_common.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ extern "C" {
6262
// TLS hooks can only be global
6363
#define TS_LUA_FUNCTION_G_VCONN_START "do_global_vconn_start"
6464

65+
// Lifecycle hooks
66+
#define TS_LUA_FUNCTION_G_SHUT_DOWN "__shutdown__"
67+
6568
#define TS_LUA_DEBUG_TAG "ts_lua"
6669

6770
#define TS_LUA_EVENT_COROUTINE_CONT 20000
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-- Licensed to the Apache Software Foundation (ASF) under one
2+
-- or more contributor license agreements. See the NOTICE file
3+
-- distributed with this work for additional information
4+
-- regarding copyright ownership. The ASF licenses this file
5+
-- to you under the Apache License, Version 2.0 (the
6+
-- "License"); you may not use this file except in compliance
7+
-- with the License. You may obtain a copy of the License at
8+
--
9+
-- http://www.apache.org/licenses/LICENSE-2.0
10+
--
11+
-- Unless required by applicable law or agreed to in writing, software
12+
-- distributed under the License is distributed on an "AS IS" BASIS,
13+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
-- See the License for the specific language governing permissions and
15+
-- limitations under the License.
16+
17+
function do_global_read_request()
18+
ts.debug('do_global_read_request called')
19+
end
20+
21+
function __shutdown__()
22+
ts.debug('__shutdown__ called')
23+
end
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'''
2+
Test __shutdown__ lua global plugin hook.
3+
'''
4+
# Licensed to the Apache Software Foundation (ASF) under one
5+
# or more contributor license agreements. See the NOTICE file
6+
# distributed with this work for additional information
7+
# regarding copyright ownership. The ASF licenses this file
8+
# to you under the Apache License, Version 2.0 (the
9+
# "License"); you may not use this file except in compliance
10+
# with the License. You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing, software
15+
# distributed under the License is distributed on an "AS IS" BASIS,
16+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
# See the License for the specific language governing permissions and
18+
# limitations under the License.
19+
20+
Test.Summary = '''
21+
Test __shutdown__ lua global plugin hook
22+
'''
23+
24+
Test.SkipUnless(Condition.PluginExists('tslua.so'),)
25+
26+
Test.ContinueOnFail = True
27+
28+
server = Test.MakeOriginServer("server")
29+
ts = Test.MakeATSProcess("ts")
30+
31+
Test.Setup.Copy("global_shutdown.lua")
32+
33+
request_header = {"headers": "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
34+
response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
35+
server.addResponse("sessionfile.log", request_header, response_header)
36+
37+
ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{}/'.format(server.Variables.Port))
38+
39+
# Use 2 states so the shutdown handler is called a predictable number of times.
40+
ts.Disk.plugin_config.AddLine('tslua.so --states=2 {}/global_shutdown.lua'.format(Test.RunDirectory))
41+
42+
ts.Disk.records_config.update({
43+
'proxy.config.diags.debug.enabled': 1,
44+
'proxy.config.diags.debug.tags': 'ts_lua',
45+
})
46+
47+
curl_and_args = '-s -D /dev/stdout -o /dev/stderr -x localhost:{} '.format(ts.Variables.port)
48+
49+
# 0 Test - Send a request to confirm the global plugin is active.
50+
tr = Test.AddTestRun("Lua global read request hook fires for HTTP requests")
51+
ps = tr.Processes.Default
52+
ps.StartBefore(server, ready=When.PortOpen(server.Variables.Port))
53+
ps.StartBefore(Test.Processes.ts)
54+
tr.MakeCurlCommand(curl_and_args + 'http://www.example.com/', ts=ts)
55+
ps.ReturnCode = 0
56+
tr.StillRunningAfter = ts
57+
58+
# Verify do_global_read_request was invoked for the HTTP request above.
59+
ts.Disk.traffic_out.Content = Testers.ContainsExpression(
60+
r'do_global_read_request called', 'do_global_read_request should be called for HTTP requests')
61+
62+
# After all test runs complete AuTest stops ATS, which fires TS_LIFECYCLE_SHUTDOWN_HOOK.
63+
# The shutdown handler calls __shutdown__ once per Lua state (2 states configured).
64+
ts.Disk.traffic_out.Content += Testers.ContainsExpression(r'__shutdown__ called', '__shutdown__ should be called on ATS shutdown')

0 commit comments

Comments
 (0)