1 #pragma once
2 
3 #include <sdbusplus/async/execution.hpp>
4 #include <sdbusplus/async/task.hpp>
5 #include <sdbusplus/bus.hpp>
6 #include <sdbusplus/event.hpp>
7 
8 #include <condition_variable>
9 #include <mutex>
10 #include <stop_token>
11 #include <thread>
12 
13 namespace sdbusplus::async
14 {
15 
16 namespace details
17 {
18 struct wait_process_completion;
19 struct context_friend;
20 } // namespace details
21 
22 /** @brief A run-loop context for handling asynchronous dbus operations.
23  *
24  *  This class encapsulates the run-loop for asynchronous operations,
25  *  especially those using co-routines.  Primarily, the object is given
26  *  co-routines (or Senders) for the processing, via `spawn`, and then the
27  *  object is `run` which handles all asynchronous operations until the
28  *  context is stopped, via `request_stop`.
29  *
30  *  The context has two threads:
31  *      - The thread which called `run`, often from `main`, and named the
32  *        `caller`.
33  *      - A worker thread (`worker`), which performs the async operations.
34  *
35  *  In order to avoid blocking the worker needlessly, the `caller` is the only
36  *  thread which called `sd_bus_wait`, but the `worker` is where all
37  *  `sd_bus_process` calls are performed.  There is a condition-variable based
38  *  handshake between the two threads to accomplish this interaction.
39  */
40 class context : public bus::details::bus_friend
41 {
42   public:
43     explicit context(bus_t&& bus = bus::new_bus());
44     context(context&&) = delete;
45     context(const context&) = delete;
46 
47     // The context destructor can throw because it will throw (and likely
48     // terminate) if it has been improperly shutdown in order to avoid leaking
49     // work.
50     ~context() noexcept(false);
51 
52     /** Run the loop. */
53     void run();
54 
55     /** Spawn a Sender to run on the context.
56      *
57      * @param[in] sender - The Sender to run.
58      */
59     template <execution::sender_of<execution::set_value_t()> Snd>
spawn(Snd && sender)60     void spawn(Snd&& sender)
61     {
62         check_stop_requested();
63 
64         pending_tasks.spawn(
65             std::move(execution::on(loop.get_scheduler(), std::move(sender))));
66 
67         spawn_watcher();
68     }
69 
get_bus()70     bus_t& get_bus() noexcept
71     {
72         return bus;
73     }
operator bus_t&()74     operator bus_t&() noexcept
75     {
76         return bus;
77     }
78 
request_name(const char * service)79     void request_name(const char* service)
80     {
81         name_requested = true;
82         bus.request_name(service);
83     }
84 
request_stop()85     bool request_stop() noexcept
86     {
87         return initial_stop.request_stop();
88     }
stop_requested()89     bool stop_requested() noexcept
90     {
91         return initial_stop.stop_requested();
92     }
93 
94     friend details::wait_process_completion;
95     friend details::context_friend;
96 
97   private:
98     bus_t bus;
99     event_source_t dbus_source;
100     event_t event_loop{};
101     bool name_requested = false;
102 
103     /** The async run-loop from std::execution. */
104     execution::run_loop loop{};
105     /** The worker thread to handle async tasks. */
106     std::thread worker_thread{};
107     /** Stop source */
108     std::stop_source initial_stop{};
109 
110     async_scope pending_tasks{};
111     // In order to coordinate final completion of work, we keep some tasks
112     // on a separate scope (the ones which maintain the sd-event/dbus state
113     // and keep a final stop-source for them.
114     async_scope internal_tasks{};
115     std::stop_source final_stop{};
116 
117     // Lock and condition variable to signal `caller`.
118     std::mutex lock{};
119     std::condition_variable caller_wait{};
120 
121     bool spawn_watcher_running = false;
122 
123     /** Completion object to signal the worker that 'sd_bus_wait' is done. */
124     details::wait_process_completion* staged = nullptr;
125     details::wait_process_completion* pending = nullptr;
126     bool wait_process_stopped = false;
127 
128     void worker_run();
129     void spawn_complete();
130     void check_stop_requested();
131     void spawn_watcher();
132 
133     void caller_run();
134     void wait_for_wait_process_stopped();
135 
136     static int dbus_event_handle(sd_event_source*, int, uint32_t, void*);
137 };
138 
139 /** @brief Simple holder of a context&
140  *
141  *  A common pattern is for classes to hold a reference to a
142  *  context.  Add a class that can be inherited instead of
143  *  having as a class member.  This allows the context-ref constructor to be
144  * placed earliest in the ctor initializer list, so that the reference can be
145  * available from inherited classes (ex. for CRTP patterns).
146  *
147  */
148 class context_ref
149 {
150   public:
151     context_ref() = delete;
context_ref(context & ctx)152     explicit context_ref(context& ctx) : ctx(ctx) {}
153 
154   protected:
155     context& ctx;
156 };
157 
158 namespace details
159 {
160 struct context_friend
161 {
get_event_loopsdbusplus::async::details::context_friend162     static event_t& get_event_loop(context& ctx)
163     {
164         return ctx.event_loop;
165     }
166 
get_schedulersdbusplus::async::details::context_friend167     static auto get_scheduler(context& ctx)
168     {
169         return ctx.loop.get_scheduler();
170     }
171 };
172 } // namespace details
173 
174 } // namespace sdbusplus::async
175