From: Remi Gacogne Date: Mon, 10 Mar 2025 16:15:02 +0000 (+0100) Subject: dnsdist: Add support for calling Lua methods when exiting X-Git-Tag: dnsdist-2.0.0-alpha2~104^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1d961156b615d6b1cf9a37da59f0fceb3ecb0f93;p=thirdparty%2Fpdns.git dnsdist: Add support for calling Lua methods when exiting --- diff --git a/pdns/dnsdistdist/dnsdist-console.cc b/pdns/dnsdistdist/dnsdist-console.cc index dcdd436cb7..55bbb4ce57 100644 --- a/pdns/dnsdistdist/dnsdist-console.cc +++ b/pdns/dnsdistdist/dnsdist-console.cc @@ -498,6 +498,7 @@ static const std::vector s_consoleKeywords{ {"addCacheHitResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a cache hit response rule"}, {"addCacheInsertedResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a cache inserted response rule"}, {"addMaintenanceCallback", true, "callback", "register a function to be called as part of the maintenance hook, every second"}, + {"addExitCallback", true, "callback", "register a function to be called when DNSdist exits"}, {"addResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a response rule"}, {"addSelfAnsweredResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a self-answered response rule"}, {"addXFRResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a XFR response rule"}, diff --git a/pdns/dnsdistdist/dnsdist-lua-hooks.cc b/pdns/dnsdistdist/dnsdist-lua-hooks.cc index 1f92e0ccf3..45cc25e1e6 100644 --- a/pdns/dnsdistdist/dnsdist-lua-hooks.cc +++ b/pdns/dnsdistdist/dnsdist-lua-hooks.cc @@ -6,9 +6,11 @@ namespace dnsdist::lua::hooks { +using ExitCallback = std::function; using MaintenanceCallback = std::function; using TicketsKeyAddedHook = std::function; +static LockGuarded> s_exitCallbacks; static LockGuarded> s_maintenanceHooks; void runMaintenanceHooks(const LuaContext& context) @@ -30,6 +32,25 @@ void clearMaintenanceHooks() s_maintenanceHooks.lock()->clear(); } +void runExitCallbacks(const LuaContext& context) +{ + (void)context; + for (const auto& callback : *(s_exitCallbacks.lock())) { + callback(); + } +} + +static void addExitCallback(const LuaContext& context, ExitCallback callback) +{ + (void)context; + s_exitCallbacks.lock()->push_back(std::move(callback)); +} + +void clearExitCallbacks() +{ + s_exitCallbacks.lock()->clear(); +} + static void setTicketsKeyAddedHook(const LuaContext& context, const TicketsKeyAddedHook& hook) { (void)context; @@ -50,6 +71,10 @@ void setupLuaHooks(LuaContext& luaCtx) setLuaSideEffect(); addMaintenanceCallback(luaCtx, callback); }); + luaCtx.writeFunction("addExitCallback", [&luaCtx](const ExitCallback& callback) { + setLuaSideEffect(); + addExitCallback(luaCtx, callback); + }); luaCtx.writeFunction("setTicketsKeyAddedHook", [&luaCtx](const TicketsKeyAddedHook& hook) { setLuaSideEffect(); setTicketsKeyAddedHook(luaCtx, hook); diff --git a/pdns/dnsdistdist/dnsdist-lua-hooks.hh b/pdns/dnsdistdist/dnsdist-lua-hooks.hh index e35c0f10ac..a1b8c3ca8f 100644 --- a/pdns/dnsdistdist/dnsdist-lua-hooks.hh +++ b/pdns/dnsdistdist/dnsdist-lua-hooks.hh @@ -29,5 +29,7 @@ namespace dnsdist::lua::hooks { void runMaintenanceHooks(const LuaContext& context); void clearMaintenanceHooks(); +void runExitCallbacks(const LuaContext& context); +void clearExitCallbacks(); void setupLuaHooks(LuaContext& luaCtx); } diff --git a/pdns/dnsdistdist/dnsdist-lua.cc b/pdns/dnsdistdist/dnsdist-lua.cc index 0f8b0a2d53..67a1fc1a6b 100644 --- a/pdns/dnsdistdist/dnsdist-lua.cc +++ b/pdns/dnsdistdist/dnsdist-lua.cc @@ -912,20 +912,10 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) } }); + void doExitNicely(int exitCode = EXIT_SUCCESS); + luaCtx.writeFunction("shutdown", []() { -#ifdef HAVE_SYSTEMD - sd_notify(0, "STOPPING=1"); -#endif /* HAVE_SYSTEMD */ -#if 0 - // Useful for debugging leaks, but might lead to race under load - // since other threads are still running. - for (auto& frontend : getDoTFrontends()) { - frontend->cleanup(); - } - g_rings.clear(); -#endif /* 0 */ - pdns::coverage::dumpCoverageData(); - _exit(0); + doExitNicely(); }); typedef LuaAssociativeTable> showserversopts_t; diff --git a/pdns/dnsdistdist/dnsdist.cc b/pdns/dnsdistdist/dnsdist.cc index 6d2d077950..f14eaf3863 100644 --- a/pdns/dnsdistdist/dnsdist.cc +++ b/pdns/dnsdistdist/dnsdist.cc @@ -2321,6 +2321,16 @@ static void secPollThread() } #endif /* DISABLE_SECPOLL */ +static std::atomic s_exiting{false}; +void doExitNicely(int exitCode = EXIT_SUCCESS); + +static void checkExiting() +{ + if (s_exiting) { + doExitNicely(); + } +} + static void healthChecksThread() { setThreadName("dnsdist/healthC"); @@ -2331,6 +2341,8 @@ static void healthChecksThread() .tv_usec = 0}; for (;;) { + checkExiting(); + timeval now{}; gettimeofday(&now, nullptr); auto elapsedTimeUsec = uSec(now - lastRound); @@ -2751,8 +2763,9 @@ static void usage() #endif #if defined(COVERAGE) || (defined(__SANITIZE_ADDRESS__) && defined(HAVE_LEAK_SANITIZER_INTERFACE)) -static void cleanupLuaObjects() +static void cleanupLuaObjects(LuaContext& /* luaCtx */) { + dnsdist::lua::hooks::clearExitCallbacks(); /* when our coverage mode is enabled, we need to make sure that the Lua objects are destroyed before the Lua contexts. */ dnsdist::configuration::updateRuntimeConfiguration([](dnsdist::configuration::RuntimeConfiguration& config) { @@ -2766,29 +2779,20 @@ static void cleanupLuaObjects() } #endif /* defined(COVERAGE) || (defined(__SANITIZE_ADDRESS__) && defined(HAVE_LEAK_SANITIZER_INTERFACE)) */ -#if defined(COVERAGE) -static void sigTermHandler(int) +void doExitNicely(int exitCode) { - cleanupLuaObjects(); - pdns::coverage::dumpCoverageData(); - _exit(EXIT_SUCCESS); -} -#else -static void sigTermHandler([[maybe_unused]] int sig) -{ -#if !defined(__SANITIZE_THREAD__) - /* TSAN is rightfully unhappy about this: - WARNING: ThreadSanitizer: signal-unsafe call inside of a signal - This is not a real problem for us, as the worst case is that - we crash trying to exit, but let's try to avoid the warnings - in our tests. - */ - if (dnsdist::logging::LoggingConfiguration::getSyslog()) { - syslog(LOG_INFO, "Exiting on user request"); + if (s_exiting) { + if (dnsdist::logging::LoggingConfiguration::getSyslog()) { + syslog(LOG_INFO, "Exiting on user request"); + } + std::cout << "Exiting on user request" << std::endl; } - std::cout << "Exiting on user request" << std::endl; -#endif /* __SANITIZE_THREAD__ */ -#if defined(__SANITIZE_ADDRESS__) && defined(HAVE_LEAK_SANITIZER_INTERFACE) + +#ifdef HAVE_SYSTEMD + sd_notify(0, "STOPPING=1"); +#endif /* HAVE_SYSTEMD */ + +#if defined(COVERAGE) || (defined(__SANITIZE_ADDRESS__) && defined(HAVE_LEAK_SANITIZER_INTERFACE)) if (dnsdist::g_asyncHolder) { dnsdist::g_asyncHolder->stop(); } @@ -2796,17 +2800,36 @@ static void sigTermHandler([[maybe_unused]] int sig) for (auto& backend : dnsdist::configuration::getCurrentRuntimeConfiguration().d_backends) { backend->stop(); } +#endif { auto lock = g_lua.lock(); - cleanupLuaObjects(); + dnsdist::lua::hooks::runExitCallbacks(*lock); +#if defined(COVERAGE) || (defined(__SANITIZE_ADDRESS__) && defined(HAVE_LEAK_SANITIZER_INTERFACE)) + cleanupLuaObjects(*lock); *lock = LuaContext(); +#endif } + +#if defined(__SANITIZE_ADDRESS__) && defined(HAVE_LEAK_SANITIZER_INTERFACE) __lsan_do_leak_check(); #endif /* __SANITIZE_ADDRESS__ && HAVE_LEAK_SANITIZER_INTERFACE */ - _exit(EXIT_SUCCESS); + +#ifdef COVERAGE + pdns::coverage::dumpCoverageData(); +#endif + + /* do not call destructors, because we have some + dependencies between objects that are not trivial + to solve. + */ + _exit(exitCode); +} + +static void sigTermHandler(int /* sig */) +{ + s_exiting.store(true); } -#endif /* COVERAGE */ static void reportFeatures() { @@ -3413,12 +3436,7 @@ int main(int argc, char** argv) } // No exception was thrown infolog("Configuration '%s' OK!", cmdLine.config); -#ifdef COVERAGE - cleanupLuaObjects(); - exit(EXIT_SUCCESS); -#else - _exit(EXIT_SUCCESS); -#endif + doExitNicely(); } infolog("dnsdist %s comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it according to the terms of the GPL version 2", VERSION); @@ -3625,12 +3643,7 @@ int main(int argc, char** argv) healththread.detach(); dnsdist::console::doConsole(); } -#ifdef COVERAGE - cleanupLuaObjects(); - exit(EXIT_SUCCESS); -#else - _exit(EXIT_SUCCESS); -#endif + doExitNicely(); } catch (const LuaContext::ExecutionErrorException& e) { try { @@ -3643,29 +3656,14 @@ int main(int argc, char** argv) catch (const PDNSException& ae) { errlog("Fatal pdns error: %s", ae.reason); } -#ifdef COVERAGE - cleanupLuaObjects(); - exit(EXIT_FAILURE); -#else - _exit(EXIT_FAILURE); -#endif + doExitNicely(EXIT_FAILURE); } catch (const std::exception& e) { errlog("Fatal error: %s", e.what()); -#ifdef COVERAGE - cleanupLuaObjects(); - exit(EXIT_FAILURE); -#else - _exit(EXIT_FAILURE); -#endif + doExitNicely(EXIT_FAILURE); } catch (const PDNSException& ae) { errlog("Fatal pdns error: %s", ae.reason); -#ifdef COVERAGE - cleanupLuaObjects(); - exit(EXIT_FAILURE); -#else - _exit(EXIT_FAILURE); -#endif + doExitNicely(EXIT_FAILURE); } } diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index 8fb57ad7ef..39d36bbecb 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -2161,9 +2161,17 @@ These values can be set at configuration time via: Other functions --------------- +.. function:: addExitCallback(callback) + + .. versionadded:: 2.0.0 + + Register a Lua function to be called when :program:`dnsdist` exists. + + :param function callback: The function to be called. It takes no parameter and returns no value. + .. function:: addMaintenanceCallback(callback) - .. versionadded:: 1.10.0 + .. versionadded:: 1.9.0 Register a Lua function to be called as part of the ``maintenance`` hook, which is executed roughly every second. The function should not block for a long period of time, as it would otherwise delay the execution of the other functions registered for this hook, as well as the execution of the :func:`maintenance` function. diff --git a/regression-tests.dnsdist/test_Async.py b/regression-tests.dnsdist/test_Async.py index bb37c7ee12..474340bdf4 100644 --- a/regression-tests.dnsdist/test_Async.py +++ b/regression-tests.dnsdist/test_Async.py @@ -515,6 +515,8 @@ class TestAsyncFFI(DNSDistTest, AsyncTests): collectgarbage() end + addExitCallback(atExit) + -- this only matters for tests actually reaching the backend addAction('tcp-only.async.tests.powerdns.com', PoolAction('tcp-only', false)) addAction('cache.async.tests.powerdns.com', PoolAction('cache', false)) @@ -628,6 +630,8 @@ class TestAsyncLua(DNSDistTest, AsyncTests): collectgarbage() end + addExitCallback(atExit) + -- this only matters for tests actually reaching the backend addAction('tcp-only.async.tests.powerdns.com', PoolAction('tcp-only', false)) addAction('cache.async.tests.powerdns.com', PoolAction('cache', false))