]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Add a pdns_control flush command, to instruct backends to write in-flight data. 16184/head
authorMiod Vallat <miod.vallat@powerdns.com>
Fri, 26 Sep 2025 09:18:02 +0000 (11:18 +0200)
committerMiod Vallat <miod.vallat@powerdns.com>
Fri, 26 Sep 2025 10:48:15 +0000 (12:48 +0200)
Signed-off-by: Miod Vallat <miod.vallat@powerdns.com>
docs/backends/lmdb.rst
docs/manpages/pdns_control.1.rst
modules/lmdbbackend/lmdbbackend.cc
modules/lmdbbackend/lmdbbackend.hh
pdns/auth-main.cc
pdns/dnsbackend.hh
pdns/dynhandler.cc
pdns/dynhandler.hh
pdns/ueberbackend.cc
pdns/ueberbackend.hh

index ad5c3818f336d291b7f7e7bf6d4d2e96963b35a4..18741ad16e2d06e15839143cba25e47954308506 100644 (file)
@@ -150,7 +150,9 @@ other changes to the domain (such as accounts) occur.
 This setting is also available in version 4.9.9.
 
 **Warning**: Running with this flag disabled will cause spurious notifications
-to be sent upon startup.
+to be sent upon startup, unless a ``flush'' command is sent using
+:doc:`pdns_control <../manpages/pdns_control.1>` before stopping the
+PowerDNS Authoritative Server.
 
 ``lmdb-lightning-stream``
 ^^^^^^^^^^^^^^^^^^^^^^^^^
index 0ffc838b51d5e8396032d37567089bf0974fe05f..f4ab9a9d7f0212ea92033560e1fd625805e186bd 100644 (file)
@@ -74,6 +74,13 @@ cycle
 Restart the nameserver so it reloads its configuration. Only works
 when the server is running in guardian mode.
 
+flush
+^^^^^
+
+Flush all backend data to stable storage.
+There is usually no such data during regular operation, unless the LMDB backend
+is used with :ref:`setting-lmdb-write-notification-update` set to ``no``.
+
 list
 ^^^^
 
index 44041b25d18607137f9dd8ef86e35a67cb688311..270ba1778343da2247d3a2275d42441c90a4c0f0 100644 (file)
@@ -669,6 +669,8 @@ bool LMDBBackend::upgradeToSchemav6(std::string& /* filename */)
 }
 
 // Serial number cache
+
+// Retrieve the serial number entry for the given domain, if any
 bool LMDBBackend::SerialCache::get(domainid_t domainid, uint32_t& serial) const
 {
   if (auto iter = d_serials.find(domainid); iter != d_serials.end()) {
@@ -678,6 +680,7 @@ bool LMDBBackend::SerialCache::get(domainid_t domainid, uint32_t& serial) const
   return false;
 }
 
+// Remove the serial number entry for the given domain
 void LMDBBackend::SerialCache::remove(domainid_t domainid)
 {
   if (auto iter = d_serials.find(domainid); iter != d_serials.end()) {
@@ -685,11 +688,25 @@ void LMDBBackend::SerialCache::remove(domainid_t domainid)
   }
 }
 
+// Create or update the serial number entry for the given domain
 void LMDBBackend::SerialCache::update(domainid_t domainid, uint32_t serial)
 {
   d_serials.insert_or_assign(domainid, serial);
 }
 
+// Return the contents of the first element and remove it
+bool LMDBBackend::SerialCache::pop(domainid_t& domainid, uint32_t& serial)
+{
+  auto iter = d_serials.begin();
+  if (iter == d_serials.end()) {
+    return false;
+  }
+  domainid = iter->first;
+  serial = iter->second;
+  (void)d_serials.erase(iter);
+  return true;
+}
+
 SharedLockGuarded<LMDBBackend::SerialCache> LMDBBackend::s_notified_serial;
 
 LMDBBackend::LMDBBackend(const std::string& suffix)
@@ -3392,6 +3409,42 @@ void LMDBBackend::rectifyZoneHook(domainid_t domain_id, bool before) const
   LMDBBackend::deleteDomainRecords(*d_rwtxn, order(domain_id), QType::NSEC3);
 }
 
