]> git.ipfire.org Git - thirdparty/knot-dns.git/commitdiff
timers: implemented configurable periodic dump
authorLibor Peltan <libor.peltan@nic.cz>
Thu, 13 Nov 2025 16:48:34 +0000 (17:48 +0100)
committerLibor Peltan <libor.peltan@nic.cz>
Mon, 24 Nov 2025 09:53:08 +0000 (10:53 +0100)
12 files changed:
doc/reference.rst
src/knot/conf/base.c
src/knot/conf/base.h
src/knot/conf/schema.c
src/knot/conf/schema.h
src/knot/server/server.c
src/knot/server/server.h
src/knot/zone/timers.c
src/knot/zone/zone.c
tests-extra/tests/dnssec/dnskey_sync/test.py
tests-extra/tools/dnstest/server.py
tests/knot/test_confio.c

index 441c32116eeeb61edf201fbfa19577a0c90e5769..317c0474b6761481994dc4f3875e08bcce6833a8 100644 (file)
@@ -1212,6 +1212,7 @@ Configuration of databases for zone contents, DNSSEC metadata, or event timers.
      kasp-db-max-size: SIZE
      timer-db: STR
      timer-db-max-size: SIZE
+     timer-db-sync: never | shutdown | immediate | TIME
      catalog-db: str
      catalog-db-max-size: SIZE
      zone-db-listen: ADDR[@INT] | STR[@INT] ...
@@ -1321,6 +1322,24 @@ The hard limit for the timer database maximum size.
 
 *Default:* ``100M`` (100 MiB)
 
+.. _database_timer-db-sync:
+
+timer-db-sync
+-------------
+
+Specifies when zone timers should be written to the persistent timer database.
+
+Possible values:
+
+- ``never`` – Never written.
+- ``shutdown`` – Written once when the server is shut down.
+- ``immediate`` – Each zone writes its timers whenever they are modified.
+  This mode might slow down zones' events if many zones are configured.
+- `INT` – A dedicated thread continuously iterates through the configured zones
+  and writes their timers at the specified non-zero interval (in seconds).
+
+*Default:* ``shutdown``
+
 .. _database_catalog-db:
 
 catalog-db
index 23fa1433bab6fdd635af24b286c22874051d55eb..04329bf6723b4f08176bda80a79b767926bd9e2c 100644 (file)
@@ -280,6 +280,9 @@ static void init_cache(
                conf->cache.srv_has_version = false;
                conf->cache.srv_version = "Knot DNS " PACKAGE_VERSION;
        }
