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