diff --git a/lib/base/io-engine.cpp b/lib/base/io-engine.cpp index 246a448f29..3190ed03d8 100644 --- a/lib/base/io-engine.cpp +++ b/lib/base/io-engine.cpp @@ -146,6 +146,11 @@ void AsioConditionVariable::Wait(boost::asio::yield_context yc) m_Timer.async_wait(yc[ec]); } +/** + * Cancels any pending timeout callback. + * + * Must be called in the strand in which the callback was scheduled! + */ void Timeout::Cancel() { m_Cancelled->store(true); diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index e7cd630965..0350d45b83 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -165,6 +165,22 @@ class AsioConditionVariable /** * I/O timeout emulator * + * This class provides a workaround for Boost.ASIO's lack of built-in timeout support. + * While Boost.ASIO handles asynchronous operations, it does not natively support timeouts for these operations. + * This class uses a boost::asio::deadline_timer to emulate a timeout by scheduling a callback to be triggered + * after a specified duration, effectively adding timeout behavior where none exists. + * The callback is executed within the provided strand, ensuring thread-safety. + * + * The constructor returns immediately after scheduling the timeout callback. + * The callback itself is invoked asynchronously when the timeout occurs. + * This allows the caller to continue execution while the timeout is running in the background. + * + * The class provides a Cancel() method to unschedule any pending callback. If the callback has already been run, + * calling Cancel() has no effect. This method can be used to abort the timeout early if the monitored operation + * completes before the callback has been run. The Timeout destructor also automatically cancels any pending callback. + * A callback is considered pending even if the timeout has already expired, + * but the callback has not been executed yet due to a busy strand. + * * @ingroup base */ class Timeout @@ -172,6 +188,14 @@ class Timeout public: using Timer = boost::asio::deadline_timer; + /** + * Schedules onTimeout to be triggered after timeoutFromNow on strand. + * + * @param strand The strand in which the callback will be executed. + * The caller must also run in this strand, as well as Cancel() and the destructor! + * @param timeoutFromNow The duration after which the timeout callback will be triggered. + * @param onTimeout The callback to invoke when the timeout occurs. + */ template Timeout(boost::asio::io_context::strand& strand, const Timer::duration_type& timeoutFromNow, OnTimeout onTimeout) : m_Timer(strand.context(), timeoutFromNow), m_Cancelled(Shared>::Make(false)) @@ -192,6 +216,11 @@ class Timeout Timeout& operator=(const Timeout&) = delete; Timeout& operator=(Timeout&&) = delete; + /** + * Cancels any pending timeout callback. + * + * Must be called in the strand in which the callback was scheduled! + */ ~Timeout() { Cancel(); @@ -201,6 +230,14 @@ class Timeout private: Timer m_Timer; + + /** + * Indicates whether the Timeout has been cancelled. + * + * This must be Shared<> between the lambda in the constructor and Cancel() for the case + * the destructor calls Cancel() while the lambda is already queued in the strand. + * The whole Timeout instance can't be kept alive by the lambda because this would delay the destructor. + */ Shared>::Ptr m_Cancelled; };