+void LMDBBackend::flush()
+{
+  if (d_write_notification_update) {
+    return; // no data needs to be synchronized
+  }
+
+  // We flush in chunks of 10 domains, in order not to keep the serial number
+  // cache locked for too long.
+  while (true) {
+    unsigned int done = 0;
+    auto container = s_notified_serial.write_lock();
+    for (; done < 10; ++done) {
+      domainid_t domid{};
+      uint32_t serial{};
+      if (!container->pop(domid, serial)) {
+        break;
+      }
+      DomainInfo info;
+      if (findDomain(domid, info)) {
+        info.notified_serial = serial;
+        auto txn = d_tdomains->getRWTransaction();
+        txn.put(info, info.id);
+        txn.commit();
+      }
+      else {
+        // Domain has been removed. This should not happen because deletion
+        // is supposed to take care of removing the entry here too.
+        // Is it worth logging something here?
+      }
+    }
+    if (done == 0) {
+      break; // no more work to do!
+    }
+  }
+}
+
 class LMDBFactory : public BackendFactory
 {
 public:
index 14c78c7f1708b5fa8c67c44f08ab90b3ca9fbcd2..7c929c7132484859783f868a867bbacc2c29217d 100644 (file)
@@ -166,6 +166,8 @@ public:
 
   bool updateEmptyNonTerminals(domainid_t domain_id, set<DNSName>& insert, set<DNSName>& erase, bool remove) override;
 
+  void flush() override;
+
   // other
   string directBackendCmd(const string& query) override;
 
@@ -365,6 +367,7 @@ private:
     bool get(domainid_t domainid, uint32_t& serial) const;
     void remove(domainid_t domainid);
     void update(domainid_t domainid, uint32_t serial);
+    bool pop(domainid_t& domainid, uint32_t& serial);
 
   private:
     std::unordered_map<domainid_t, uint32_t> d_serials;
index ce1c9f2df3aabd0fa4e7ad789e304129c15dc3e4..e15cc8cf7347ea6323794f2ad508a500a7430361 100644 (file)
@@ -1456,6 +1456,7 @@ int main(int argc, char** argv)
     DynListener::registerExitFunc("QUIT", &DLRQuitHandler);
     DynListener::registerFunc("CCOUNTS", &DLCCHandler, "get cache statistics");
     DynListener::registerFunc("CURRENT-CONFIG", &DLCurrentConfigHandler, "retrieve the current configuration", "[diff]");
+    DynListener::registerFunc("FLUSH", &DLFlushHandler, "flush backend data");
     DynListener::registerFunc("LIST-ZONES", &DLListZones, "show list of zones", "[primary|secondary|native|consumer|producer]");
     DynListener::registerFunc("NOTIFY", &DLNotifyHandler, "queue a notification", "<zone>");
     DynListener::registerFunc("NOTIFY-HOST", &DLNotifyHostHandler, "notify host for specific zone", "<zone> <host>");
index 5887950625850b53e16d198ff2b31063f294f70b..999559f8ba64bc4b5e7dab74e40d8fb3d79b0208 100644 (file)
@@ -531,6 +531,10 @@ public:
     return get(rec);
   }
 
+  virtual void flush()
+  {
+  }
+
 protected:
   bool mustDo(const string& key);
   const string& getArg(const string& key);
index 212a59242e912ac88be34cbaf6750e901447d801..8403e6f732f12c6ca26e3e003108b2b08b11b87d 100644 (file)
@@ -407,6 +407,14 @@ string DLListZones(const vector<string>& parts, Utility::pid_t /* ppid */)
   return ret.str();
 }
 
+string DLFlushHandler(const vector<string>& /*parts*/, Utility::pid_t /*ppid*/)
+{
+  UeberBackend B; // NOLINT(readability-identifier-length)
+  B.flush();
+  g_log<<Logger::Error<<"Backend flush was requested"<<endl;
+  return "Ok";
+}
+
 #ifdef HAVE_P11KIT1
 extern bool PKCS11ModuleSlotLogin(const std::string& module, const string& tokenId, const std::string& pin);
 #endif
index 8c6faf0a11fa6bda2e530f88450388bbcfdfae72..ddba5c73e9f1787efa5a38f1fb0b12b60801aedd 100644 (file)
@@ -34,6 +34,7 @@ bool DLQuitPlease();
 void setStatus(const string &str);
 string DLCCHandler(const vector<string>&parts, Utility::pid_t ppid);
 string DLCurrentConfigHandler(const vector<string>&parts, Utility::pid_t ppid);
+string DLFlushHandler(const vector<string>&parts, Utility::pid_t ppid);
 string DLListZones(const vector<string>&parts, Utility::pid_t ppid);
 string DLNotifyHandler(const vector<string>&parts, Utility::pid_t ppid);
 string DLNotifyHostHandler(const vector<string>&parts, Utility::pid_t ppid);
index 064219410f71d529ae52cd814cd6eda806aecc84..e6270b190e481b809c3e10c050ac83738843592e 100644 (file)
@@ -1056,6 +1056,13 @@ unsigned int UeberBackend::getCapabilities()
   return capabilities;
 }
 
+void UeberBackend::flush()
+{
+  for (auto& backend : backends) {
+    backend->flush();
+  }
+}
+
 AtomicCounter UeberBackend::handle::instances(0);
 
 UeberBackend::handle::handle()
index db0681e7642cbece2271056231ca54f05a35f585..213754a6e49d3ad76c59702bf8424120badd1dad 100644 (file)
@@ -149,6 +149,8 @@ public:
 
   unsigned int getCapabilities();
 
+  void flush();
+
 private:
   handle d_handle;
   vector<DNSZoneRecord> d_answers;