From 3010f024ea2378ff776ee773dc88cd53871b9a42 Mon Sep 17 00:00:00 2001 From: mechanik20051988 <mechanik20051988@tarantool.org> Date: Tue, 16 Feb 2021 16:53:30 +0300 Subject: [PATCH] Implement on_shutdown API Implemented on_shutdown API, which allows to register functions that will be called when the tarantool stopped. Functions will be called in the reverse order they are registered. So the module developer registers one fuction that starts module termination and waits for its competition. This function should be fast or used an asynchronous waiting mechanism (coio_wait or cord_cojoin for example). Closes #5723 @TarantoolBot document Title: Implement on_shutdown API Implemented on_shutdown API, which allows to register functions that will be called when the tarantool stopped. Functions will be called in the reverse order they are registered. So the module developer registers one fuction that starts module termination and waits for its competition. This function should be fast or used an asynchronous waiting mechanism (coio_wait or cord_cojoin for example). --- .../unreleased/implement-on-shutdown-api.md | 5 + src/CMakeLists.txt | 5 +- src/exports.h | 1 + src/on_shutdown.c | 130 +++++++++++++++++ src/on_shutdown.h | 66 +++++++++ test/app/CMakeLists.txt | 1 + test/app/on_shutdown.lua | 7 + test/app/on_shutdown.result | 136 ++++++++++++++++++ test/app/on_shutdown.test.lua | 49 +++++++ test/app/on_shutdownlib.c | 126 ++++++++++++++++ 10 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/implement-on-shutdown-api.md create mode 100644 src/on_shutdown.c create mode 100644 src/on_shutdown.h create mode 100644 test/app/on_shutdown.lua create mode 100644 test/app/on_shutdown.result create mode 100644 test/app/on_shutdown.test.lua create mode 100644 test/app/on_shutdownlib.c diff --git a/changelogs/unreleased/implement-on-shutdown-api.md b/changelogs/unreleased/implement-on-shutdown-api.md new file mode 100644 index 0000000000..c7d7425796 --- /dev/null +++ b/changelogs/unreleased/implement-on-shutdown-api.md @@ -0,0 +1,5 @@ +## feature/core + +* Implemented on_shutdown API, which allows tarantool module + developer to register functions that will be called when + tarantool stopped (gh-5723). \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 76c410a701..fb528b6666 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -98,6 +98,8 @@ add_library(crc32 STATIC ) target_link_libraries(crc32 cpu_feature) +add_library(shutdown STATIC on_shutdown.c) + set (server_sources find_path.c curl.c @@ -142,6 +144,7 @@ set (server_sources set(api_headers ${CMAKE_BINARY_DIR}/src/trivia/config.h ${CMAKE_SOURCE_DIR}/src/trivia/util.h + ${CMAKE_SOURCE_DIR}/src/on_shutdown.h ${CMAKE_SOURCE_DIR}/src/lib/core/say.h ${CMAKE_SOURCE_DIR}/src/lib/core/fiber.h ${CMAKE_SOURCE_DIR}/src/lib/core/fiber_cond.h @@ -187,7 +190,7 @@ target_link_libraries(server core coll http_parser bit uri uuid swim swim_udp # Rule of thumb: if exporting a symbol from a static library, list the # library here. set (reexport_libraries server core misc bitset csv swim swim_udp swim_ev - ${LUAJIT_LIBRARIES} ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES}) + shutdown ${LUAJIT_LIBRARIES} ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES}) set (common_libraries ${reexport_libraries} diff --git a/src/exports.h b/src/exports.h index 1b95e75f62..eb72bc928e 100644 --- a/src/exports.h +++ b/src/exports.h @@ -48,6 +48,7 @@ EXPORT(box_latch_lock) EXPORT(box_latch_new) EXPORT(box_latch_trylock) EXPORT(box_latch_unlock) +EXPORT(box_on_shutdown) EXPORT(box_region_aligned_alloc) EXPORT(box_region_alloc) EXPORT(box_region_truncate) diff --git a/src/on_shutdown.c b/src/on_shutdown.c new file mode 100644 index 0000000000..5444f802e3 --- /dev/null +++ b/src/on_shutdown.c @@ -0,0 +1,130 @@ +/* + * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "on_shutdown.h" + +#include "box/box.h" +#include "say.h" + +#include <stdlib.h> +#include <small/rlist.h> +#include <errno.h> +#include <trigger.h> + +struct on_shutdown_trigger { + struct trigger trigger; + /** Shutdown trigger function */ + int (*handler)(void *); + /** Trigger function argument */ + void *arg; + /** link for on_shutdown_trigger_list */ + struct rlist link; +}; + +static struct rlist on_shutdown_trigger_list = + RLIST_HEAD_INITIALIZER(on_shutdown_trigger_list); + +static int +trigger_commom_f(struct trigger *trigger, MAYBE_UNUSED void *event) +{ + struct on_shutdown_trigger *on_shutdown_trigger = + container_of(trigger, struct on_shutdown_trigger, trigger); + return on_shutdown_trigger->handler(on_shutdown_trigger->arg); +} + +static int +on_shutdown_trigger_create(int (*handler)(void *), void *arg) +{ + struct on_shutdown_trigger *trigger = (struct on_shutdown_trigger *) + malloc(sizeof(struct on_shutdown_trigger)); + if (trigger == NULL) + return -1; + trigger_create(&trigger->trigger, trigger_commom_f, NULL, NULL); + trigger->handler = handler; + trigger->arg = arg; + trigger_add(&box_on_shutdown_trigger_list, &trigger->trigger); + rlist_add_entry(&on_shutdown_trigger_list, trigger, link); + return 0; +} + +API_EXPORT int +box_on_shutdown(void *arg, int (*new_handler)(void *), + int (*old_handler)(void *)) +{ + struct on_shutdown_trigger *trigger; + if (old_handler == NULL) { + if (new_handler == NULL) { + /* + * Invalid function params, old_handler or new_handler + * must be set + */ + say_error("Invalid function argument: old_handler and " + "new_handler cannot be equal to zero at the " + "same time."); + errno = EINVAL; + return -1; + } + return on_shutdown_trigger_create(new_handler, arg); + } + + rlist_foreach_entry(trigger, &on_shutdown_trigger_list, link) { + if (trigger->handler == old_handler) { + if (new_handler != NULL) { + /* + * Change on_shutdown trigger handler, and arg + */ + trigger->handler = new_handler; + trigger->arg = arg; + } else { + /* + * In case new_handler == NULL + * Remove old on_shutdown trigger and + * destroy it + */ + trigger_clear(&trigger->trigger); + rlist_del_entry(trigger, link); + free(trigger); + } + return 0; + } + } + + /* + * Here we are in case when we not find on_shutdown trigger, + * which we want to destroy return -1. + */ + say_error("Invalid function argument: previously registered trigger " + "with handler == old_handler not found."); + errno = EINVAL; + return -1; +} + + diff --git a/src/on_shutdown.h b/src/on_shutdown.h new file mode 100644 index 0000000000..3afb5767cd --- /dev/null +++ b/src/on_shutdown.h @@ -0,0 +1,66 @@ +#pragma once +/* + * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include <trivia/util.h> + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +/** \cond public */ + +/** + * Function, which registers or deletes on_shutdown handler. + * @param[in] arg on_shutdown function's argument. + * @param[in] new_handler New on_shutdown handler, in + * case this argument is NULL, function finds + * and destroys old on_shutdown handler. + * @param[in] old_handler Old on_shutdown handler. + * @retval return 0 if success othrewise return -1 and sets + * errno. There are three cases when + * function fails: + * - both old_handler and new_handler are equal to + * zero (sets errno to EINVAL). + * - old_handler != NULL, but there is no trigger + * with such function (sets errno to EINVAL). + * - malloc for some internal struct memory allocation + * return NULL (errno sets by malloc to ENOMEM). + */ +API_EXPORT int +box_on_shutdown(void *arg, int (*new_hadler)(void *), + int (*old_handler)(void *)); + +/** \endcond public */ + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ + diff --git a/test/app/CMakeLists.txt b/test/app/CMakeLists.txt index 059ee8f3d6..7e509bc4c4 100644 --- a/test/app/CMakeLists.txt +++ b/test/app/CMakeLists.txt @@ -1 +1,2 @@ build_module(loaderslib loaderslib.c) +build_module(on_shutdownlib on_shutdownlib.c) diff --git a/test/app/on_shutdown.lua b/test/app/on_shutdown.lua new file mode 100644 index 0000000000..72063552c3 --- /dev/null +++ b/test/app/on_shutdown.lua @@ -0,0 +1,7 @@ +#!/usr/bin/env tarantool + +require('console').listen(os.getenv('ADMIN')) + +box.cfg({ + listen = os.getenv("LISTEN"), +}) diff --git a/test/app/on_shutdown.result b/test/app/on_shutdown.result new file mode 100644 index 0000000000..c3f86cdeda --- /dev/null +++ b/test/app/on_shutdown.result @@ -0,0 +1,136 @@ +-- test-run result file version 2 +env = require('test_run') + | --- + | ... +test_run = env.new() + | --- + | ... +build_dir = os.getenv("BUILDDIR") .. "/test/app/" + | --- + | ... +soext = (jit.os == "OSX" and "dylib" or "so") + | --- + | ... +on_shutdownlib = "on_shutdownlib."..soext + | --- + | ... +os.execute(string.format("cp %s/%s .", build_dir, on_shutdownlib)) + | --- + | - 0 + | ... + +test_run:cmd('create server test with script="app/on_shutdown.lua"') + | --- + | - true + | ... +test_run:cmd('start server test') + | --- + | - true + | ... +test_run:cmd('switch test') + | --- + | - true + | ... + +-- Default on_shutdown triggers timeout == 3, but we sets it +-- here directly to make test clear +box.ctl.set_on_shutdown_timeout(3) + | --- + | ... +module = require('on_shutdownlib') + | --- + | ... +-- Setting shutdown timeout for main module fiber function. +-- This timeout < on_shutdown triggers timeout, so on_shutdown +-- trigger completes successfully. +shutdown_timeout = 1 + | --- + | ... +module.cfg(shutdown_timeout) + | --- + | ... + +test_run:cmd('switch default') + | --- + | - true + | ... +test_run:cmd('stop server test') + | --- + | - true + | ... +os.execute(string.format("grep -r \"stop module fiber\" on_shutdown.log")) + | --- + | - 0 + | ... +os.execute(string.format("grep -r \"join module fiber\" on_shutdown.log")) + | --- + | - 0 + | ... +os.execute(string.format("grep -r \"module_fiber_f finished\" \ + on_shutdown.log")) + | --- + | - 0 + | ... +os.execute(string.format("grep -r \"join module fiber finished\" \ + on_shutdown.log")) + | --- + | - 0 + | ... +test_run:cmd('start server test') + | --- + | - true + | ... +test_run:cmd('switch test') + | --- + | - true + | ... + +box.ctl.set_on_shutdown_timeout(1) + | --- + | ... +module = require('on_shutdownlib') + | --- + | ... +-- Setting shutdown timeout for main module fiber function. +-- This timeout > on_shutdown triggers timeout, so on_shutdown +-- trigger does not have time to complete. +shutdown_timeout = 10 + | --- + | ... +module.cfg(shutdown_timeout) + | --- + | ... + +test_run:cmd('switch default') + | --- + | - true + | ... +test_run:cmd('stop server test') + | --- + | - true + | ... +os.execute(string.format("rm %s", on_shutdownlib)) + | --- + | - 0 + | ... +os.execute(string.format("grep -r \"stop module fiber\" on_shutdown.log")) + | --- + | - 0 + | ... +os.execute(string.format("grep -r \"join module fiber\" on_shutdown.log")) + | --- + | - 0 + | ... +os.execute(string.format("grep -r \"SystemError timed out\" on_shutdown.log")) + | --- + | - 0 + | ... + +test_run:cmd('cleanup server test') + | --- + | - true + | ... +test_run:cmd('delete server test') + | --- + | - true + | ... diff --git a/test/app/on_shutdown.test.lua b/test/app/on_shutdown.test.lua new file mode 100644 index 0000000000..c133209b2d --- /dev/null +++ b/test/app/on_shutdown.test.lua @@ -0,0 +1,49 @@ +env = require('test_run') +test_run = env.new() +build_dir = os.getenv("BUILDDIR") .. "/test/app/" +soext = (jit.os == "OSX" and "dylib" or "so") +on_shutdownlib = "on_shutdownlib."..soext +os.execute(string.format("cp %s/%s .", build_dir, on_shutdownlib)) + +test_run:cmd('create server test with script="app/on_shutdown.lua"') +test_run:cmd('start server test') +test_run:cmd('switch test') + +-- Default on_shutdown triggers timeout == 3, but we sets it +-- here directly to make test clear +box.ctl.set_on_shutdown_timeout(3) +module = require('on_shutdownlib') +-- Setting shutdown timeout for main module fiber function. +-- This timeout < on_shutdown triggers timeout, so on_shutdown +-- trigger completes successfully. +shutdown_timeout = 1 +module.cfg(shutdown_timeout) + +test_run:cmd('switch default') +test_run:cmd('stop server test') +os.execute(string.format("grep -r \"stop module fiber\" on_shutdown.log")) +os.execute(string.format("grep -r \"join module fiber\" on_shutdown.log")) +os.execute(string.format("grep -r \"module_fiber_f finished\" \ + on_shutdown.log")) +os.execute(string.format("grep -r \"join module fiber finished\" \ + on_shutdown.log")) +test_run:cmd('start server test') +test_run:cmd('switch test') + +box.ctl.set_on_shutdown_timeout(1) +module = require('on_shutdownlib') +-- Setting shutdown timeout for main module fiber function. +-- This timeout > on_shutdown triggers timeout, so on_shutdown +-- trigger does not have time to complete. +shutdown_timeout = 10 +module.cfg(shutdown_timeout) + +test_run:cmd('switch default') +test_run:cmd('stop server test') +os.execute(string.format("rm %s", on_shutdownlib)) +os.execute(string.format("grep -r \"stop module fiber\" on_shutdown.log")) +os.execute(string.format("grep -r \"join module fiber\" on_shutdown.log")) +os.execute(string.format("grep -r \"SystemError timed out\" on_shutdown.log")) + +test_run:cmd('cleanup server test') +test_run:cmd('delete server test') diff --git a/test/app/on_shutdownlib.c b/test/app/on_shutdownlib.c new file mode 100644 index 0000000000..1569bffdcb --- /dev/null +++ b/test/app/on_shutdownlib.c @@ -0,0 +1,126 @@ +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> +#include <luaconf.h> + +#include <module.h> +#include "../unit/unit.h" + +struct module { + /** + * Flag of the module state, May be -1, 0, 1 + * -1 means that module thread failed to start + * 0 means that module currently stop + * 1 menas that module currently running + */ + int is_running; + /** + * Module fiber + */ + struct fiber *fiber; + /** + * Time to sleep, before module fiber finished + */ + double timeout; +}; + +static struct module module; + +/** Shutdown function to test on_shutdown API */ +static int +on_shutdown_module_bad_func(void *arg) +{ + fail_unless(0); + return 0; +} + +/** Shutdown function to stop the main fiber of the module */ +static int +on_shutdown_module_stop_func(void *arg) +{ + fprintf(stderr, "stop module fiber\n"); + fiber_wakeup(module.fiber); + fprintf(stderr, "join module fiber\n"); + fiber_join(module.fiber); + fprintf(stderr, "join module fiber finished\n"); + return 0; +} + +/** Main module thread function. */ +static int +module_fiber_f(va_list arg) +{ + + __atomic_store_n(&module.is_running, 1, __ATOMIC_SEQ_CST); + /* + * In first yield we return control until the + * on_shutdown_module_stop_func function is called. + * Sleep required for test purpose: if sleep time > + * on_shutdown_wait_time then timeout in trigger_fiber_run + * will expire and tarantool will shutdown without waiting for the + * module to finish working. + */ + fiber_yield(); + fiber_sleep(module.timeout); + __atomic_store_n(&module.is_running, 0, __ATOMIC_SEQ_CST); + fprintf(stderr, "module_fiber_f finished\n"); + return 0; +} + +static int +cfg(lua_State *L) +{ + int index = lua_gettop(L); + if (index != 1) { + lua_pushstring(L, "fuction expected one argument"); + lua_error(L); + } + double timeout = luaL_checknumber(L, 1); + if (timeout < 0) { + lua_pushstring(L, "module shutdown sleep time must be greater " + "then or equal zero"); + lua_error(L); + } + + /** In case module already started do nothing */ + if (__atomic_load_n(&module.is_running, __ATOMIC_SEQ_CST) == 1) + return 0; + + module.timeout = timeout; + + int save_errno = errno; + /* + * Invalid function arguments: old_handle and new_hanlder can't + * be equal to zero at the same time. + */ + fail_unless(box_on_shutdown(&module, + NULL, + NULL) == -1); + fail_unless(errno == EINVAL); + errno = save_errno; + /** Registering the module stop function */ + fail_unless(box_on_shutdown(&module, + on_shutdown_module_bad_func, + NULL) == 0); + /** Changing the module stop function */ + fail_unless(box_on_shutdown(&module, + on_shutdown_module_stop_func, + on_shutdown_module_bad_func) == 0); + module.fiber = fiber_new("fiber", module_fiber_f); + fail_unless(module.fiber != NULL); + fiber_set_joinable(module.fiber, true); + fiber_start(module.fiber); + return 0; +} + +static const struct luaL_Reg on_shutdownlib[] = { + {"cfg", cfg}, + {NULL, NULL} +}; + +LUALIB_API int +luaopen_on_shutdownlib(lua_State *L) +{ + luaL_openlib(L, "on_shutdownlib", on_shutdownlib, 0); + return 0; +} -- GitLab