From 9a9168f823cb40479efa1f89c940b671cfa88e87 Mon Sep 17 00:00:00 2001 From: dsiganos Date: Wed, 5 May 2021 10:48:23 +0100 Subject: [PATCH] Sighup reload bandwidth params (#3257) Feature: reload bandwidth parameters from config file on receipt of SIGHUP signal This commit does not get compiled in the Windows version of the software. The config keys that are read and updated are: node.bandwidth_limit node.bandwidth_limit_burst_ratio --- nano/core_test/network.cpp | 16 ++++++++ nano/core_test/utility.cpp | 34 ++++++++++++++++ nano/lib/rate_limiting.cpp | 25 ++++++++---- nano/lib/rate_limiting.hpp | 3 ++ nano/nano_node/daemon.cpp | 24 +++++++++++ nano/node/network.cpp | 5 +++ nano/node/network.hpp | 1 + nano/node/node.cpp | 8 ++++ nano/node/node.hpp | 1 + nano/node/transport/transport.cpp | 5 +++ nano/node/transport/transport.hpp | 1 + systest/set_bandwidth_params.sh | 68 +++++++++++++++++++++++++++++++ 12 files changed, 182 insertions(+), 9 deletions(-) create mode 100755 systest/set_bandwidth_params.sh diff --git a/nano/core_test/network.cpp b/nano/core_test/network.cpp index 71f3d19f..e203842f 100644 --- a/nano/core_test/network.cpp +++ b/nano/core_test/network.cpp @@ -937,6 +937,22 @@ TEST (network, bandwidth_limiter) channel2->send (message, nullptr, nano::buffer_drop_policy::no_limiter_drop); ASSERT_TIMELY (1s, 1 == node.stats.count (nano::stat::type::drop, nano::stat::detail::publish, nano::stat::dir::out)); + // change the bandwidth settings, 2 packets will be dropped + node.network.set_bandwidth_params (1.1, message_size * 2); + channel1->send (message); + channel2->send (message); + channel1->send (message); + channel2->send (message); + ASSERT_TIMELY (1s, 3 == node.stats.count (nano::stat::type::drop, nano::stat::detail::publish, nano::stat::dir::out)); + + // change the bandwidth settings, no packet will be dropped + node.network.set_bandwidth_params (4, message_size); + channel1->send (message); + channel2->send (message); + channel1->send (message); + channel2->send (message); + ASSERT_TIMELY (1s, 3 == node.stats.count (nano::stat::type::drop, nano::stat::detail::publish, nano::stat::dir::out)); + node.stop (); } diff --git a/nano/core_test/utility.cpp b/nano/core_test/utility.cpp index 9cff9bdd..a14475fa 100644 --- a/nano/core_test/utility.cpp +++ b/nano/core_test/utility.cpp @@ -51,6 +51,40 @@ TEST (rate, network) ASSERT_FALSE (bucket.try_consume (1)); } +TEST (rate, reset) +{ + nano::rate::token_bucket bucket (0, 0); + + // consume lots of tokens, buckets should be unlimited + ASSERT_TRUE (bucket.try_consume (1000000)); + ASSERT_TRUE (bucket.try_consume (1000000)); + + // set bucket to be limited + bucket.reset (1000, 1000); + ASSERT_FALSE (bucket.try_consume (1001)); + ASSERT_TRUE (bucket.try_consume (1000)); + ASSERT_FALSE (bucket.try_consume (1000)); + std::this_thread::sleep_for (2ms); + ASSERT_TRUE (bucket.try_consume (2)); + + // reduce the limit + bucket.reset (100, 100 * 1000); + ASSERT_FALSE (bucket.try_consume (101)); + ASSERT_TRUE (bucket.try_consume (100)); + std::this_thread::sleep_for (1ms); + ASSERT_TRUE (bucket.try_consume (100)); + + // increase the limit + bucket.reset (2000, 1); + ASSERT_FALSE (bucket.try_consume (2001)); + ASSERT_TRUE (bucket.try_consume (2000)); + + // back to unlimited + bucket.reset (0, 0); + ASSERT_TRUE (bucket.try_consume (1000000)); + ASSERT_TRUE (bucket.try_consume (1000000)); +} + TEST (rate, unlimited) { nano::rate::token_bucket bucket (0, 0); diff --git a/nano/lib/rate_limiting.cpp b/nano/lib/rate_limiting.cpp index 5e2a0930..a236ff25 100644 --- a/nano/lib/rate_limiting.cpp +++ b/nano/lib/rate_limiting.cpp @@ -6,15 +6,7 @@ nano::rate::token_bucket::token_bucket (size_t max_token_count_a, size_t refill_rate_a) { - // A token count of 0 indicates unlimited capacity. We use 1e9 as - // a sentinel, allowing largest burst to still be computed. - if (max_token_count_a == 0 || refill_rate_a == 0) - { - refill_rate_a = max_token_count_a = static_cast (1e9); - } - max_token_count = smallest_size = current_size = max_token_count_a; - refill_rate = refill_rate_a; - last_refill = std::chrono::steady_clock::now (); + reset (max_token_count_a, refill_rate_a); } bool nano::rate::token_bucket::try_consume (unsigned tokens_required_a) @@ -51,3 +43,18 @@ size_t nano::rate::token_bucket::largest_burst () const nano::lock_guard lk (bucket_mutex); return max_token_count - smallest_size; } + +void nano::rate::token_bucket::reset (size_t max_token_count_a, size_t refill_rate_a) +{ + nano::lock_guard lk (bucket_mutex); + + // A token count of 0 indicates unlimited capacity. We use 1e9 as + // a sentinel, allowing largest burst to still be computed. + if (max_token_count_a == 0 || refill_rate_a == 0) + { + refill_rate_a = max_token_count_a = static_cast (1e9); + } + max_token_count = smallest_size = current_size = max_token_count_a; + refill_rate = refill_rate_a; + last_refill = std::chrono::steady_clock::now (); +} diff --git a/nano/lib/rate_limiting.hpp b/nano/lib/rate_limiting.hpp index 6dd25538..0ffdfb06 100644 --- a/nano/lib/rate_limiting.hpp +++ b/nano/lib/rate_limiting.hpp @@ -41,6 +41,9 @@ namespace rate /** Returns the largest burst observed */ size_t largest_burst () const; + /** Update the max_token_count and/or refill_rate_a parameters */ + void reset (size_t max_token_count_a, size_t refill_rate_a); + private: void refill (); size_t max_token_count; diff --git a/nano/nano_node/daemon.cpp b/nano/nano_node/daemon.cpp index f172473b..0e9ccb37 100644 --- a/nano/nano_node/daemon.cpp +++ b/nano/nano_node/daemon.cpp @@ -21,6 +21,21 @@ namespace volatile sig_atomic_t sig_int_or_term = 0; } +static void load_and_set_bandwidth_params (std::shared_ptr const & node, boost::filesystem::path const & data_path, nano::node_flags const & flags) +{ + nano::daemon_config config (data_path); + + auto error = nano::read_node_config_toml (data_path, config, flags.config_overrides); + if (!error) + { + error = nano::flags_config_conflicts (flags, config.node); + if (!error) + { + node->set_bandwidth_params (config.node.bandwidth_limit, config.node.bandwidth_limit_burst_ratio); + } + } +} + void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano::node_flags const & flags) { // Override segmentation fault and aborting. @@ -141,6 +156,15 @@ void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano:: // sigterm is less likely to come in bunches so only trap it once sigman.register_signal_handler (SIGTERM, &nano::signal_handler, false); +#ifndef _WIN32 + // on sighup we should reload the bandwidth parameters + std::function sighup_signal_handler ([&node, &data_path, &flags] (int signum) { + debug_assert (signum == SIGHUP); + load_and_set_bandwidth_params (node, data_path, flags); + }); + sigman.register_signal_handler (SIGHUP, sighup_signal_handler, true); +#endif + runner = std::make_unique (io_ctx, node->config.io_threads); runner->join (); diff --git a/nano/node/network.cpp b/nano/node/network.cpp index 2a8b53ae..60ae608d 100644 --- a/nano/node/network.cpp +++ b/nano/node/network.cpp @@ -795,6 +795,11 @@ void nano::network::erase (nano::transport::channel const & channel_a) } } +void nano::network::set_bandwidth_params (double limit_burst_ratio_a, size_t limit_a) +{ + limiter.reset (limit_burst_ratio_a, limit_a); +} + nano::message_buffer_manager::message_buffer_manager (nano::stat & stats_a, size_t size, size_t count) : stats (stats_a), free (count), diff --git a/nano/node/network.hpp b/nano/node/network.hpp index 9c7f15a1..6ebd1a65 100644 --- a/nano/node/network.hpp +++ b/nano/node/network.hpp @@ -179,6 +179,7 @@ public: float size_sqrt () const; bool empty () const; void erase (nano::transport::channel const &); + void set_bandwidth_params (double, size_t); nano::message_buffer_manager buffer_container; boost::asio::ip::udp::resolver resolver; std::vector packet_processing_threads; diff --git a/nano/node/node.cpp b/nano/node/node.cpp index c6193d79..e2cd0665 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -1438,6 +1438,14 @@ bool nano::node::epoch_upgrader (nano::raw_key const & prv_a, nano::epoch epoch_ return error; } +void nano::node::set_bandwidth_params (size_t limit, double ratio) +{ + config.bandwidth_limit_burst_ratio = ratio; + config.bandwidth_limit = limit; + network.set_bandwidth_params (limit, ratio); + logger.always_log (boost::str (boost::format ("set_bandwidth_params(%1%, %2%)") % limit % ratio)); +} + void nano::node::epoch_upgrader_impl (nano::raw_key const & prv_a, nano::epoch epoch_a, uint64_t count_limit, uint64_t threads) { nano::thread_role::set (nano::thread_role::name::epoch_upgrader); diff --git a/nano/node/node.hpp b/nano/node/node.hpp index 4d977854..5261f898 100644 --- a/nano/node/node.hpp +++ b/nano/node/node.hpp @@ -148,6 +148,7 @@ public: bool online () const; bool init_error () const; bool epoch_upgrader (nano::raw_key const &, nano::epoch, uint64_t, uint64_t); + void set_bandwidth_params (size_t limit, double ratio); std::pair get_bootstrap_weights () const; void populate_backlog (); nano::write_database_queue write_database_queue; diff --git a/nano/node/transport/transport.cpp b/nano/node/transport/transport.cpp index 52557436..7637af37 100644 --- a/nano/node/transport/transport.cpp +++ b/nano/node/transport/transport.cpp @@ -262,3 +262,8 @@ bool nano::bandwidth_limiter::should_drop (const size_t & message_size_a) { return !bucket.try_consume (nano::narrow_cast (message_size_a)); } + +void nano::bandwidth_limiter::reset (const double limit_burst_ratio_a, const size_t limit_a) +{ + bucket.reset (static_cast (limit_a * limit_burst_ratio_a), limit_a); +} diff --git a/nano/node/transport/transport.hpp b/nano/node/transport/transport.hpp index cd2e5c5b..5fb8acef 100644 --- a/nano/node/transport/transport.hpp +++ b/nano/node/transport/transport.hpp @@ -14,6 +14,7 @@ public: // initialize with limit 0 = unbounded bandwidth_limiter (const double, const size_t); bool should_drop (const size_t &); + void reset (const double, const size_t); private: nano::rate::token_bucket bucket; diff --git a/systest/set_bandwidth_params.sh b/systest/set_bandwidth_params.sh new file mode 100755 index 00000000..4c02291a --- /dev/null +++ b/systest/set_bandwidth_params.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +# the caller should set the env var NANO_NODE_EXE to point to the nano_node executable +# if NANO_NODE_EXE is unser ot empty then "../../build/nano_node" is used +NANO_NODE_EXE=${NANO_NODE_EXE:-../../build/nano_node} + +mkdir -p data/log +rm data/log/log_*.log + +# start nano_node and store its pid so we can later send it +# the SIGHUP signal and so we can terminate it +echo start nano_node +$NANO_NODE_EXE --daemon --data_path data & +pid=$! +echo pid=$pid + +# wait for the node to start-up +sleep 2 + +# set bandwidth params 42 and 43 in the config file +cat > data/config-node.toml < data/config-node.toml <