Skip to content

Commit

Permalink
Document Timeout
Browse files Browse the repository at this point in the history
Co-authored-by: ChatGPT <[email protected]>
  • Loading branch information
Al2Klimov and ChatGPT committed Dec 2, 2024
1 parent e6a043f commit bfb0303
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 0 deletions.
5 changes: 5 additions & 0 deletions lib/base/io-engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
37 changes: 37 additions & 0 deletions lib/base/io-engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,37 @@ 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
{
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<class OnTimeout>
Timeout(boost::asio::io_context::strand& strand, const Timer::duration_type& timeoutFromNow, OnTimeout onTimeout)
: m_Timer(strand.context(), timeoutFromNow), m_Cancelled(Shared<Atomic<bool>>::Make(false))
Expand All @@ -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();
Expand All @@ -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<Atomic<bool>>::Ptr m_Cancelled;
};

Expand Down

0 comments on commit bfb0303

Please sign in to comment.