Close connections without holding locks
This commit is contained in:
parent
5bb0ac41b5
commit
0357518b8b
5 changed files with 104 additions and 61 deletions
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
@ -65,6 +66,28 @@ size_t erase_if (Container & container, Pred pred)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erase elements from container when predicate returns true and return erased elements as a std::deque
|
||||||
|
*/
|
||||||
|
template <class Container, class Pred>
|
||||||
|
std::deque<typename Container::value_type> erase_if_and_collect (Container & container, Pred pred)
|
||||||
|
{
|
||||||
|
std::deque<typename Container::value_type> removed_elements;
|
||||||
|
for (auto it = container.begin (); it != container.end ();)
|
||||||
|
{
|
||||||
|
if (pred (*it))
|
||||||
|
{
|
||||||
|
removed_elements.push_back (*it);
|
||||||
|
it = container.erase (it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed_elements;
|
||||||
|
}
|
||||||
|
|
||||||
/** Safe narrowing cast which silences warnings and asserts on data loss in debug builds. This is optimized away. */
|
/** Safe narrowing cast which silences warnings and asserts on data loss in debug builds. This is optimized away. */
|
||||||
template <typename TARGET_TYPE, typename SOURCE_TYPE>
|
template <typename TARGET_TYPE, typename SOURCE_TYPE>
|
||||||
constexpr TARGET_TYPE narrow_cast (SOURCE_TYPE const & val)
|
constexpr TARGET_TYPE narrow_cast (SOURCE_TYPE const & val)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#include <nano/node/node.hpp>
|
#include <nano/node/node.hpp>
|
||||||
#include <nano/node/transport/tcp_channels.hpp>
|
#include <nano/node/transport/tcp_channels.hpp>
|
||||||
|
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* tcp_channels
|
* tcp_channels
|
||||||
*/
|
*/
|
||||||
|
|
@ -308,7 +310,7 @@ bool nano::transport::tcp_channels::track_reachout (nano::endpoint const & endpo
|
||||||
|
|
||||||
void nano::transport::tcp_channels::purge (std::chrono::steady_clock::time_point cutoff_deadline)
|
void nano::transport::tcp_channels::purge (std::chrono::steady_clock::time_point cutoff_deadline)
|
||||||
{
|
{
|
||||||
nano::lock_guard<nano::mutex> lock{ mutex };
|
auto channels_l = all_channels ();
|
||||||
|
|
||||||
auto should_close = [this, cutoff_deadline] (auto const & channel) {
|
auto should_close = [this, cutoff_deadline] (auto const & channel) {
|
||||||
// Remove channels that haven't successfully sent a message within the cutoff time
|
// Remove channels that haven't successfully sent a message within the cutoff time
|
||||||
|
|
@ -332,27 +334,35 @@ void nano::transport::tcp_channels::purge (std::chrono::steady_clock::time_point
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto const & entry : channels)
|
// Close stale channels without holding the mutex
|
||||||
|
for (auto const & channel : channels_l)
|
||||||
{
|
{
|
||||||
if (should_close (entry.channel))
|
if (should_close (channel))
|
||||||
{
|
{
|
||||||
entry.channel->close ();
|
channel->close ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
erase_if (channels, [this] (auto const & entry) {
|
nano::unique_lock<nano::mutex> lock{ mutex };
|
||||||
if (!entry.channel->alive ())
|
|
||||||
{
|
|
||||||
node.logger.debug (nano::log::type::tcp_channels, "Removing dead channel: {}", entry.channel->to_string ());
|
|
||||||
entry.channel->close ();
|
|
||||||
return true; // Erase
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove keepalive attempt tracking for attempts older than cutoff
|
// Remove keepalive attempt tracking for attempts older than cutoff
|
||||||
auto attempts_cutoff (attempts.get<last_attempt_tag> ().lower_bound (cutoff_deadline));
|
auto attempts_cutoff (attempts.get<last_attempt_tag> ().lower_bound (cutoff_deadline));
|
||||||
attempts.get<last_attempt_tag> ().erase (attempts.get<last_attempt_tag> ().begin (), attempts_cutoff);
|
attempts.get<last_attempt_tag> ().erase (attempts.get<last_attempt_tag> ().begin (), attempts_cutoff);
|
||||||
|
|
||||||
|
// Erase dead channels from list, but close them outside of the lock
|
||||||
|
auto erased_connections = erase_if_and_collect (channels, [this] (auto const & entry) {
|
||||||
|
return !entry.channel->alive ();
|
||||||
|
});
|
||||||
|
|
||||||
|
lock.unlock ();
|
||||||
|
|
||||||
|
for (auto const & connection : erased_connections)
|
||||||
|
{
|
||||||
|
node.stats.inc (nano::stat::type::tcp_channels, nano::stat::detail::erase_dead);
|
||||||
|
node.logger.debug (nano::log::type::tcp_channels, "Removing dead channel: {}", connection.channel->to_string ());
|
||||||
|
|
||||||
|
connection.channel->close ();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void nano::transport::tcp_channels::keepalive ()
|
void nano::transport::tcp_channels::keepalive ()
|
||||||
|
|
@ -437,6 +447,27 @@ bool nano::transport::tcp_channels::start_tcp (nano::endpoint const & endpoint)
|
||||||
return node.tcp_listener.connect (endpoint.address (), endpoint.port ());
|
return node.tcp_listener.connect (endpoint.address (), endpoint.port ());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto nano::transport::tcp_channels::all_sockets () const -> std::deque<std::shared_ptr<tcp_socket>>
|
||||||
|
{
|
||||||
|
nano::lock_guard<nano::mutex> lock{ mutex };
|
||||||
|
auto r = channels | std::views::transform ([] (auto const & entry) { return entry.socket; });
|
||||||
|
return { r.begin (), r.end () };
|
||||||
|
}
|
||||||
|
|
||||||
|
auto nano::transport::tcp_channels::all_servers () const -> std::deque<std::shared_ptr<tcp_server>>
|
||||||
|
{
|
||||||
|
nano::lock_guard<nano::mutex> lock{ mutex };
|
||||||
|
auto r = channels | std::views::transform ([] (auto const & entry) { return entry.server; });
|
||||||
|
return { r.begin (), r.end () };
|
||||||
|
}
|
||||||
|
|
||||||
|
auto nano::transport::tcp_channels::all_channels () const -> std::deque<std::shared_ptr<tcp_channel>>
|
||||||
|
{
|
||||||
|
nano::lock_guard<nano::mutex> lock{ mutex };
|
||||||
|
auto r = channels | std::views::transform ([] (auto const & entry) { return entry.channel; });
|
||||||
|
return { r.begin (), r.end () };
|
||||||
|
}
|
||||||
|
|
||||||
nano::container_info nano::transport::tcp_channels::container_info () const
|
nano::container_info nano::transport::tcp_channels::container_info () const
|
||||||
{
|
{
|
||||||
nano::lock_guard<nano::mutex> guard{ mutex };
|
nano::lock_guard<nano::mutex> guard{ mutex };
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,10 @@ public:
|
||||||
// Connection start
|
// Connection start
|
||||||
bool start_tcp (nano::endpoint const &);
|
bool start_tcp (nano::endpoint const &);
|
||||||
|
|
||||||
|
std::deque<std::shared_ptr<tcp_socket>> all_sockets () const;
|
||||||
|
std::deque<std::shared_ptr<tcp_server>> all_servers () const;
|
||||||
|
std::deque<std::shared_ptr<tcp_channel>> all_channels () const;
|
||||||
|
|
||||||
nano::container_info container_info () const;
|
nano::container_info container_info () const;
|
||||||
|
|
||||||
private: // Dependencies
|
private: // Dependencies
|
||||||
|
|
|
||||||
|
|
@ -153,14 +153,8 @@ void nano::transport::tcp_listener::stop ()
|
||||||
|
|
||||||
for (auto & connection : connections_l)
|
for (auto & connection : connections_l)
|
||||||
{
|
{
|
||||||
if (auto socket = connection.socket.lock ())
|
connection.socket->close ();
|
||||||
{
|
connection.server->stop ();
|
||||||
socket->close ();
|
|
||||||
}
|
|
||||||
if (auto server = connection.server.lock ())
|
|
||||||
{
|
|
||||||
server->stop ();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug (nano::log::type::tcp_listener, "Stopped");
|
logger.debug (nano::log::type::tcp_listener, "Stopped");
|
||||||
|
|
@ -173,35 +167,40 @@ void nano::transport::tcp_listener::run_cleanup ()
|
||||||
{
|
{
|
||||||
stats.inc (nano::stat::type::tcp_listener, nano::stat::detail::cleanup);
|
stats.inc (nano::stat::type::tcp_listener, nano::stat::detail::cleanup);
|
||||||
|
|
||||||
cleanup ();
|
|
||||||
timeout ();
|
timeout ();
|
||||||
|
purge (lock);
|
||||||
|
debug_assert (!lock.owns_lock ());
|
||||||
|
lock.lock ();
|
||||||
|
|
||||||
condition.wait_for (lock, 1s, [this] () { return stopped.load (); });
|
condition.wait_for (lock, 1s, [this] () { return stopped.load (); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void nano::transport::tcp_listener::cleanup ()
|
void nano::transport::tcp_listener::purge (nano::unique_lock<nano::mutex> & lock)
|
||||||
{
|
{
|
||||||
|
debug_assert (lock.owns_lock ());
|
||||||
debug_assert (!mutex.try_lock ());
|
debug_assert (!mutex.try_lock ());
|
||||||
|
|
||||||
// Erase dead connections
|
|
||||||
erase_if (connections, [this] (auto const & connection) {
|
|
||||||
if (connection.socket.expired () && connection.server.expired ())
|
|
||||||
{
|
|
||||||
stats.inc (nano::stat::type::tcp_listener, nano::stat::detail::erase_dead);
|
|
||||||
logger.debug (nano::log::type::tcp_listener, "Evicting dead connection: {}", connection.endpoint);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Erase completed attempts
|
// Erase completed attempts
|
||||||
erase_if (attempts, [this] (auto const & attempt) {
|
erase_if (attempts, [this] (auto const & attempt) {
|
||||||
return attempt.task.ready ();
|
return attempt.task.ready ();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Erase dead connections
|
||||||
|
auto erased_connections = erase_if_and_collect (connections, [this] (auto const & connection) {
|
||||||
|
return !connection.socket->alive ();
|
||||||
|
});
|
||||||
|
|
||||||
|
lock.unlock ();
|
||||||
|
|
||||||
|
for (auto const & connection : erased_connections)
|
||||||
|
{
|
||||||
|
stats.inc (nano::stat::type::tcp_listener, nano::stat::detail::erase_dead);
|
||||||
|
logger.debug (nano::log::type::tcp_listener, "Evicting dead connection: {}", connection.endpoint);
|
||||||
|
|
||||||
|
connection.socket->close ();
|
||||||
|
connection.server->stop ();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void nano::transport::tcp_listener::timeout ()
|
void nano::transport::tcp_listener::timeout ()
|
||||||
|
|
@ -215,7 +214,7 @@ void nano::transport::tcp_listener::timeout ()
|
||||||
{
|
{
|
||||||
if (!attempt.task.ready () && attempt.start < cutoff)
|
if (!attempt.task.ready () && attempt.start < cutoff)
|
||||||
{
|
{
|
||||||
attempt.task.cancel ();
|
attempt.task.cancel (); // Cancel is non-blocking and will return immediately, safe to call under lock
|
||||||
|
|
||||||
stats.inc (nano::stat::type::tcp_listener, nano::stat::detail::attempt_timeout);
|
stats.inc (nano::stat::type::tcp_listener, nano::stat::detail::attempt_timeout);
|
||||||
logger.debug (nano::log::type::tcp_listener, "Connection attempt timed out: {} (started {}s ago)",
|
logger.debug (nano::log::type::tcp_listener, "Connection attempt timed out: {} (started {}s ago)",
|
||||||
|
|
@ -431,7 +430,7 @@ auto nano::transport::tcp_listener::accept_one (asio::ip::tcp::socket raw_socket
|
||||||
return { accept_result::accepted, socket, server };
|
return { accept_result::accepted, socket, server };
|
||||||
}
|
}
|
||||||
|
|
||||||
auto nano::transport::tcp_listener::check_limits (asio::ip::address const & ip, connection_type type) -> accept_result
|
auto nano::transport::tcp_listener::check_limits (asio::ip::address const & ip, connection_type type) const -> accept_result
|
||||||
{
|
{
|
||||||
debug_assert (!mutex.try_lock ());
|
debug_assert (!mutex.try_lock ());
|
||||||
|
|
||||||
|
|
@ -440,8 +439,6 @@ auto nano::transport::tcp_listener::check_limits (asio::ip::address const & ip,
|
||||||
return accept_result::rejected;
|
return accept_result::rejected;
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup ();
|
|
||||||
|
|
||||||
if (node.network.excluded_peers.check (ip)) // true => error
|
if (node.network.excluded_peers.check (ip)) // true => error
|
||||||
{
|
{
|
||||||
stats.inc (nano::stat::type::tcp_listener_rejected, nano::stat::detail::excluded, to_stat_dir (type));
|
stats.inc (nano::stat::type::tcp_listener_rejected, nano::stat::detail::excluded, to_stat_dir (type));
|
||||||
|
|
@ -526,11 +523,7 @@ size_t nano::transport::tcp_listener::realtime_count () const
|
||||||
nano::lock_guard<nano::mutex> lock{ mutex };
|
nano::lock_guard<nano::mutex> lock{ mutex };
|
||||||
|
|
||||||
return std::count_if (connections.begin (), connections.end (), [] (auto const & connection) {
|
return std::count_if (connections.begin (), connections.end (), [] (auto const & connection) {
|
||||||
if (auto socket = connection.socket.lock ())
|
return connection.socket->type () == nano::transport::socket_type::realtime;
|
||||||
{
|
|
||||||
return socket->is_realtime_connection ();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -539,11 +532,7 @@ size_t nano::transport::tcp_listener::bootstrap_count () const
|
||||||
nano::lock_guard<nano::mutex> lock{ mutex };
|
nano::lock_guard<nano::mutex> lock{ mutex };
|
||||||
|
|
||||||
return std::count_if (connections.begin (), connections.end (), [] (auto const & connection) {
|
return std::count_if (connections.begin (), connections.end (), [] (auto const & connection) {
|
||||||
if (auto socket = connection.socket.lock ())
|
return connection.socket->type () == nano::transport::socket_type::bootstrap;
|
||||||
{
|
|
||||||
return socket->is_bootstrap_connection ();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -588,18 +577,14 @@ asio::ip::tcp::endpoint nano::transport::tcp_listener::endpoint () const
|
||||||
auto nano::transport::tcp_listener::all_sockets () const -> std::deque<std::shared_ptr<tcp_socket>>
|
auto nano::transport::tcp_listener::all_sockets () const -> std::deque<std::shared_ptr<tcp_socket>>
|
||||||
{
|
{
|
||||||
nano::lock_guard<nano::mutex> lock{ mutex };
|
nano::lock_guard<nano::mutex> lock{ mutex };
|
||||||
auto r = connections
|
auto r = connections | std::views::transform ([] (auto const & connection) { return connection.socket; });
|
||||||
| std::views::transform ([] (auto const & connection) { return connection.socket.lock (); })
|
|
||||||
| std::views::filter ([] (auto const & socket) { return socket != nullptr; });
|
|
||||||
return { r.begin (), r.end () };
|
return { r.begin (), r.end () };
|
||||||
}
|
}
|
||||||
|
|
||||||
auto nano::transport::tcp_listener::all_servers () const -> std::deque<std::shared_ptr<tcp_server>>
|
auto nano::transport::tcp_listener::all_servers () const -> std::deque<std::shared_ptr<tcp_server>>
|
||||||
{
|
{
|
||||||
nano::lock_guard<nano::mutex> lock{ mutex };
|
nano::lock_guard<nano::mutex> lock{ mutex };
|
||||||
auto r = connections
|
auto r = connections | std::views::transform ([] (auto const & connection) { return connection.server; });
|
||||||
| std::views::transform ([] (auto const & connection) { return connection.server.lock (); })
|
|
||||||
| std::views::filter ([] (auto const & server) { return server != nullptr; });
|
|
||||||
return { r.begin (), r.end () };
|
return { r.begin (), r.end () };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ private:
|
||||||
asio::awaitable<void> wait_available_slots () const;
|
asio::awaitable<void> wait_available_slots () const;
|
||||||
|
|
||||||
void run_cleanup ();
|
void run_cleanup ();
|
||||||
void cleanup ();
|
void purge (nano::unique_lock<nano::mutex> &);
|
||||||
void timeout ();
|
void timeout ();
|
||||||
|
|
||||||
asio::awaitable<void> connect_impl (asio::ip::tcp::endpoint);
|
asio::awaitable<void> connect_impl (asio::ip::tcp::endpoint);
|
||||||
|
|
@ -106,7 +106,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
accept_return accept_one (asio::ip::tcp::socket, connection_type);
|
accept_return accept_one (asio::ip::tcp::socket, connection_type);
|
||||||
accept_result check_limits (asio::ip::address const & ip, connection_type);
|
accept_result check_limits (asio::ip::address const & ip, connection_type) const;
|
||||||
asio::awaitable<asio::ip::tcp::socket> accept_socket ();
|
asio::awaitable<asio::ip::tcp::socket> accept_socket ();
|
||||||
|
|
||||||
size_t count_per_type (connection_type) const;
|
size_t count_per_type (connection_type) const;
|
||||||
|
|
@ -119,8 +119,8 @@ private:
|
||||||
{
|
{
|
||||||
connection_type type;
|
connection_type type;
|
||||||
asio::ip::tcp::endpoint endpoint;
|
asio::ip::tcp::endpoint endpoint;
|
||||||
std::weak_ptr<tcp_socket> socket;
|
std::shared_ptr<tcp_socket> socket;
|
||||||
std::weak_ptr<tcp_server> server;
|
std::shared_ptr<tcp_server> server;
|
||||||
|
|
||||||
asio::ip::address address () const
|
asio::ip::address address () const
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue