From b525498d3951edeaf5d8255411efab4f5604b2f9 Mon Sep 17 00:00:00 2001 From: rippleitinnz Date: Tue, 19 May 2026 16:13:24 +1200 Subject: [PATCH 1/2] fix: dynamic log level update from patch.cfg without container restart When log.log_level is present in patch.cfg, apply_patch_config() now updates the live plog logger severity via plog::get()->setMaxSeverity() in addition to persisting the change to hp.cfg and the runtime cfg struct. Previously log level was only read at startup (hplog::init()) and could not be changed on a running node without a container restart. This meant operators had no way to change log verbosity on external Evernode hosts where they don't control the container lifecycle. The fix uses plog's built-in setMaxSeverity() API which is thread-safe and takes effect immediately on the next log statement. --- src/conf.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/conf.cpp b/src/conf.cpp index 47e6e696..0acae924 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -853,6 +853,29 @@ namespace conf return -1; } + // Apply log level change dynamically if present in patch config. + // Uses plog::get()->setMaxSeverity() to update the live logger without restart. + try { + if (jdoc.contains("log") && jdoc["log"].contains("log_level")) { + const std::string new_log_level = jdoc["log"]["log_level"].as(); + const std::unordered_set valid_loglevels({"dbg", "inf", "wrn", "err"}); + if (valid_loglevels.count(new_log_level) == 1) { + cfg.log.log_level = new_log_level; + cfg.log.log_level_type = get_loglevel_type(new_log_level); + temp_cfg.log.log_level = new_log_level; + temp_cfg.log.log_level_type = get_loglevel_type(new_log_level); + plog::Severity new_plog_level; + if (cfg.log.log_level_type == LOG_SEVERITY::DEBUG) new_plog_level = plog::Severity::debug; + else if (cfg.log.log_level_type == LOG_SEVERITY::INFO) new_plog_level = plog::Severity::info; + else if (cfg.log.log_level_type == LOG_SEVERITY::WARN) new_plog_level = plog::Severity::warning; + else new_plog_level = plog::Severity::error; + plog::get()->setMaxSeverity(new_plog_level); + LOG_INFO << "Log level updated dynamically to: " << new_log_level; + } + } + } catch (const std::exception &e) { + LOG_ERROR << "Error applying log level from patch config: " << e.what(); + } LOG_INFO << "Contract config updated from patch file."; return 0; } From 809160500f4dad96c51b8209c70f8d049a3362cc Mon Sep 17 00:00:00 2001 From: rippleitinnz Date: Thu, 21 May 2026 09:24:29 +1200 Subject: [PATCH 2/2] feat: dynamic mesh idle_timeout from patch.cfg without container restart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When mesh.idle_timeout is present in patch.cfg, apply_patch_config() now updates all active peer sessions via a new p2p::update_idle_timeout() function and also updates the cached metric_thresholds array for future connections. Previously mesh.idle_timeout was only read at startup (p2p::init()) and could not be changed on a running node without a container restart. This is critical for operators who need to increase roundtime beyond mesh.idle_timeout * 4 — without this fix, increasing roundtime past 480000ms (at default idle_timeout of 120000ms) causes peer disconnections and permanent consensus failure. Implementation: - comm_server.hpp: added for_each_session() template to iterate live sessions - p2p.cpp: added update_idle_timeout() which updates metric_thresholds[4] and calls set_threshold(IDLE_CONNECTION_TIMEOUT) on all active sessions - conf.cpp: reads mesh.idle_timeout from patch.cfg in apply_patch_config(), calls p2p::update_idle_timeout() when value changes Sibling PRs (part of dynamic config series): - fix/dynamic-log-level-from-patch-cfg (already raised) - feat/dynamic-user-idle-timeout-from-patch-cfg (to be raised) Fixes: mesh connections dropping during long roundtimes --- src/comm/comm_server.hpp | 11 +++++++++++ src/conf.cpp | 18 ++++++++++++++++++ src/p2p/p2p.cpp | 18 ++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/comm/comm_server.hpp b/src/comm/comm_server.hpp index c4c195ee..c4dd15cd 100644 --- a/src/comm/comm_server.hpp +++ b/src/comm/comm_server.hpp @@ -322,6 +322,17 @@ namespace comm message_processor_thread.join(); } + /** + * Iterate over all active sessions and apply a function to each. + * Used to update live session thresholds (e.g. idle_timeout) without restart. + */ + template + void for_each_session(F&& func) + { + std::scoped_lock lock(sessions_mutex); + for (T &session : sessions) + func(session); + } }; } // namespace comm diff --git a/src/conf.cpp b/src/conf.cpp index 0acae924..b01ad9f0 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -7,6 +7,9 @@ #include "ledger/ledger_mount.hpp" #include "sc/contract_mount.hpp" +// Forward declaration to avoid circular dependency with p2p/p2p.hpp +namespace p2p { void update_idle_timeout(const uint32_t timeout_ms); } + namespace conf { @@ -876,6 +879,21 @@ namespace conf } catch (const std::exception &e) { LOG_ERROR << "Error applying log level from patch config: " << e.what(); } + // Apply mesh idle timeout change dynamically if present in patch config. + // Updates all active peer sessions and future connections without restart. + try { + if (jdoc.contains("mesh") && jdoc["mesh"].contains("idle_timeout")) { + const uint32_t new_idle_timeout = jdoc["mesh"]["idle_timeout"].as(); + if (new_idle_timeout != cfg.mesh.idle_timeout) { + cfg.mesh.idle_timeout = new_idle_timeout; + temp_cfg.mesh.idle_timeout = new_idle_timeout; + p2p::update_idle_timeout(new_idle_timeout); + LOG_INFO << "Mesh idle timeout updated dynamically to: " << new_idle_timeout << "ms"; + } + } + } catch (const std::exception &e) { + LOG_ERROR << "Error applying mesh idle timeout from patch config: " << e.what(); + } LOG_INFO << "Contract config updated from patch file."; return 0; } diff --git a/src/p2p/p2p.cpp b/src/p2p/p2p.cpp index c48016fc..94972caf 100644 --- a/src/p2p/p2p.cpp +++ b/src/p2p/p2p.cpp @@ -70,6 +70,24 @@ namespace p2p } } + /** + * Update mesh idle timeout on all active peer sessions and for future connections. + * Called from conf::apply_patch_config() when mesh.idle_timeout changes in patch.cfg. + * Updates both the cached metric_thresholds array (for new sessions) and all + * existing live sessions via set_threshold() so no restart is required. + */ + void update_idle_timeout(const uint32_t timeout_ms) + { + metric_thresholds[4] = timeout_ms; + if (ctx.server.has_value()) + { + ctx.server->for_each_session([timeout_ms](peer_comm_session &session) { + session.set_threshold(comm::SESSION_THRESHOLDS::IDLE_CONNECTION_TIMEOUT, timeout_ms, 60000); + }); + LOG_INFO << "Mesh idle timeout updated dynamically to: " << timeout_ms << "ms"; + } + } + int start_peer_connections() { const uint16_t listen_port = conf::cfg.mesh.listen ? conf::cfg.mesh.port : 0;