From 6dc4c8d7b5b40d66fe0451ef4d1f4bdf4d2cf60e Mon Sep 17 00:00:00 2001 From: Serge Petrenko <sergepetrenko@tarantool.org> Date: Fri, 25 Jan 2019 17:04:50 +0300 Subject: [PATCH] lua: patch os.exit() to execute on_shutdown triggers. Make os.exit() call tarantool_exit(), just like the signal handler does. Now on_shutdown triggers are not run only when a fatal signal is received. Closes #1607 @TarantoolBot document Title: Document box.ctl.on_shutdown triggers on_shutdown triggers may be set similar to space:on_replace triggers: ``` box.ctl.on_shutdown(new_trigger, old_trigger) ``` The triggers will be run when tarantool exits due to receiving one of the signals: `SIGTERM`, `SIGINT`, `SIGHUP` or when user executes `os.exit()`. Note that the triggers will not be run if tarantool receives a fatal signal: `SIGSEGV`, `SIGABORT` or any signal causing immediate program termination. --- extra/exports | 1 + src/lua/init.lua | 12 ++++++ src/main.cc | 12 +++--- src/main.h | 3 ++ test/box/misc.result | 89 ++++++++++++++++++++++++++++++++++++++++++ test/box/misc.test.lua | 30 ++++++++++++++ 6 files changed, 142 insertions(+), 5 deletions(-) diff --git a/extra/exports b/extra/exports index 5f69e07309..35b47ae003 100644 --- a/extra/exports +++ b/extra/exports @@ -69,6 +69,7 @@ say_set_log_level say_logrotate say_set_log_format tarantool_uptime +tarantool_exit log_pid space_by_id space_run_triggers diff --git a/src/lua/init.lua b/src/lua/init.lua index fa324d34c8..9fd56f483f 100644 --- a/src/lua/init.lua +++ b/src/lua/init.lua @@ -38,6 +38,8 @@ double tarantool_uptime(void); typedef int32_t pid_t; pid_t getpid(void); +void +tarantool_exit(int); ]] local fio = require("fio") @@ -50,6 +52,16 @@ dostring = function(s, ...) return chunk(...) end +local fiber = require("fiber") +os.exit = function(code) + code = (type(code) == 'number') and code or 0 + ffi.C.tarantool_exit(code) + -- Make sure we yield even if the code after + -- os.exit() never yields. After on_shutdown + -- fiber completes, we will never wake up again. + while true do fiber.yield() end +end + local function uptime() return tonumber(ffi.C.tarantool_uptime()); end diff --git a/src/main.cc b/src/main.cc index a3d96843d6..4edae21852 100644 --- a/src/main.cc +++ b/src/main.cc @@ -95,6 +95,7 @@ static struct fiber *on_shutdown_fiber = NULL; static bool is_shutting_down = false; /** A trigger which will break the event loop on shutdown. */ static struct trigger break_loop_trigger; +static int exit_code = 0; double tarantool_uptime(void) @@ -134,9 +135,10 @@ on_shutdown_f(va_list ap) return 0; } -static void -tarantool_exit(void) +void +tarantool_exit(int code) { + start_loop = false; if (is_shutting_down) { /* * We are already running on_shutdown triggers, @@ -146,6 +148,7 @@ tarantool_exit(void) return; } is_shutting_down = true; + exit_code = code; fiber_call(on_shutdown_fiber); } @@ -165,8 +168,7 @@ signal_cb(ev_loop *loop, struct ev_signal *w, int revents) */ if (pid_file) say_crit("got signal %d - %s", w->signum, strsignal(w->signum)); - start_loop = false; - tarantool_exit(); + tarantool_exit(0); } static void @@ -849,5 +851,5 @@ main(int argc, char **argv) if (start_loop) say_crit("exiting the event loop"); /* freeing resources */ - return 0; + return exit_code; } diff --git a/src/main.h b/src/main.h index 221374144a..f509e905bf 100644 --- a/src/main.h +++ b/src/main.h @@ -39,6 +39,9 @@ extern "C" { double tarantool_uptime(void); +void +tarantool_exit(int); + void load_cfg(); diff --git a/test/box/misc.result b/test/box/misc.result index 6912915c16..97189ecbb2 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -1321,3 +1321,92 @@ box.space.shutdown:select{} box.space.shutdown:drop() --- ... +-- Check that os.exit invokes triggers +fiber = require("fiber") +--- +... +test_run:cmd("create server test with script='box/proxy.lua'") +--- +- true +... +test_run:cmd("start server test") +--- +- true +... +logfile = test_run:eval("test", "box.cfg.log")[1] +--- +... +test_run:cmd("stop server test") +--- +- true +... +-- clean up any leftover logs +require("fio").unlink(logfile) +--- +- true +... +test_run:cmd("start server test") +--- +- true +... +test_run:cmd("switch test") +--- +- true +... +_ = box.ctl.on_shutdown(function() print("on_shutdown 5") end) +--- +... +-- Check that we don't hang infinitely after os.exit() +-- even if the following code doesn't yield. +fiber = require("fiber") +--- +... +_ = fiber.create(function() fiber.sleep(0.05) os.exit() while true do end end) +--- +... +test_run:cmd("switch default") +--- +- true +... +fiber.sleep(0.1) +--- +... +-- The server should be already stopped by os.exit(), +-- but start doesn't work without a prior call to stop. +test_run:cmd("stop server test") +--- +- true +... +test_run:cmd("start server test") +--- +- true +... +test_run:cmd("switch test") +--- +- true +... +test_run:grep_log('test', 'on_shutdown 5', nil, {noreset=true}) +--- +- on_shutdown 5 +... +-- make sure we exited because of os.exit(), not a signal. +test_run:grep_log('test', 'signal', nil, {noreset=true}) +--- +- null +... +test_run:cmd("switch default") +--- +- true +... +test_run:cmd("stop server test") +--- +- true +... +test_run:cmd("cleanup server test") +--- +- true +... +test_run:cmd("delete server test") +--- +- true +... diff --git a/test/box/misc.test.lua b/test/box/misc.test.lua index f1c9d8e8cb..18128b2991 100644 --- a/test/box/misc.test.lua +++ b/test/box/misc.test.lua @@ -384,3 +384,33 @@ test_run:grep_log('default', 'on_shutdown 3', nil, {noreset=true}) test_run:grep_log('default', 'on_shutdown 4', nil, {noreset=true}) box.space.shutdown:select{} box.space.shutdown:drop() + +-- Check that os.exit invokes triggers +fiber = require("fiber") +test_run:cmd("create server test with script='box/proxy.lua'") +test_run:cmd("start server test") +logfile = test_run:eval("test", "box.cfg.log")[1] +test_run:cmd("stop server test") +-- clean up any leftover logs +require("fio").unlink(logfile) +test_run:cmd("start server test") +test_run:cmd("switch test") +_ = box.ctl.on_shutdown(function() print("on_shutdown 5") end) +-- Check that we don't hang infinitely after os.exit() +-- even if the following code doesn't yield. +fiber = require("fiber") +_ = fiber.create(function() fiber.sleep(0.05) os.exit() while true do end end) +test_run:cmd("switch default") +fiber.sleep(0.1) +-- The server should be already stopped by os.exit(), +-- but start doesn't work without a prior call to stop. +test_run:cmd("stop server test") +test_run:cmd("start server test") +test_run:cmd("switch test") +test_run:grep_log('test', 'on_shutdown 5', nil, {noreset=true}) +-- make sure we exited because of os.exit(), not a signal. +test_run:grep_log('test', 'signal', nil, {noreset=true}) +test_run:cmd("switch default") +test_run:cmd("stop server test") +test_run:cmd("cleanup server test") +test_run:cmd("delete server test") -- GitLab