orotool/src/roar11/ThreadSafeQueue.hpp
King_DuckZ 497d797e32 Quit cleanly when an exception is thrown
The trick is to break the ev loop from within its
same thread. This can be done with the async event
for example, so I repurposed the one I already had
to call stop() in case an error has been set.
Otherwise its callback is a no-op just like before.
2020-09-12 00:40:41 +01:00

145 lines
3.8 KiB
C++

/**
* The ThreadSafeQueue class.
* Provides a wrapper around a basic queue to provide thread safety.
*/
#pragma once
#include <system_error>
#ifndef THREADSAFEQUEUE_HPP
#define THREADSAFEQUEUE_HPP
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <utility>
#include <iostream>
namespace roar11
{
template <typename T>
class ThreadSafeQueue
{
public:
/**
* Destructor.
*/
~ThreadSafeQueue(void)
{
invalidate();
}
/**
* Attempt to get the first value in the queue.
* Returns true if a value was successfully written to the out parameter, false otherwise.
*/
bool tryPop(T& out)
{
std::lock_guard<std::mutex> lock{m_mutex};
if(m_queue.empty() || !m_valid)
{
return false;
}
out = std::move(m_queue.front());
m_queue.pop();
return true;
}
/**
* Get the first value in the queue.
* Will block until a value is available unless clear is called or the instance is destructed.
* Returns true if a value was successfully written to the out parameter, false otherwise.
*/
bool waitPop(T& out)
{
std::unique_lock<std::mutex> lock{m_mutex};
m_condition.wait(lock, [this]()
{
return !m_queue.empty() || !m_valid;
});
/*
* Using the condition in the predicate ensures that spurious wakeups with a valid
* but empty queue will not proceed, so only need to check for validity before proceeding.
*/
if(!m_valid)
{
return false;
}
out = std::move(m_queue.front());
m_queue.pop();
return true;
}
/**
* Push a new value onto the queue.
*/
void push(T value)
{
std::lock_guard<std::mutex> lock{m_mutex};
if (m_valid)
{
m_queue.push(std::move(value));
m_condition.notify_one();
}
}
/**
* Check whether or not the queue is empty.
*/
bool empty(void) const
{
std::lock_guard<std::mutex> lock{m_mutex};
return m_queue.empty();
}
/**
* Clear all items from the queue.
*/
void clear(void)
{
std::lock_guard<std::mutex> lock{m_mutex};
while(!m_queue.empty())
{
m_queue.pop();
}
m_condition.notify_all();
}
/**
* Invalidate the queue.
* Used to ensure no conditions are being waited on in waitPop when
* a thread or the application is trying to exit.
* The queue is invalid after calling this method and it is an error
* to continue using a queue after this method has been called.
*/
void invalidate(void) noexcept
{
try {
std::lock_guard<std::mutex> lock{m_mutex}; //<-- can throw
m_valid = false;
m_condition.notify_all();
}
catch (const std::system_error& err) {
std::cerr << "Error in ThreadSafeQueue::invalidate(): " <<
err.what() << '\n';
}
}
/**
* Returns whether or not this queue is valid.
*/
bool isValid(void) const
{
std::lock_guard<std::mutex> lock{m_mutex};
return m_valid;
}
private:
std::atomic_bool m_valid{true};
mutable std::mutex m_mutex;
std::queue<T> m_queue;
std::condition_variable m_condition;
};
}
#endif