changeset 819:d2737ddd7e36

irccd: improve connection error and (forced) reconnections
author David Demelier <markand@malikania.fr>
date Mon, 26 Nov 2018 21:53:27 +0100
parents 49fa22f0b4b9
children 9109f19987fb
files libirccd-daemon/irccd/daemon/irc.cpp libirccd-daemon/irccd/daemon/irc.hpp libirccd-daemon/irccd/daemon/server.cpp libirccd-daemon/irccd/daemon/server.hpp libirccd-daemon/irccd/daemon/server_service.cpp libirccd-daemon/irccd/daemon/server_service.hpp
diffstat 6 files changed, 149 insertions(+), 89 deletions(-) [+]
line wrap: on
line diff
--- a/libirccd-daemon/irccd/daemon/irc.cpp	Fri Nov 23 21:50:20 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/irc.cpp	Mon Nov 26 21:53:27 2018 +0100
@@ -103,7 +103,7 @@
 		if (*it == ':')
 			arg = string(++it, end);
 		else {
-			while (!isspace(*it) && it != end)
+			while (it != end && !isspace(*it))
 				arg.push_back(*it++);
 
 			// Skip space after param.
@@ -139,7 +139,7 @@
 
 #if defined(IRCCD_HAVE_SSL)
 	ssl_socket_.async_handshake(stream_base::client, [handler] (auto code) {
-		handler(std::move(code));
+		handler(move(code));
 	});
 #endif
 }
@@ -219,6 +219,11 @@
 	resolve(hostname, service, move(chain));
 }
 
+void connection::disconnect()
+{
+	socket_.close();
+}
+
 void connection::recv(recv_handler handler)
 {
 #if !defined(NDEBUG)
@@ -237,7 +242,7 @@
 			return handler(make_error_code(errc::argument_list_too_long), message());
 		if (code == boost::asio::error::eof || xfer == 0)
 			return handler(make_error_code(errc::connection_reset), message());
-		else if (code)
+		if (code)
 			return handler(move(code), message());
 
 		string data;
--- a/libirccd-daemon/irccd/daemon/irc.hpp	Fri Nov 23 21:50:20 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/irc.hpp	Mon Nov 26 21:53:27 2018 +0100
@@ -1360,6 +1360,11 @@
 	void connect(std::string_view hostname, std::string_view service, connect_handler handler);
 
 	/**
+	 * Force disconnection.
+	 */
+	void disconnect();
+
+	/**
 	 * Start receiving data.
 	 *
 	 * The handler must not throw exceptions and `this` must be valid in the
--- a/libirccd-daemon/irccd/daemon/server.cpp	Fri Nov 23 21:50:20 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/server.cpp	Mon Nov 26 21:53:27 2018 +0100
@@ -441,28 +441,47 @@
 	 */
 	if (code) {
 		disconnect();
-		handler(std::move(code), event(std::monostate()));
+		handler(std::move(code), std::monostate{});
 	} else if (!dispatch(message, handler))
 		handler({}, std::monostate{});
 }
 
 void server::recv(recv_handler handler) noexcept
 {
+	assert(state_ == state::identifying || state_ == state::connected);
+
 	const auto self = shared_from_this();
 
+	timer_.expires_from_now(boost::posix_time::seconds(timeout_));
+	timer_.async_wait([this, handler, self, c = conn_] (auto code) {
+		if (c != conn_)
+			return;
+
+		if (!code) {
+			disconnect();
+			handler(make_error_code(std::errc::timed_out), std::monostate{});
+		}
+	});
+
 	conn_->recv([this, handler, self, c = conn_] (auto code, auto message) {
+		if (c != conn_)
+			return;
+
 		handle_recv(std::move(code), message, handler);
 	});
 }
 
 void server::flush()
 {
-	if (queue_.empty())
+	if (!conn_ || queue_.empty())
 		return;
 
 	const auto self = shared_from_this();
 
 	conn_->send(queue_.front(), [this, self, c = conn_] (auto code) {
+		if (c != conn_)
+			return;
+
 		handle_send(std::move(code));
 	});
 }
@@ -478,12 +497,6 @@
 	send(str(format("USER %1% unknown unknown :%2%") % username_ % realname_));
 }
 
