From 71714fdc0ea01e861832ffa7ae3f5fc3a3da9f8b Mon Sep 17 00:00:00 2001 From: Dimitrios Symonidis Date: Thu, 5 Mar 2026 21:27:30 +0100 Subject: [PATCH] db: introduce read-write lock to synchronize config updates with REST API Config is reloaded from SIGHUP on shard 0 and broadcast to all shards under a write lock. REST API callers reading find_config_id acquire a read lock via value_as_json_string_for_name() and are guaranteed a consistent snapshot even when a reload is in progress. --- api/config.cc | 17 +++++++++-------- db/config.cc | 19 +++++++++++++++++++ db/config.hh | 18 ++++++++++++++++++ main.cc | 1 + 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/api/config.cc b/api/config.cc index f855d54d68..eb93df0d62 100644 --- a/api/config.cc +++ b/api/config.cc @@ -82,15 +82,16 @@ void set_config(std::shared_ptr < api_registry_builder20 > rb, http_context& ctx }); }); - cs::find_config_id.set(r, [&cfg] (const_req r) { - auto id = r.get_path_param("id"); - for (auto&& cfg_ref : cfg.values()) { - auto&& cfg = cfg_ref.get(); - if (id == cfg.name()) { - return cfg.value_as_json(); - } + cs::find_config_id.set(r, [&cfg] (std::unique_ptr req) -> future { + auto id = req->get_path_param("id"); + auto value = co_await cfg.value_as_json_string_for_name(id); + if (!value) { + throw bad_param_exception(sstring("No such config entry: ") + id); } - throw bad_param_exception(sstring("No such config entry: ") + id); + //value is already a json string + json::json_return_type ret{json::json_void()}; + ret._res = std::move(*value); + co_return ret; }); sp::get_rpc_timeout.set(r, [&cfg](const_req req) { diff --git a/db/config.cc b/db/config.cc index 3ab564b3d4..b24d62b65b 100644 --- a/db/config.cc +++ b/db/config.cc @@ -7,6 +7,7 @@ * SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.1 */ +#include #include #include @@ -19,6 +20,7 @@ #include #include +#include #include #include #include @@ -1725,6 +1727,23 @@ void db::config::maybe_in_workdir(named_value& tos, const char* sub } } +future> db::config::value_as_json_string_for_name(sstring name) const { + // Config reloads triggered by SIGHUP are applied on shard 0 and then + // broadcast to all other shards. We read the value on shard 0 under + // a read lock to guarantee a consistent snapshot — the SIGHUP handler + // holds the write lock across the entire reload-and-broadcast sequence. + co_return co_await smp::submit_to(0, [this, name = std::move(name)] () -> future> { + auto lock = co_await _config_update_lock.hold_read_lock(); + for (auto&& cfg_ref : values()) { + auto&& c = cfg_ref.get(); + if (name == c.name()) { + co_return c.value_as_json()._res; + } + } + co_return std::nullopt; + }); +} + const sstring db::config::default_tls_priority("SECURE128:-VERS-TLS1.0"); template <> diff --git a/db/config.hh b/db/config.hh index 24a572664c..52e9019028 100644 --- a/db/config.hh +++ b/db/config.hh @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -179,6 +180,16 @@ public: static fs::path get_conf_dir(); static fs::path get_conf_sub(fs::path); + future lock_for_config_update() { + return _config_update_lock.hold_write_lock(); + }; + + // Look up a config entry by name and return its JSON representation as a string. + // Runs on shard 0 under a read lock so the result is consistent with + // any in-progress SIGHUP reload + broadcast_to_all_shards() sequence. + // Returns std::nullopt if no config entry with the given name exists. + future> value_as_json_string_for_name(sstring name) const; + using string_map = std::unordered_map; //program_options::string_map; using string_list = std::vector; @@ -657,6 +668,13 @@ private: void maybe_in_workdir(named_value&, const char*); std::shared_ptr _extensions; + + // Read-write lock used to synchronize config updates (SIGHUP reload + + // broadcast to all shards) with config value readers. + // The SIGHUP handler holds the write lock across read_config() + + // broadcast_to_all_shards(). Readers acquire the read lock on shard 0 + // via value_as_json_string_for_name() so they always see a consistent snapshot. + mutable rwlock _config_update_lock; }; } diff --git a/main.cc b/main.cc index 44678c836a..95f5990b33 100644 --- a/main.cc +++ b/main.cc @@ -270,6 +270,7 @@ private: _pending = false; try { startlog.info("re-reading configuration file"); + auto lock = _cfg.lock_for_config_update().get(); read_config(_opts, _cfg).get(); _cfg.broadcast_to_all_shards().get(); startlog.info("completed re-reading configuration file");