From 647f1547a9c2f94d6f1432c2e4c68bac1ad2944f Mon Sep 17 00:00:00 2001
From: "Alexander A. Klimov" <alexander.klimov@icinga.com>
Date: Wed, 5 Feb 2020 17:17:41 +0100
Subject: [PATCH] Generalize I/O timeout emulation

---
 lib/base/io-engine.cpp     |  8 ++++++
 lib/base/io-engine.hpp     | 52 ++++++++++++++++++++++++++++++++++++++
 lib/remote/apilistener.cpp | 31 +++++++----------------
 3 files changed, 69 insertions(+), 22 deletions(-)

diff --git a/lib/base/io-engine.cpp b/lib/base/io-engine.cpp
index 5dd3ee59c..d3197790e 100644
--- a/lib/base/io-engine.cpp
+++ b/lib/base/io-engine.cpp
@@ -144,3 +144,11 @@ void AsioConditionVariable::Wait(boost::asio::yield_context yc)
 	boost::system::error_code ec;
 	m_Timer.async_wait(yc[ec]);
 }
+
+void Timeout::Cancel()
+{
+	m_Cancelled.store(true);
+
+	boost::system::error_code ec;
+	m_Timer.cancel(ec);
+}
diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp
index ba4ebcfc5..dabd6730b 100644
--- a/lib/base/io-engine.hpp
+++ b/lib/base/io-engine.hpp
@@ -6,10 +6,12 @@
 #include "base/exception.hpp"
 #include "base/lazy-init.hpp"
 #include "base/logger.hpp"
+#include "base/shared-object.hpp"
 #include <atomic>
 #include <exception>
 #include <memory>
 #include <thread>
+#include <utility>
 #include <vector>
 #include <stdexcept>
 #include <boost/exception/all.hpp>
@@ -153,6 +155,56 @@ private:
 	boost::asio::deadline_timer m_Timer;
 };
 
+/**
+ * I/O timeout emulator
+ *
+ * @ingroup base
+ */
+class Timeout : public SharedObject
+{
+public:
+	DECLARE_PTR_TYPEDEFS(Timeout);
+
+	template<class Executor, class TimeoutFromNow, class OnTimeout>
+	Timeout(boost::asio::io_context& io, Executor& executor, TimeoutFromNow timeoutFromNow, OnTimeout onTimeout)
+		: m_Timer(io)
+	{
+		Ptr keepAlive (this);
+
+		m_Cancelled.store(false);
+		m_Timer.expires_from_now(std::move(timeoutFromNow));
+
+		IoEngine::SpawnCoroutine(executor, [this, keepAlive, onTimeout](boost::asio::yield_context yc) {
+			if (m_Cancelled.load()) {
+				return;
+			}
+
+			{
+				boost::system::error_code ec;
+
+				m_Timer.async_wait(yc[ec]);
+
+				if (ec) {
+					return;
+				}
+			}
+
+			if (m_Cancelled.load()) {
+				return;
+			}
+
+			auto f (onTimeout);
+			f(std::move(yc));
+		});
+	}
+
+	void Cancel();
+
+private:
+	boost::asio::deadline_timer m_Timer;
+	std::atomic<bool> m_Cancelled;
+};
+
 }
 
 #endif /* IO_ENGINE_H */
diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp
index cb024e1fd..0eb9c248b 100644
--- a/lib/remote/apilistener.cpp
+++ b/lib/remote/apilistener.cpp
@@ -542,32 +542,19 @@ void ApiListener::NewClientHandlerInternal(
 	boost::system::error_code ec;
 
 	{
-		struct DoneHandshake
-		{
-			bool Done = false;
-		};
-
-		auto doneHandshake (Shared<DoneHandshake>::Make());
-
-		IoEngine::SpawnCoroutine(*strand, [strand, client, doneHandshake](asio::yield_context yc) {
-			namespace sys = boost::system;
-
-			{
-				boost::asio::deadline_timer timer (strand->context());
-				timer.expires_from_now(boost::posix_time::microseconds(intmax_t(Configuration::TlsHandshakeTimeout * 1000000)));
-
-				sys::error_code ec;
-				timer.async_wait(yc[ec]);
-			}
-
-			if (!doneHandshake->Done) {
-				sys::error_code ec;
+		Timeout::Ptr handshakeTimeout (new Timeout(
+			strand->context(),
+			*strand,
+			boost::posix_time::microseconds(intmax_t(Configuration::TlsHandshakeTimeout * 1000000)),
+			[strand, client](asio::yield_context yc) {
+				boost::system::error_code ec;
 				client->lowest_layer().cancel(ec);
 			}
-		});
+		));
 
 		sslConn.async_handshake(role == RoleClient ? sslConn.client : sslConn.server, yc[ec]);
-		doneHandshake->Done = true;
+
+		handshakeTimeout->Cancel();
 	}
 
 	if (ec) {