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