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.
This commit is contained in:
Dimitrios Symonidis
2026-03-05 21:27:30 +01:00
parent 0ae22a09d4
commit 71714fdc0e
4 changed files with 47 additions and 8 deletions

View File

@@ -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<http::request> req) -> future<json::json_return_type> {
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) {

View File

@@ -7,6 +7,7 @@
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.1
*/
#include <optional>
#include <unordered_map>
#include <sstream>
@@ -19,6 +20,7 @@
#include <seastar/core/coroutine.hh>
#include <seastar/core/format.hh>
#include <seastar/core/smp.hh>
#include <seastar/core/sstring.hh>
#include <seastar/json/json_elements.hh>
#include <seastar/util/log.hh>
@@ -1725,6 +1727,23 @@ void db::config::maybe_in_workdir(named_value<string_list>& tos, const char* sub
}
}
future<std::optional<sstring>> 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<std::optional<sstring>> {
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 <>

View File

@@ -12,6 +12,7 @@
#include <unordered_map>
#include <seastar/core/sstring.hh>
#include <seastar/core/rwlock.hh>
#include <seastar/util/program-options.hh>
#include <seastar/util/log.hh>
@@ -179,6 +180,16 @@ public:
static fs::path get_conf_dir();
static fs::path get_conf_sub(fs::path);
future<rwlock::holder> 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<std::optional<sstring>> value_as_json_string_for_name(sstring name) const;
using string_map = std::unordered_map<sstring, sstring>;
//program_options::string_map;
using string_list = std::vector<sstring>;
@@ -657,6 +668,13 @@ private:
void maybe_in_workdir(named_value<string_list>&, const char*);
std::shared_ptr<db::extensions> _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;
};
}

View File

@@ -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");