blob: 05994c9940e55a4803fb7f25a550c4295d7e58a1 [file] [log] [blame]
#pragma once
#include <sdbusplus/async/execution.hpp>
#include <sdbusplus/async/task.hpp>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/event.hpp>
#include <condition_variable>
#include <mutex>
#include <stop_token>
#include <thread>
namespace sdbusplus::async
{
namespace details
{
struct wait_process_completion;
struct context_friend;
} // namespace details
/** @brief A run-loop context for handling asynchronous dbus operations.
*
* This class encapsulates the run-loop for asynchronous operations,
* especially those using co-routines. Primarily, the object is given
* co-routines (or Senders) for the processing, via `spawn`, and then the
* object is `run` which handles all asynchronous operations until the
* context is stopped, via `request_stop`.
*
* The context has two threads:
* - The thread which called `run`, often from `main`, and named the
* `caller`.
* - A worker thread (`worker`), which performs the async operations.
*
* In order to avoid blocking the worker needlessly, the `caller` is the only
* thread which called `sd_bus_wait`, but the `worker` is where all
* `sd_bus_process` calls are performed. There is a condition-variable based
* handshake between the two threads to accomplish this interaction.
*/
class context : public bus::details::bus_friend
{
public:
explicit context(bus_t&& bus = bus::new_default());
context(context&&) = delete;
context(const context&) = delete;
// The context destructor can throw because it will throw (and likely
// terminate) if it has been improperly shutdown in order to avoid leaking
// work.
~context() noexcept(false);
/** Run the loop. */
void run();
/** Spawn a Sender to run on the context.
*
* @param[in] sender - The Sender to run.
*/
template <execution::sender_of<execution::set_value_t()> Snd>
void spawn(Snd&& sender)
{
check_stop_requested();
pending_tasks.spawn(
std::move(execution::on(loop.get_scheduler(), std::move(sender))));
spawn_watcher();
}
bus_t& get_bus() noexcept
{
return bus;
}
operator bus_t&() noexcept
{
return bus;
}
void request_name(const char* service)
{
name_requested = true;
bus.request_name(service);
}
bool request_stop() noexcept
{
return initial_stop.request_stop();
}
bool stop_requested() noexcept
{
return initial_stop.stop_requested();
}
friend details::wait_process_completion;
friend details::context_friend;
private:
bus_t bus;
event_source_t dbus_source;
event_t event_loop{};
bool name_requested = false;
/** The async run-loop from std::execution. */
execution::run_loop loop{};
/** The worker thread to handle async tasks. */
std::thread worker_thread{};
/** Stop source */
std::stop_source initial_stop{};
async_scope pending_tasks{};
// In order to coordinate final completion of work, we keep some tasks
// on a separate scope (the ones which maintain the sd-event/dbus state
// and keep a final stop-source for them.
async_scope internal_tasks{};
std::stop_source final_stop{};
// Lock and condition variable to signal `caller`.
std::mutex lock{};
std::condition_variable caller_wait{};
bool spawn_watcher_running = false;
/** Completion object to signal the worker that 'sd_bus_wait' is done. */
details::wait_process_completion* staged = nullptr;
details::wait_process_completion* pending = nullptr;
bool wait_process_stopped = false;
void worker_run();
void spawn_complete();
void check_stop_requested();
void spawn_watcher();
void caller_run();
void wait_for_wait_process_stopped();
static int dbus_event_handle(sd_event_source*, int, uint32_t, void*);
};
/** @brief Simple holder of a context&
*
* A common pattern is for classes to hold a reference to a
* context. Add a class that can be inherited instead of
* having as a class member. This allows the context-ref constructor to be
* placed earliest in the ctor initializer list, so that the reference can be
* available from inherited classes (ex. for CRTP patterns).
*
*/
class context_ref
{
public:
context_ref() = delete;
explicit context_ref(context& ctx) : ctx(ctx) {}
protected:
context& ctx;
};
namespace details
{
struct context_friend
{
static event_t& get_event_loop(context& ctx)
{
return ctx.event_loop;
}
static auto get_scheduler(context& ctx)
{
return ctx.loop.get_scheduler();
}
};
} // namespace details
} // namespace sdbusplus::async