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