]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add support for calling Lua methods when exiting
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 10 Mar 2025 16:15:02 +0000 (17:15 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 20 Mar 2025 12:33:02 +0000 (13:33 +0100)
pdns/dnsdistdist/dnsdist-console.cc
pdns/dnsdistdist/dnsdist-lua-hooks.cc
pdns/dnsdistdist/dnsdist-lua-hooks.hh
pdns/dnsdistdist/dnsdist-lua.cc
pdns/dnsdistdist/dnsdist.cc
pdns/dnsdistdist/docs/reference/config.rst
regression-tests.dnsdist/test_Async.py

index dcdd436cb75dde38689fc4d270c67bbc1830de3b..55bbb4ce57f6d2b3ae764baf9a9f35b0c2903e74 100644 (file)
@@ -498,6 +498,7 @@ static const std::vector<dnsdist::console::ConsoleKeyword> 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"},
index 1f92e0ccf3c29b3e7a7b6c767e569ff33f60a6c7..45cc25e1e6fba730895ff1e9d2573dbc48518625 100644 (file)
@@ -6,9 +6,11 @@
 
 namespace dnsdist::lua::hooks
 {
+using ExitCallback = std::function<void()>;
 using MaintenanceCallback = std::function<void()>;
 using TicketsKeyAddedHook = std::function<void(const std::string&, size_t)>;
 
+static LockGuarded<std::vector<ExitCallback>> s_exitCallbacks;
 static LockGuarded<std::vector<MaintenanceCallback>> 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);
index e35c0f10ac5f9b03a32500ef02fd90399d2ced61..a1b8c3ca8f96b5cdff40d5524c19d50f884af1a9 100644 (file)
@@ -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);
 }
index 0f8b0a2d53e55f8be7c39e831710935ddee6d653..67a1fc1a6b14a7ef0bb9c92100dbee4dfbf0ae32 100644 (file)
@@ -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<boost::variant<bool, std::string>> showserversopts_t;
index 6d2d0779508ea16672c5c82c05df1e563bdb7846..f14eaf38633521dbed313751489e87db2f112471 100644 (file)
@@ -2321,6 +2321,16 @@ static void secPollThread()
 }
 #endif /* DISABLE_SECPOLL */
 
+static std::atomic<bool> 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);
   }
 }
index 8fb57ad7ef1f87556088c6c8c4a0d0d896a14a9f..39d36bbecbf1f2904270436c2e825eb828004f88 100644 (file)
@@ -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.
index bb37c7ee12e8dbd15df90e1372a2c1d25e6862c6..474340bdf41583c44182ad34f55b667792dc6047 100644 (file)
@@ -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))