-void server::handle_wait(const std::error_code& code, const connect_handler& handler)
-{
-	if (code && code != std::errc::operation_canceled)
-		handler(code);
-}
-
 void server::handle_connect(const std::error_code& code, const connect_handler& handler)
 {
 	timer_.cancel();
@@ -668,24 +681,52 @@
 	state_ = state::connecting;
 
 	timer_.expires_from_now(boost::posix_time::seconds(timeout_));
-	timer_.async_wait([this, handler] (auto code) {
-		handle_wait(code, handler);
+	timer_.async_wait([this, handler, c = conn_] (auto code) {
+		if (c != conn_)
+			return;
+
+		if (!code) {
+			disconnect();
+			handler(make_error_code(std::errc::timed_out));
+		}
 	});
 
 	const auto self = shared_from_this();
 
 	conn_->connect(hostname_, std::to_string(port_), [this, handler, c = conn_] (auto code) {
+		if (c != conn_)
+			return;
+
 		handle_connect(code, handler);
 	});
 }
 
-void server::disconnect() noexcept
+void server::disconnect()
 {
-	conn_ = nullptr;
 	state_ = state::disconnected;
+
+	if (conn_) {
+		conn_->disconnect();
+		conn_ = nullptr;
+	}
+
+	timer_.cancel();
 	queue_.clear();
 }
 
+void server::wait(connect_handler handler)
+{
+	assert(state_ == state::disconnected);
+
+	const auto self = shared_from_this();
+
+	timer_.expires_from_now(boost::posix_time::seconds(recodelay_));
+	timer_.async_wait([this, handler, self, c = conn_] (auto code) {
+		if (code != boost::asio::error::operation_aborted)
+			handler({});
+	});
+}
+
 void server::invite(std::string_view target, std::string_view channel)
 {
 	assert(!target.empty());
--- a/libirccd-daemon/irccd/daemon/server.hpp	Fri Nov 23 21:50:20 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/server.hpp	Mon Nov 26 21:53:27 2018 +0100
@@ -354,7 +354,6 @@
 	void identify();
 	void handle_send(const std::error_code&);
 	void handle_recv(const std::error_code&, const irc::message&, const recv_handler&);
-	void handle_wait(const std::error_code&, const connect_handler&);
 	void handle_connect(const std::error_code&, const connect_handler&);
 
 public:
@@ -573,7 +572,15 @@
 	/**
 	 * Force disconnection.
 	 */
-	virtual void disconnect() noexcept;
+	virtual void disconnect();
+
+	/**
+	 * Wait for reconnect delay.
+	 *
+	 * \pre another wait operation must not be running
+	 * \pre get_state() == state::disconnected
+	 */
+	virtual void wait(connect_handler handler);
 
 	/**
 	 * Receive next event.
--- a/libirccd-daemon/irccd/daemon/server_service.cpp	Fri Nov 23 21:50:20 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/server_service.cpp	Mon Nov 26 21:53:27 2018 +0100
@@ -458,6 +458,14 @@
 
 } // !namespace
 
+void server_service::handle_connect(const std::shared_ptr<server>& server, const std::error_code& code)
+{
+	if (code)
+		handle_error(server, code);
+	else
+		recv(server);
+}
+
 void server_service::handle_error(const std::shared_ptr<server>& server,
                                   const std::error_code& code)
 {
@@ -465,35 +473,19 @@
 
 	bot_.get_log().warning(*server) << code.message() << std::endl;
 
-	if ((server->get_options() & server::options::auto_reconnect) != server::options::auto_reconnect)
+	if ((server->get_options() & server::options::auto_reconnect) != server::options::auto_reconnect) {
 		remove(server->get_id());
-	else {
-		bot_.get_log().info(*server) << "reconnecting in "
-			<< server->get_reconnect_delay() << " second(s)" << std::endl;
-		wait(server);
-	}
-}
-
-void server_service::handle_wait(const std::shared_ptr<server>& server, const std::error_code& code)
-{
-	/*
-	 * The timer runs on his own control, it will complete either if the delay
-	 * was reached, there was an error or if the io_context was called to cancel
-	 * all pending operations.
-	 *
-	 * This means while the timer is running someone may already have ask a
-	 * server for explicit reconnection (e.g. remote command, plugin). Thus we
-	 * check for server state and if it is still present in service.
-	 */
-	if (code && code != std::errc::operation_canceled) {
-		bot_.get_log().warning(*server) << code.message() << std::endl;
 		return;
 	}
 
-	if (server->get_state() == server::state::connected || !has(server->get_id()))
-		return;
+	bot_.get_log().info(*server) << "reconnecting in " << server->get_reconnect_delay()
+	                             << " second(s)" << std::endl;
 
-	connect(server);
+	server->wait([this, server] (auto code) {
+		handle_wait(server, code);
+	});
+
+	dispatcher{bot_}(disconnect_event{server});
 }
 
 void server_service::handle_recv(const std::shared_ptr<server>& server,
@@ -502,32 +494,49 @@
 {
 	assert(server);
 
-	if (code)
+	if (code) {
 		handle_error(server, code);
-	else {
-		recv(server);
-		std::visit(dispatcher(bot_), event);
+		return;
+	}
+
+	recv(server);
+	std::visit(dispatcher(bot_), event);
+}
+
+void server_service::handle_wait(const std::shared_ptr<server>& server, const std::error_code& code)
+{
+	if (code == std::errc::operation_canceled || server->get_state() != server::state::disconnected)
+		return;
+
+	connect(server);
+}
+
+void server_service::connect(const std::shared_ptr<server>& server)
+{
+	assert(server);
+
+	server->connect([this, server] (auto code) {
+		handle_connect(server, code);
+	});
+}
+
+void server_service::disconnect(const std::shared_ptr<server>& server)
+{
+	if (server->get_state() != server::state::disconnected) {
+		server->disconnect();
+		servers_.erase(std::find(servers_.begin(), servers_.end(), server), servers_.end());
+		dispatcher{bot_}(disconnect_event{server});
 	}
 }
 
-void server_service::handle_connect(const std::shared_ptr<server>& server, const std::error_code& code)
+void server_service::reconnect(const std::shared_ptr<server>& server)
 {
-	if (code)
-		handle_error(server, code);
-	else
-		recv(server);
-}
+	disconnect(server);
 
-void server_service::wait(const std::shared_ptr<server>& server)
-{
-	assert(server);
-
-	auto timer = std::make_shared<boost::asio::deadline_timer>(bot_.get_service());
-
-	timer->expires_from_now(boost::posix_time::seconds(server->get_reconnect_delay()));
-	timer->async_wait([this, server, timer] (auto code) {
-		handle_wait(server, code);
-	});
+	if (has(server->get_id()))
+		connect(server);
+	else
+		add(server);
 }
 
 void server_service::recv(const std::shared_ptr<server>& server)
@@ -539,15 +548,6 @@
 	});
 }
 
-void server_service::connect(const std::shared_ptr<server>& server)
-{
-	assert(server);
-
-	server->connect([this, server] (auto code) {
-		handle_connect(server, code);
-	});
-}
-
 server_service::server_service(bot& bot)
 	: bot_(bot)
 {
@@ -558,7 +558,7 @@
 	return servers_;
 }
 
-auto server_service::has(const std::string& name) const noexcept -> bool
+auto server_service::has(std::string_view name) const noexcept -> bool
 {
 	return std::count_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
 		return server->get_id() == name;
@@ -601,25 +601,26 @@
 
 void server_service::disconnect(std::string_view id)
 {
-	const auto s = require(id);
-
-	s->disconnect();
-	dispatcher{bot_}(disconnect_event{s});
+	disconnect(require(id));
 }
 
 void server_service::reconnect(std::string_view id)
 {
-	disconnect(id);
-	connect(require(id));
+	const auto save = get(id);
+
+	if (!save)
+		return;
+
+	reconnect(save);
 }
 
 void server_service::reconnect()
 {
-	for (const auto& s : servers_) {
+	const auto save = servers_;
+
+	for (const auto& s : save) {
 		try {
-			s->disconnect();
-			dispatcher{bot_}(disconnect_event{s});
-			connect(s);
+			reconnect(s);
 		} catch (const server_error& ex) {
 			bot_.get_log().warning(*s) << ex.what() << std::endl;
 		}
@@ -641,13 +642,13 @@
 void server_service::clear() noexcept
 {
 	/*
-	 * Copy the array, because disconnect() may trigger on_die signal which
-	 * erase the server from itself.
+	 * Copy the array, because disconnect() interrupted signals may remove the
+	 * server from the array.
 	 */
 	const auto save = servers_;
 
 	for (const auto& server : save)
-		server->disconnect();
+		disconnect(server);
 
 	servers_.clear();
 }
--- a/libirccd-daemon/irccd/daemon/server_service.hpp	Fri Nov 23 21:50:20 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/server_service.hpp	Mon Nov 26 21:53:27 2018 +0100
@@ -49,14 +49,15 @@
 	bot& bot_;
 	std::vector<std::shared_ptr<server>> servers_;
 
+	void handle_connect(const std::shared_ptr<server>&, const std::error_code&);
 	void handle_error(const std::shared_ptr<server>&, const std::error_code&);
-	void handle_wait(const std::shared_ptr<server>&, const std::error_code&);
 	void handle_recv(const std::shared_ptr<server>&, const std::error_code&, const event&);
-	void handle_connect(const std::shared_ptr<server>&, const std::error_code&);
+	void handle_wait(const std::shared_ptr<server>&, const std::error_code&);
 
-	void wait(const std::shared_ptr<server>&);
+	void connect(const std::shared_ptr<server>&);
+	void disconnect(const std::shared_ptr<server>&);
+	void reconnect(const std::shared_ptr<server>&);
 	void recv(const std::shared_ptr<server>&);
-	void connect(const std::shared_ptr<server>&);
 
 public:
 	/**
@@ -79,7 +80,7 @@
 	 * \param name the name
 	 * \return true if exists
 	 */
-	auto has(const std::string& name) const noexcept -> bool;
+	auto has(std::string_view name) const noexcept -> bool;
 
 	/**
 	 * Add a new server to the application.