Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions lib/perfdata/perfdatawriterconnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ bool PerfdataWriterConnection::IsStopped() const

void PerfdataWriterConnection::Disconnect()
{
if (m_Stopped.exchange(true, std::memory_order_relaxed)) {
if (m_Stopped.exchange(true)) {
return;
}

std::promise<void> promise;
SyncResult<void> ret;

IoEngine::SpawnCoroutine(m_Strand, [&](boost::asio::yield_context yc) {
try {
Expand All @@ -76,23 +76,27 @@ void PerfdataWriterConnection::Disconnect()
* completion.
*/
std::visit(
[](const auto& stream) {
[&](const auto& stream) {
if (stream->lowest_layer().is_open()) {
stream->lowest_layer().cancel();
if (m_Connected) {
stream->lowest_layer().cancel();
} else {
stream->lowest_layer().close();
}
}
},
m_Stream
);
m_ReconnectTimer.cancel();

Disconnect(std::move(yc));
promise.set_value();
ret.SetValue();
} catch (const std::exception& ex) {
promise.set_exception(std::current_exception());
ret.SetException(std::current_exception());
}
});

promise.get_future().get();
ret.Get();
}

AsioTlsOrTcpStream PerfdataWriterConnection::MakeStream() const
Expand Down Expand Up @@ -133,6 +137,10 @@ void PerfdataWriterConnection::EnsureConnected(const boost::asio::yield_context&
::Connect(stream->lowest_layer(), m_Host, m_Port, yc);

if constexpr (std::is_same_v<std::decay_t<decltype(stream)>, Shared<AsioTlsStream>::Ptr>) {
if (m_Stopped) {
BOOST_THROW_EXCEPTION(Stopped{});
}

using type = boost::asio::ssl::stream_base::handshake_type;

stream->next_layer().async_handshake(type::client, yc);
Expand All @@ -156,7 +164,7 @@ void PerfdataWriterConnection::EnsureConnected(const boost::asio::yield_context&

void PerfdataWriterConnection::Disconnect(boost::asio::yield_context yc)
{
if (!m_Connected.exchange(false, std::memory_order_relaxed)) {
if (!m_Connected.exchange(false)) {
return;
}

Expand All @@ -165,8 +173,9 @@ void PerfdataWriterConnection::Disconnect(boost::asio::yield_context yc)
if constexpr (std::is_same_v<std::decay_t<decltype(stream)>, Shared<AsioTlsStream>::Ptr>) {
stream->GracefulDisconnect(m_Strand, yc);
} else {
stream->lowest_layer().shutdown(boost::asio::socket_base::shutdown_both);
stream->lowest_layer().close();
boost::system::error_code ec;
stream->lowest_layer().shutdown(boost::asio::socket_base::shutdown_both, ec);
stream->lowest_layer().close(ec);
}
},
m_Stream
Expand Down
63 changes: 57 additions & 6 deletions lib/perfdata/perfdatawriterconnection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/string_body.hpp>
#include <future>
#include <utility>

namespace icinga {

Expand All @@ -21,6 +22,56 @@ class PerfdataWriterConnection final : public Object
static constexpr auto InitialRetryWait = 50ms;
static constexpr auto FinalRetryWait = 32s;

template<typename T>
class SyncResult
{
using ValueType = std::variant<std::monostate, std::conditional_t<std::is_void_v<T>, bool, T>, std::exception_ptr>;

public:
template<typename U, typename V = T, typename = std::enable_if_t<!std::is_void_v<V>>>
void SetValue(U&& v)
{
std::lock_guard lock(m_Mutex);
m_Value = std::forward<U>(v);
m_Cv.notify_one();
}

template<typename V = T, typename = std::enable_if_t<std::is_void_v<V>>>
void SetValue()
{
std::lock_guard lock(m_Mutex);
m_Value = true;
m_Cv.notify_one();
}

void SetException(std::exception_ptr ep)
{
std::lock_guard lock(m_Mutex);
m_Value = ValueType{ep};
m_Cv.notify_one();
}

T Get()
{
std::unique_lock l(m_Mutex);
m_Cv.wait(l, [&] { return !std::holds_alternative<std::monostate>(m_Value); });
if (std::holds_alternative<std::exception_ptr>(m_Value)) {
std::rethrow_exception(std::get<std::exception_ptr>(m_Value));
}

if constexpr (std::is_void_v<T>) {
return;
} else {
return std::move(std::get<T>(m_Value));
}
}

private:
std::mutex m_Mutex;
std::condition_variable m_Cv;
ValueType m_Value;
};

public:
DECLARE_PTR_TYPEDEFS(PerfdataWriterConnection);

Expand Down Expand Up @@ -66,7 +117,7 @@ class PerfdataWriterConnection final : public Object
}

using RetType = decltype(WriteMessage(std::declval<Buffer>(), std::declval<boost::asio::yield_context>()));
std::promise<RetType> promise;
SyncResult<RetType> ret;

IoEngine::SpawnCoroutine(m_Strand, [&](boost::asio::yield_context yc) {
while (true) {
Expand All @@ -75,16 +126,16 @@ class PerfdataWriterConnection final : public Object

if constexpr (std::is_void_v<RetType>) {
WriteMessage(std::forward<Buffer>(buf), yc);
promise.set_value();
ret.SetValue();
} else {
promise.set_value(WriteMessage(std::forward<Buffer>(buf), yc));
ret.SetValue(WriteMessage(std::forward<Buffer>(buf), yc));
}

m_RetryTimeout = InitialRetryWait;
return;
} catch (const std::exception& ex) {
if (m_Stopped) {
promise.set_exception(std::make_exception_ptr(Stopped{}));
ret.SetException(std::make_exception_ptr(Stopped{}));
return;
}

Expand All @@ -98,14 +149,14 @@ class PerfdataWriterConnection final : public Object
try {
BackoffWait(yc);
} catch (const std::exception&) {
promise.set_exception(std::make_exception_ptr(Stopped{}));
ret.SetException(std::make_exception_ptr(Stopped{}));
return;
}
}
}
});

return promise.get_future().get();
return ret.Get();
}

void Disconnect();
Expand Down
11 changes: 10 additions & 1 deletion test/perfdata-perfdatatargetfixture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,20 @@ class PerfdataWriterTargetFixture
BOOST_REQUIRE(stream->next_layer().IsVerifyOK());
}

void Shutdown()
void Shutdown(bool wait = false)
{
BOOST_REQUIRE(std::holds_alternative<Shared<AsioTlsStream>::Ptr>(m_Stream));
auto& stream = std::get<Shared<AsioTlsStream>::Ptr>(m_Stream);
try {
if (wait) {
std::array<std::byte, 128> buf{};
boost::asio::mutable_buffer readBuf (buf.data(), buf.size());
boost::system::error_code ec;

do {
stream->read_some(readBuf, ec);
} while (!ec);
}
stream->next_layer().shutdown();
} catch (const std::exception& ex) {
if (const auto* se = dynamic_cast<const boost::system::system_error*>(&ex);
Expand Down
4 changes: 2 additions & 2 deletions test/perfdata-perfdatawriterconnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ BOOST_AUTO_TEST_CASE(stuck_reading_response)
requestReadPromise.set_value();
// Do not send a response but react to the shutdown to be polite.
shutdownPromise.get_future().get();
Shutdown();
Shutdown(true);
}};

TestThread timeoutThread{[&]() {
Expand Down Expand Up @@ -315,7 +315,7 @@ BOOST_AUTO_TEST_CASE(http_send_retry)

SendResponse();

Shutdown();
Shutdown(true);
}};

boost::beast::http::request<boost::beast::http::string_body> request{boost::beast::http::verb::post, "foo", 10};
Expand Down
Loading