event: add a simple wrapper around sd-event
In order to facilitate stop-conditions on the async::context,
we need to be able to escape the wait on the dbus fd. sd-event
will allow us to do this and also build other co-routine primitives
in the future (such as `co_await async::delay(1s)` backed by sd-event
timers). Define a simple wrapper around sd-event here which can
be leveraged by the async framework.
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I945416cbce3c98d98135c42cb7ca249a57cb9a60
diff --git a/src/event.cpp b/src/event.cpp
new file mode 100644
index 0000000..e9c11af
--- /dev/null
+++ b/src/event.cpp
@@ -0,0 +1,176 @@
+#include <sys/eventfd.h>
+
+#include <sdbusplus/event.hpp>
+#include <sdbusplus/exception.hpp>
+
+namespace sdbusplus::event
+{
+
+source::source(source&& s)
+{
+ if (&s == this)
+ {
+ return;
+ }
+ ev = std::exchange(s.ev, nullptr);
+ sourcep = std::exchange(s.sourcep, nullptr);
+}
+
+source& source::operator=(source&& s)
+{
+ if (nullptr != sourcep)
+ {
+ auto l = ev->obtain_lock();
+ sd_event_source_unref(sourcep);
+ }
+ ev = std::exchange(s.ev, nullptr);
+ sourcep = std::exchange(s.sourcep, nullptr);
+
+ return *this;
+}
+
+source::~source()
+{
+ if (nullptr != sourcep)
+ {
+ auto l = ev->obtain_lock();
+ sd_event_source_unref(sourcep);
+ }
+}
+
+condition::condition(condition&& c)
+{
+ if (&c == this)
+ {
+ return;
+ }
+
+ condition_source = std::move(c.condition_source);
+ fd = std::exchange(c.fd, -1);
+}
+
+condition& condition::operator=(condition&& c)
+{
+ condition_source = std::move(c.condition_source);
+ if (fd >= 0)
+ {
+ close(fd);
+ }
+ fd = std::exchange(c.fd, -1);
+
+ return *this;
+}
+
+void condition::signal()
+{
+ uint64_t value = 1;
+ auto rc = write(fd, &value, sizeof(value));
+ if (rc < static_cast<decltype(rc)>(sizeof(value)))
+ {
+ throw exception::SdBusError(errno, __func__);
+ }
+}
+
+void condition::ack()
+{
+ uint64_t value = 0;
+ auto rc = read(fd, &value, sizeof(value));
+ if (rc < static_cast<decltype(rc)>(sizeof(value)))
+ {
+ throw exception::SdBusError(errno, __func__);
+ }
+}
+
+event::event()
+{
+ if (auto rc = sd_event_new(&eventp); rc < 0)
+ {
+ throw exception::SdBusError(-rc, __func__);
+ }
+ run_condition = add_condition(run_wakeup, this);
+}
+
+void event::run_one(std::chrono::microseconds timeout)
+{
+ auto l = obtain_lock<false>();
+
+ auto rc = sd_event_run(eventp, static_cast<uint64_t>(timeout.count()));
+ if (rc < 0)
+ {
+ throw exception::SdBusError(-rc, __func__);
+ }
+}
+
+void event::break_run()
+{
+ run_condition.signal();
+}
+
+source event::add_io(int fd, uint32_t events, sd_event_io_handler_t handler,
+ void* data)
+{
+ auto l = obtain_lock();
+
+ source s{*this};
+
+ auto rc = sd_event_add_io(eventp, &s.sourcep, fd, events, handler, data);
+ if (rc < 0)
+ {
+ throw exception::SdBusError(-rc, __func__);
+ }
+
+ return s;
+}
+
+condition event::add_condition(sd_event_io_handler_t handler, void* data)
+{
+ // We don't need any locks here because we only touch the sd_event
+ // indirectly through `add_io` which handles its own locking.
+
+ auto fd = eventfd(0, 0);
+ if (fd < 0)
+ {
+ throw exception::SdBusError(errno, __func__);
+ }
+
+ try
+ {
+ auto io = add_io(fd, EPOLLIN, handler, data);
+ return {std::move(io), std::move(fd)};
+ }
+ catch (...)
+ {
+ close(fd);
+ throw;
+ }
+}
+
+int event::run_wakeup(sd_event_source*, int, uint32_t, void* data)
+{
+ auto self = static_cast<event*>(data);
+ self->run_condition.ack();
+
+ return 0;
+}
+
+template <bool Signal>
+std::unique_lock<std::recursive_mutex> event::obtain_lock()
+{
+ std::unique_lock<std::recursive_mutex> l{this->lock, std::defer_lock_t()};
+ if constexpr (Signal)
+ {
+ if (!l.try_lock())
+ {
+ run_condition.signal();
+ l.lock();
+ }
+ }
+ else
+ {
+ l.lock();
+ }
+
+ return l;
+}
+
+} // namespace sdbusplus::event