+
+       val = conf_get(conf, C_DB, C_TIMER_DB_SYNC);
+       conf->cache.db_timer_db_sync = conf_int(&val);
 }
 
 int conf_new(
index 675e8b956f205710cada9e3eada7acba785783e9..ec60293fcc78a77daab9bf34e6f7d4a4651d86a7 100644 (file)
@@ -150,6 +150,7 @@ typedef struct {
                uint32_t xdp_tcp_idle_close;
                uint32_t xdp_tcp_idle_reset;
                uint32_t xdp_tcp_idle_resend;
+               int32_t db_timer_db_sync;
                size_t srv_quic_max_clients;
                size_t srv_quic_obuf_max_size;
                const uint8_t *srv_nsid_data;
index d75d8922d157f6d6d8240d8123a0a8d8fc75aafc..a0d52cfb1ef202d5a719d1961134604a6dd71dd7 100644 (file)
@@ -176,6 +176,13 @@ static const knot_lookup_t journal_modes[] = {
        { 0, NULL }
 };
 
+static const knot_lookup_t timer_db_sync[] = {
+       { TIMER_DB_SYNC_IMMEDIATE, "immediate" },
+       { TIMER_DB_SYNC_NEVER,     "never" },
+       { TIMER_DB_SYNC_SHUTDOWN,  "shutdown" },
+       { 0, NULL }
+};
+
 static const knot_lookup_t catalog_roles[] = {
        { CATALOG_ROLE_NONE,      "none" },
        { CATALOG_ROLE_INTERPRET, "interpret" },
@@ -310,6 +317,8 @@ static const yp_item_t desc_database[] = {
        { C_TIMER_DB,              YP_TSTR,  YP_VSTR = { "timers" } },
        { C_TIMER_DB_MAX_SIZE,     YP_TINT,  YP_VINT = { MEGA(1), VIRT_MEM_LIMIT(GIGA(100)),
                                                         MEGA(100), YP_SSIZE } },
+       { C_TIMER_DB_SYNC,         YP_TOPTINT, YP_VOPTINT = { 1, UINT32_MAX, TIMER_DB_SYNC_SHUTDOWN,
+                                                             YP_STIME, 0, timer_db_sync } },
        { C_CATALOG_DB,            YP_TSTR,  YP_VSTR = { "catalog" } },
        { C_CATALOG_DB_MAX_SIZE,   YP_TINT,  YP_VINT = { MEGA(5), VIRT_MEM_LIMIT(GIGA(100)),
                                                         VIRT_MEM_LIMIT(GIGA(20)), YP_SSIZE } },
index 6face8f1ee62d5fcd4218f3b6069f28866c06142..30a034873958a6d9265ec9327b8144040e407e11 100644 (file)
 #define C_TIMER                        "\x05""timer"
 #define C_TIMER_DB             "\x08""timer-db"
 #define C_TIMER_DB_MAX_SIZE    "\x11""timer-db-max-size"
+#define C_TIMER_DB_SYNC                "\x0D""timer-db-sync"
 #define C_TLS                  "\x03""tls"
 #define C_TPL                  "\x08""template"
 #define C_UDP                  "\x03""udp"
@@ -264,6 +265,12 @@ enum {
        JOURNAL_MODE_ASYNC  = 1, // Asynchronous journal DB disk synchronization.
 };
 
+enum {
+       TIMER_DB_SYNC_IMMEDIATE =  0,
+       TIMER_DB_SYNC_NEVER     = -1,
+       TIMER_DB_SYNC_SHUTDOWN  = -2,
+};
+
 enum {
        ZONEFILE_LOAD_NONE  = 0,
        ZONEFILE_LOAD_DIFF  = 1,
index 31c37d1004ff1b59797e30c1c4189a5bc3589597..7c0503c11d3e8cc5eeb86d28b1c6ddc0a49aee65 100644 (file)
@@ -982,6 +982,29 @@ static int rdb_listener_run(struct dthread *thread)
 }
 #endif // ENABLE_REDIS
 
+static int timer_db_do_sync(struct dthread *thread)
+{
+       server_t *s = thread->data;
+
+       while (thread->state & ThreadActive) {
+               int ret = zone_timers_write_all(&s->timerdb, s->zone_db);
+               if (ret == KNOT_EOK) {
+                       log_info("updated persistent timer DB");
+               } else {
+                       log_error("failed to update persistent timer DB (%s)", knot_strerror(ret));
+               }
+
+               if (conf()->cache.db_timer_db_sync <= 0) {
+                       break;
+               }
+               /* NOTE the following sleep() may be interrupted by any signal, including
+                  SIGALRM during dt_stop() in case of server_stop() or server_reconfigure(). */
+               sleep(conf()->cache.db_timer_db_sync);
+       }
+
+       return KNOT_EOK;
+}
+
 int server_init(server_t *server, int bg_workers)
 {
        if (server == NULL) {
@@ -1057,12 +1080,14 @@ void server_deinit(server_t *server)
        zone_backups_deinit(&server->backup_ctxs);
 
        /* Save zone timers. */
-       if (server->zone_db != NULL) {
+       bool should_sync = (conf()->cache.db_timer_db_sync == TIMER_DB_SYNC_SHUTDOWN ||
+                           conf()->cache.db_timer_db_sync > 0);
+       if (should_sync && server->zone_db != NULL) {
                log_info("updating persistent timer DB");
                int ret = zone_timers_write_all(&server->timerdb, server->zone_db);
                if (ret != KNOT_EOK) {
-                       log_warning("failed to update persistent timer DB (%s)",
-                                   knot_strerror(ret));
+                       log_error("failed to update persistent timer DB (%s)",
+                                 knot_strerror(ret));
                }
        }
 
@@ -1072,6 +1097,9 @@ void server_deinit(server_t *server)
        /* Free threads and event handlers. */
        worker_pool_destroy(server->workers);
 
+       /* Free eventual timer DB syncing thread. */
+       dt_delete(&server->timerdb_sync);
+
        /* Free optional zone DB event thread. */
        dt_delete(&server->rdb_events);
 
@@ -1191,6 +1219,9 @@ int server_start(server_t *server, bool answering)
        /* Start workers. */
        worker_pool_start(server->workers);
 
+       /* Start timer DB syncing thread. NOTE ignoring return code including KNOT_EINVAL when disabled (NULL). */
+       dt_start(server->timerdb_sync);
+
        /* Start zone DB event loop. */
        dt_start(server->rdb_events);
 
@@ -1582,6 +1613,8 @@ void server_stop(server_t *server)
        evsched_stop(&server->sched);
        /* Mark the server is shutting down. */
        server->state |= ServerShutting;
+       /* Stop timer DB syncing thread */
+       dt_stop(server->timerdb_sync);
        /* Interrupt background workers. */
        worker_pool_stop(server->workers);
 
@@ -1653,6 +1686,22 @@ static int reconfigure_timer_db(conf_t *conf, server_t *server)
        conf_val_t timer_size = conf_db_param(conf, C_TIMER_DB_MAX_SIZE);
        int ret = knot_lmdb_reconfigure(&server->timerdb, timer_dir, conf_int(&timer_size), 0);
        free(timer_dir);
+       if (ret != KNOT_EOK) {
+               return ret;
+       }
+
+       bool should_sync = (conf->cache.db_timer_db_sync > 0);
+       bool exists_sync = (server->timerdb_sync != NULL);
+       if (should_sync && !exists_sync) {
+               server->timerdb_sync = dt_create(1, timer_db_do_sync, NULL, server);
+               if (server->timerdb_sync == NULL) {
+                       return KNOT_ENOMEM;
+               }
+       } else if (!should_sync && exists_sync) {
+               dt_stop(server->timerdb_sync);
+               dt_delete(&server->timerdb_sync);
+       }
+
        return ret;
 }
 
index 5faf0102101bdef88ef5692c4b5adb8318744f8c..ee881841865e170628e78802f958ba3b3cb4b6ca 100644 (file)
@@ -108,6 +108,9 @@ typedef struct server {
        /*! \brief Background jobs. */
        worker_pool_t *workers;
 
+       /*! \brief TimerDB syncing thread. */
+       dt_unit_t *timerdb_sync;
+
        /*! \brief Zone DB event loop context. */
        dt_unit_t *rdb_events;
        struct redisContext *rdb_ctx;
index e920f11d128894779e6de6251c85abc8610909b6..72f4cdbe9201cba8e187fd7a84e8c162f9e63c04 100644 (file)
@@ -8,6 +8,8 @@
 #include "contrib/wire_ctx.h"
 #include "knot/zone/zonedb.h"
 
+#include <urcu.h>
+
 /*
  * # Timer database
  *
@@ -208,7 +210,10 @@ int zone_timers_write(knot_lmdb_db_t *db, const knot_dname_t *zone,
 
 static void txn_zone_write(zone_t *z, knot_lmdb_txn_t *txn)
 {
-       txn_write_timers(txn, z->name, z->timers_static);
+       rcu_read_lock();
+       zone_timers_t *t = z->timers_static;
+       txn_write_timers(txn, z->name, t);
+       rcu_read_unlock();
 }
 
 int zone_timers_write_all(knot_lmdb_db_t *db, knot_zonedb_t *zonedb)
index 01e03f83d5dd0164325a80d70038574d4e9503b7..8ac063404096592d4d28e19234ade6e6f4c62b85 100644 (file)
@@ -746,6 +746,14 @@ void zone_timers_commit(zone_t *zone)
                cleanup->timers = old_static;
                call_rcu((struct rcu_head *)cleanup, timers_cleanup);
        }
+
+       if (conf()->cache.db_timer_db_sync == TIMER_DB_SYNC_IMMEDIATE) {
+               int ret = zone_timers_write(&zone->server->timerdb, zone->name, zone->timers);
+               if (ret != KNOT_EOK) {
+                       log_zone_error(zone->name, "failed to update persistent timer DB (%s)",
+                                      knot_strerror(ret));
+               }
+       }
 }
 
 static int try_remote(conf_t *conf, zone_t *zone, zone_master_cb callback,
index b438799ab68e86414365d93665a0ab885a23502a..40d96e5005d6499c3a5078d38d49d61ab8bf0400 100644 (file)
@@ -29,7 +29,8 @@ def detect_ddns_deadlock(server):
     lastline=""
     with open(server.fout, "r") as fl:
         for line in fl:
-            lastline = line
+            if "persistent" not in line:
+                lastline = line
 
     #detect recent (any) activity in the logfile so that servers settle down before equivalence test
     if now_hms(0) in lastline:
index deda3c62114d0e067302fb4aabe4bf06cd72b80a..8de79ec2e3ada6ca01d062ce5c01a90a2ec3a3b6 100644 (file)
@@ -1692,6 +1692,7 @@ class Knot(Server):
         s.item_str("kasp-db-max-size", self.kasp_db_size)
         s.item_str("journal-db-max-size", self.journal_db_size)
         s.item_str("timer-db-max-size", self.timer_db_size)
+        s.item_str("timer-db-sync", random.choice(["shutdown", "immediate", "5", "3600"]))
         s.item_str("catalog-db-max-size", self.catalog_db_size)
         if self.redis is not None:
             tls = random.choice([True, False])
index 7c8251728c01aab480e5baac90c06cf87dfbb04f..8e0a49bed52e09e4a853341bb9cb5c642e351288 100644 (file)
@@ -883,6 +883,7 @@ static void test_conf_io_list(void)
        ref = "server\n"
              "xdp\n"
              "control\n"
+             "database\n"
              "remote\n"
              "template\n"
              "zone\n"
@@ -1013,6 +1014,11 @@ static const yp_item_t desc_remote[] = {
        { NULL }
 };
 
+static const yp_item_t desc_database[] = {
+       { C_TIMER_DB_SYNC, YP_TOPTINT, YP_VNONE },
+       { NULL }
+};
+
 static const knot_lookup_t opts[] = {
        { 1, "opt1" },
        { 2, "opt2" },
@@ -1048,6 +1054,7 @@ const yp_item_t test_schema[] = {
        { C_SRV,  YP_TGRP, YP_VGRP = { desc_server } },
        { C_XDP,  YP_TGRP, YP_VGRP = { desc_xdp } },
        { C_CTL,  YP_TGRP, YP_VGRP = { desc_control } },
+       { C_DB,   YP_TGRP, YP_VGRP = { desc_database } },
        { C_RMT,  YP_TGRP, YP_VGRP = { desc_remote }, YP_FMULTI, { check_remote } },
        { C_TPL,  YP_TGRP, YP_VGRP = { desc_template }, YP_FMULTI, { check_template } },
        { C_ZONE, YP_TGRP, YP_VGRP = { desc_zone }, YP_FMULTI, { check_zone } },