blob: 951db43903e6ea48349aa0b49a205ce77b915c74 [file] [log] [blame]
#pragma once
#include <systemd/sd-event.h>
#include <chrono>
#include <mutex>
#include <utility>
namespace sdbusplus
{
namespace event
{
class event;
/** RAII holder for sd_event_sources */
class source
{
public:
friend event;
source() = default;
explicit source(event& e) : ev(&e) {}
source(const source&) = delete;
source(source&&);
source& operator=(const source&) = delete;
source& operator=(source&&);
~source();
private:
source(event& e, sd_event_source*& s) : ev(&e)
{
sourcep = std::exchange(s, nullptr);
}
event* ev = nullptr;
sd_event_source* sourcep = nullptr;
};
/** sd-event wrapper for eventfd
*
* This can be used to create something similar to a std::condition_variable
* but backed by sd-event.
*/
class condition
{
public:
friend event;
condition() = delete;
explicit condition(event& e) : condition_source(e) {}
condition(const condition&) = delete;
condition(condition&&);
condition& operator=(const condition&) = delete;
condition& operator=(condition&&);
~condition()
{
if (fd >= 0)
{
close(fd);
}
}
/** Increment the signal count on the eventfd. */
void signal();
/** Acknowledge all pending signals on the eventfd. */
void ack();
private:
condition(source&& s, int&& f) :
condition_source(std::move(s)), fd(std::exchange(f, -1))
{}
source condition_source;
int fd = -1;
};
/** sd-event based run-loop implementation.
*
* This is sd-event is thread-safe in the sense that one thread may be
* executing 'run_one' while other threads create (or destruct) additional
* sources. This might result in the 'run_one' exiting having done no
* work, but the state of the underlying sd-event structures is kept
* thread-safe.
*/
class event
{
public:
using time_resolution = std::chrono::microseconds;
event();
event(const event&) = delete;
event(event&& e) = delete;
~event()
{
sd_event_unref(eventp);
}
/** Execute a single iteration of the run-loop (see sd_event_run). */
void run_one(time_resolution timeout = time_resolution::max());
/** Force a pending `run_one` to exit. */
void break_run();
/** Add a file-descriptor source to the sd-event (see sd_event_add_io). */
source add_io(int fd, uint32_t events, sd_event_io_handler_t handler,
void* data);
/** Add a eventfd-based sdbusplus::event::condition to the run-loop. */
condition add_condition(sd_event_io_handler_t handler, void* data);
/** Add a one shot timer source to the run-loop. */
source add_oneshot_timer(
sd_event_time_handler_t handler, void* data, time_resolution time,
time_resolution accuracy = std::chrono::milliseconds(1));
friend source;
private:
static int run_wakeup(sd_event_source*, int, uint32_t, void*);
sd_event* eventp = nullptr;
// Condition to allow 'break_run' to exit the run-loop.
condition run_condition{*this};
// Lock for the sd_event.
//
// There are cases where we need to lock the mutex from inside the context
// of a sd-event callback, while the lock is already held. Use a
// recursive_mutex to allow this.
std::recursive_mutex lock{};
// Safely get the lock, possibly signaling the running 'run_one' to exit.
template <bool Signal = true>
std::unique_lock<std::recursive_mutex> obtain_lock();
// When obtain_lock signals 'run_one' to exit, we want a priority of
// obtaining the lock so that the 'run_one' task doesn't run and reclaim
// the lock before the signaller can run. This stage is first obtained
// prior to getting the primary lock in order to set an order.
std::mutex obtain_lock_stage{};
};
} // namespace event
using event_t = event::event;
using event_source_t = event::source;
using event_cond_t = event::condition;
} // namespace sdbusplus