mirror of
https://github.com/Icinga/icinga2.git
synced 2025-07-26 23:24:09 +02:00
EventsInbox: simplify Shift
& make it cancellable
This simplifies the `Shift()` method and makes it also cancellable from external. Apart from that, there were a potential race condition in the `Shift` method, where it called `m_Timer.expires_at(boost::posix_time::neg_infin)` without holding the lock, and vilated so the boost Asio operationguarantee on the shared objects.
This commit is contained in:
parent
1d29260d77
commit
a8553dd4d6
@ -136,7 +136,7 @@ std::map<String, EventsInbox::Filter> EventsInbox::m_Filters ({{"", EventsInbox:
|
|||||||
EventsRouter EventsRouter::m_Instance;
|
EventsRouter EventsRouter::m_Instance;
|
||||||
|
|
||||||
EventsInbox::EventsInbox(String filter, const String& filterSource)
|
EventsInbox::EventsInbox(String filter, const String& filterSource)
|
||||||
: m_Timer(IoEngine::Get().GetIoContext())
|
: m_Timer(IoEngine::Get().GetIoContext(), boost::posix_time::neg_infin)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock (m_FiltersMutex);
|
std::unique_lock<std::mutex> lock (m_FiltersMutex);
|
||||||
m_Filter = m_Filters.find(filter);
|
m_Filter = m_Filters.find(filter);
|
||||||
@ -176,40 +176,68 @@ const Expression::Ptr& EventsInbox::GetFilter()
|
|||||||
|
|
||||||
void EventsInbox::Push(Dictionary::Ptr event)
|
void EventsInbox::Push(Dictionary::Ptr event)
|
||||||
{
|
{
|
||||||
|
if (m_Discarded.load(std::memory_order_relaxed)) {
|
||||||
|
return; // If the inbox has been discarded, do not push any events.
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock (m_Mutex);
|
std::unique_lock<std::mutex> lock (m_Mutex);
|
||||||
|
|
||||||
m_Queue.emplace(std::move(event));
|
m_Queue.emplace(std::move(event));
|
||||||
m_Timer.expires_at(boost::posix_time::neg_infin);
|
m_Timer.expires_at(boost::posix_time::neg_infin);
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary::Ptr EventsInbox::Shift(boost::asio::yield_context yc, double timeout)
|
/**
|
||||||
|
* Discards all events in the inbox and sets the inbox to a discarded state.
|
||||||
|
*
|
||||||
|
* This method discards all events currently in the inbox and cancels @c Shift() operation that might be waiting
|
||||||
|
* for an event to be pushed into the inbox. After this method is called, any subsequent calls to @c Shift() will
|
||||||
|
* return nullptr immediately, indicating that the inbox has been discarded and no further events will be processed.
|
||||||
|
*
|
||||||
|
* This method is typically used when the inbox is no longer needed, as it allows for immediate wake-up of any
|
||||||
|
* waiting operations in @c Shift() and prevents further processing of events.
|
||||||
|
*
|
||||||
|
* @param yc The yield_context for this operation. This method must be called from within a coroutine that uses @c yc.
|
||||||
|
*/
|
||||||
|
void EventsInbox::Discard(boost::asio::yield_context& yc)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock (m_Mutex, std::defer_lock);
|
m_Discarded.store(true, std::memory_order_relaxed);
|
||||||
|
|
||||||
|
auto lock(AsyncLock(yc));
|
||||||
m_Timer.expires_at(boost::posix_time::neg_infin);
|
m_Timer.expires_at(boost::posix_time::neg_infin);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
/**
|
||||||
boost::system::error_code ec;
|
* Shifts the first event from the inbox, blocking until an event is available or the timeout expires.
|
||||||
|
*
|
||||||
while (!lock.try_lock()) {
|
* This method attempts to retrieve the first event from the inbox. If the inbox is empty, it will block until
|
||||||
m_Timer.async_wait(yc[ec]);
|
* either an event is pushed into the inbox or the specified timeout expires. If the inbox has been discarded,
|
||||||
}
|
* this method will return nullptr immediately.
|
||||||
|
*
|
||||||
|
* @param yc The yield_context for this operation. This method must be called from within a coroutine that uses @c yc.
|
||||||
|
* @param timeout The maximum time to wait for an event in seconds. Defaults to 5 seconds.
|
||||||
|
*
|
||||||
|
* @return A pointer to the first event in the inbox, or nullptr if the inbox has been discarded or no events are available.
|
||||||
|
*/
|
||||||
|
Dictionary::Ptr EventsInbox::Shift(boost::asio::yield_context& yc, double timeout)
|
||||||
|
{
|
||||||
|
if (m_Discarded.load(std::memory_order_relaxed)) {
|
||||||
|
return nullptr; // Nothing to shift, inbox has been discarded.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto lock(AsyncLock(yc));
|
||||||
|
|
||||||
if (m_Queue.empty()) {
|
if (m_Queue.empty()) {
|
||||||
m_Timer.expires_from_now(boost::posix_time::milliseconds((unsigned long)(timeout * 1000.0)));
|
m_Timer.expires_from_now(boost::posix_time::milliseconds(static_cast<long>(timeout * 1000.0)));
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
{
|
boost::system::error_code ec;
|
||||||
boost::system::error_code ec;
|
m_Timer.async_wait(yc[ec]);
|
||||||
m_Timer.async_wait(yc[ec]);
|
|
||||||
|
|
||||||
while (!lock.try_lock()) {
|
// Someone has woken us up (either by pushing an event, by timeout or by Discard()), so we need
|
||||||
m_Timer.async_wait(yc[ec]);
|
// to re-acquire the lock to check the queue again and return the event if not discarded.
|
||||||
}
|
lock = AsyncLock(yc);
|
||||||
}
|
|
||||||
|
|
||||||
if (m_Queue.empty()) {
|
if (m_Discarded || m_Queue.empty()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,6 +247,26 @@ Dictionary::Ptr EventsInbox::Shift(boost::asio::yield_context yc, double timeout
|
|||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously acquires a unique lock on @c m_Mutex and returns it.
|
||||||
|
*
|
||||||
|
* @param yc The yield_context for this operation.
|
||||||
|
*
|
||||||
|
* @return A unique_lock on @c m_Mutex that is acquired asynchronously.
|
||||||
|
*/
|
||||||
|
std::unique_lock<std::mutex> EventsInbox::AsyncLock(boost::asio::yield_context& yc)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_Mutex, std::defer_lock);
|
||||||
|
|
||||||
|
boost::system::error_code ec;
|
||||||
|
// Try to acquire the lock without blocking, since there might be some mutex contention,
|
||||||
|
// and we want to avoid blocking the I/O thread for too long.
|
||||||
|
while (!lock.try_lock()) {
|
||||||
|
m_Timer.async_wait(yc[ec]);
|
||||||
|
}
|
||||||
|
return std::move(lock);
|
||||||
|
}
|
||||||
|
|
||||||
EventsSubscriber::EventsSubscriber(std::set<EventType> types, String filter, const String& filterSource)
|
EventsSubscriber::EventsSubscriber(std::set<EventType> types, String filter, const String& filterSource)
|
||||||
: m_Types(std::move(types)), m_Inbox(new EventsInbox(std::move(filter), filterSource))
|
: m_Types(std::move(types)), m_Inbox(new EventsInbox(std::move(filter), filterSource))
|
||||||
{
|
{
|
||||||
|
@ -99,9 +99,12 @@ public:
|
|||||||
const Expression::Ptr& GetFilter();
|
const Expression::Ptr& GetFilter();
|
||||||
|
|
||||||
void Push(Dictionary::Ptr event);
|
void Push(Dictionary::Ptr event);
|
||||||
Dictionary::Ptr Shift(boost::asio::yield_context yc, double timeout = 5);
|
void Discard(boost::asio::yield_context& yc);
|
||||||
|
Dictionary::Ptr Shift(boost::asio::yield_context& yc, double timeout = 5);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::unique_lock<std::mutex> AsyncLock(boost::asio::yield_context& yc);
|
||||||
|
|
||||||
struct Filter
|
struct Filter
|
||||||
{
|
{
|
||||||
std::size_t Refs;
|
std::size_t Refs;
|
||||||
@ -114,6 +117,7 @@ private:
|
|||||||
std::mutex m_Mutex;
|
std::mutex m_Mutex;
|
||||||
decltype(m_Filters.begin()) m_Filter;
|
decltype(m_Filters.begin()) m_Filter;
|
||||||
std::queue<Dictionary::Ptr> m_Queue;
|
std::queue<Dictionary::Ptr> m_Queue;
|
||||||
|
Atomic<bool> m_Discarded{false}; // Set when Discard() is called, so that Shift() can return nullptr instead of blocking indefinitely.
|
||||||
boost::asio::deadline_timer m_Timer;
|
boost::asio::deadline_timer m_Timer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user