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_default());
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>
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 
70     bus_t& get_bus() noexcept
71     {
72         return bus;
73     }
74     operator bus_t&() noexcept
75     {
76         return bus;
77     }
78 
79     bool request_stop() noexcept
80     {
81         return initial_stop.request_stop();
82     }
83     bool stop_requested() noexcept
84     {
85         return initial_stop.stop_requested();
86     }
87 
88     friend details::wait_process_completion;
89     friend details::context_friend;
90 
91   private:
92     bus_t bus;
93     event_source_t dbus_source;
94     event_t event_loop{};
95 
96     /** The async run-loop from std::execution. */
97     execution::run_loop loop{};
98     /** The worker thread to handle async tasks. */
99     std::thread worker_thread{};
100     /** Stop source */
101     std::stop_source initial_stop{};
102 
103     async_scope pending_tasks{};
104     // In order to coordinate final completion of work, we keep some tasks
105     // on a separate scope (the ones which maintain the sd-event/dbus state
106     // and keep a final stop-source for them.
107     async_scope internal_tasks{};
108     std::stop_source final_stop{};
109 
110     // Lock and condition variable to signal `caller`.
111     std::mutex lock{};
112     std::condition_variable caller_wait{};
113 
114     bool spawn_watcher_running = false;
115 
116     /** Completion object to signal the worker that 'sd_bus_wait' is done. */
117     details::wait_process_completion* staged = nullptr;
118     details::wait_process_completion* pending = nullptr;
119     bool wait_process_stopped = false;
120 
121     void worker_run();
122     void spawn_complete();
123     void check_stop_requested();
124     void spawn_watcher();
125 
126     void caller_run();
127     void wait_for_wait_process_stopped();
128 
129     static int dbus_event_handle(sd_event_source*, int, uint32_t, void*);
130 };
131 
132 /** @brief Simple holder of a context&
133  *
134  *  A common pattern is for classes to hold a reference to a
135  *  context.  Add a class that can be inherited instead of
136  *  having as a class member.  This allows the context-ref constructor to be
137  * placed earliest in the ctor initializer list, so that the reference can be
138  * available from inherited classes (ex. for CRTP patterns).
139  *
140  */
141 class context_ref
142 {
143   public:
144     context_ref() = delete;
145     explicit context_ref(context& ctx) : ctx(ctx) {}
146 
147   protected:
148     context& ctx;
149 };
150 
151 namespace details
152 {
153 struct context_friend
154 {
155     static event_t& get_event_loop(context& ctx)
156     {
157         return ctx.event_loop;
158     }
159 
160     static auto get_scheduler(context& ctx)
161     {
162         return ctx.loop.get_scheduler();
163     }
164 };
165 } // namespace details
166 
167 } // namespace sdbusplus::async
168