1 #pragma once
2 
3 #include <systemd/sd-event.h>
4 
5 #include <chrono>
6 #include <mutex>
7 #include <utility>
8 
9 namespace sdbusplus
10 {
11 namespace event
12 {
13 class event;
14 
15 /** RAII holder for sd_event_sources */
16 class source
17 {
18   public:
19     friend event;
20 
21     source() = default;
source(event & e)22     explicit source(event& e) : ev(&e) {}
23 
24     source(const source&) = delete;
25     source(source&&);
26     source& operator=(const source&) = delete;
27     source& operator=(source&&);
28     ~source();
29 
30   private:
source(event & e,sd_event_source * & s)31     source(event& e, sd_event_source*& s) : ev(&e)
32     {
33         sourcep = std::exchange(s, nullptr);
34     }
35 
36     event* ev = nullptr;
37     sd_event_source* sourcep = nullptr;
38 };
39 
40 /** sd-event wrapper for eventfd
41  *
42  *  This can be used to create something similar to a std::condition_variable
43  *  but backed by sd-event.
44  */
45 class condition
46 {
47   public:
48     friend event;
49 
50     condition() = delete;
condition(event & e)51     explicit condition(event& e) : condition_source(e) {}
52     condition(const condition&) = delete;
53     condition(condition&&);
54 
55     condition& operator=(const condition&) = delete;
56     condition& operator=(condition&&);
57 
~condition()58     ~condition()
59     {
60         if (fd >= 0)
61         {
62             close(fd);
63         }
64     }
65 
66     /** Increment the signal count on the eventfd. */
67     void signal();
68     /** Acknowledge all pending signals on the eventfd. */
69     void ack();
70 
71   private:
condition(source && s,int && f)72     condition(source&& s, int&& f) :
73         condition_source(std::move(s)), fd(std::exchange(f, -1))
74     {}
75 
76     source condition_source;
77     int fd = -1;
78 };
79 
80 /** sd-event based run-loop implementation.
81  *
82  *  This is sd-event is thread-safe in the sense that one thread may be
83  *  executing 'run_one' while other threads create (or destruct) additional
84  *  sources.  This might result in the 'run_one' exiting having done no
85  *  work, but the state of the underlying sd-event structures is kept
86  *  thread-safe.
87  */
88 class event
89 {
90   public:
91     using time_resolution = std::chrono::microseconds;
92 
93     event();
94     event(const event&) = delete;
95     event(event&& e) = delete;
96 
~event()97     ~event()
98     {
99         sd_event_unref(eventp);
100     }
101 
102     /** Execute a single iteration of the run-loop (see sd_event_run). */
103     void run_one(time_resolution timeout = time_resolution::max());
104     /** Force a pending `run_one` to exit. */
105     void break_run();
106 
107     /** Add a file-descriptor source to the sd-event (see sd_event_add_io). */
108     source add_io(int fd, uint32_t events, sd_event_io_handler_t handler,
109                   void* data);
110 
111     /** Add a eventfd-based sdbusplus::event::condition to the run-loop. */
112     condition add_condition(sd_event_io_handler_t handler, void* data);
113 
114     /** Add a one shot timer source to the run-loop. */
115     source add_oneshot_timer(
116         sd_event_time_handler_t handler, void* data, time_resolution time,
117         time_resolution accuracy = std::chrono::milliseconds(1));
118 
119     friend source;
120 
121   private:
122     static int run_wakeup(sd_event_source*, int, uint32_t, void*);
123 
124     sd_event* eventp = nullptr;
125 
126     // Condition to allow 'break_run' to exit the run-loop.
127     condition run_condition{*this};
128 
129     // Lock for the sd_event.
130     //
131     // There are cases where we need to lock the mutex from inside the context
132     // of a sd-event callback, while the lock is already held.  Use a
133     // recursive_mutex to allow this.
134     std::recursive_mutex lock{};
135 
136     // Safely get the lock, possibly signaling the running 'run_one' to exit.
137     template <bool Signal = true>
138     std::unique_lock<std::recursive_mutex> obtain_lock();
139     // When obtain_lock signals 'run_one' to exit, we want a priority of
140     // obtaining the lock so that the 'run_one' task doesn't run and reclaim
141     // the lock before the signaller can run.  This stage is first obtained
142     // prior to getting the primary lock in order to set an order.
143     std::mutex obtain_lock_stage{};
144 };
145 
146 } // namespace event
147 
148 using event_t = event::event;
149 using event_source_t = event::source;
150 using event_cond_t = event::condition;
151 
152 } // namespace sdbusplus
153