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 <