8#include <condition_variable>
42 virtual void post(std::function<
void()> fn) = 0;
50 void schedule(std::function<
void()> fn)
override {
75 if (threads == 0) threads = 1;
76 for (std::size_t i = 0; i < threads; ++i) {
77 workers_.emplace_back([
this]() { worker_loop_(); });
96 std::lock_guard lk(mutex_);
100 for (
auto& t : workers_)
if (t.joinable()) t.join();
103 void post(std::function<
void()> fn)
override {
105 std::lock_guard lk(mutex_);
106 queue_.push(std::move(fn));
107 inflight_.fetch_add(1, std::memory_order_relaxed);
122 [[nodiscard]] std::size_t
worker_count() const noexcept {
return workers_.size(); }
128 std::unique_lock lk(mutex_);
129 idle_cv_.wait(lk, [
this] {
130 return queue_.empty() && inflight_.load() == 0;
135 void worker_loop_() {
137 std::function<void()> fn;
139 std::unique_lock lk(mutex_);
140 cv_.wait(lk, [
this] {
return stop_ || !queue_.empty(); });
141 if (stop_ && queue_.empty())
return;
142 fn = std::move(queue_.front());
149 std::string_view{
"executor.thread_pool.worker"},
150 std::current_exception());
153 if (inflight_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
155 std::lock_guard lk(mutex_);
156 idle_cv_.notify_all();
161 std::vector<std::thread> workers_;
162 std::queue<std::function<void()>> queue_;
164 std::condition_variable cv_;
165 std::condition_variable idle_cv_;
166 std::atomic<int> inflight_{0};
190 void post(std::function<
void()> fn)
override {
196 std::string_view{
"executor.inline.post"},
197 std::current_exception());
249 void post(std::function<
void()> fn)
override {
251 std::lock_guard lk(m_);
252 queue_.push_back(std::move(fn));
280 std::size_t total = 0;
282 std::vector<std::function<void()>> local;
284 std::lock_guard lk(m_);
285 if (queue_.empty())
break;
286 local.reserve(queue_.size());
287 std::move(queue_.begin(), queue_.end(), std::back_inserter(local));
290 for (
auto& fn : local) {
295 std::string_view{
"executor.main_thread.drain"},
296 std::current_exception());
310 template<
typename Pred>
312 std::chrono::milliseconds timeout = std::chrono::seconds{2}) {
314 const auto deadline = std::chrono::steady_clock::now() + timeout;
317 if (predicate())
return true;
318 const auto now = std::chrono::steady_clock::now();
319 if (now >= deadline)
return false;
320 std::unique_lock lk(m_);
321 cv_.wait_until(lk, deadline, [
this]{
return !queue_.empty(); });
334 std::function<void()> fn;
336 std::unique_lock lk(m_);
337 cv_.wait(lk, [
this]{
return !queue_.empty(); });
338 fn = std::move(queue_.front());
345 std::string_view{
"executor.main_thread.run_one"},
346 std::current_exception());
353 const auto id = owner_.load(std::memory_order_acquire);
354 return id == std::thread::id{} ||
id == std::this_thread::get_id();
357 [[nodiscard]] std::size_t
pending() const noexcept {
358 std::lock_guard lk(m_);
359 return queue_.size();
365 std::deque<std::function<void()>> retired;
367 std::lock_guard lk(m_);
368 retired.swap(queue_);
375 void bind_owner_() noexcept {
376 std::thread::id expected{};
377 const auto self = std::this_thread::get_id();
378 if (owner_.compare_exchange_strong(expected, self,
379 std::memory_order_acq_rel)) {
383 assert(expected == self
384 &&
"MainThreadExecutor pumped from a non-owner thread. "
385 "post() is fine from any thread, but drain/pump/run_one "
386 "must run on the thread that originally bound the executor.");
389 mutable std::mutex m_;
390 std::condition_variable cv_;
391 std::deque<std::function<void()>> queue_;
392 std::atomic<std::thread::id> owner_{};
399 bool await_ready()
const noexcept {
return false; }
400 void await_suspend(std::coroutine_handle<> h)
const {
401 exec.
post([h]()
mutable { h.resume(); });
403 void await_resume()
const noexcept {}
405 return Awaiter{exec};
Definition scheduler.hpp:152
Abstract executor interface — schedules a callable to run "somewhere".
Definition executor.hpp:36
~IExecutor() override=default
virtual bool is_safe_graph_executor() const noexcept
True iff this executor is safe to use as the graph-thread (UI) executor.
Definition executor.hpp:59
void schedule(std::function< void()> fn) override
Submit fn for execution "soon". Defines Caps::Post.
Definition executor.hpp:50
virtual void post(std::function< void()> fn)=0
Legacy / canonical executor entry point.
aria::SchedulerCaps caps() const noexcept override
Capability bitmask.
Definition executor.hpp:45
virtual bool is_safe_worker_executor() const noexcept
True iff this executor can host worker tasks.
Definition executor.hpp:65
Inline executor — runs callable synchronously on the calling thread.
Definition executor.hpp:184
void post(std::function< void()> fn) override
Synchronously runs fn on the caller's thread.
Definition executor.hpp:190
aria::SchedulerCaps caps() const noexcept override
Capability bitmask.
Definition executor.hpp:206
Main-thread executor — queues callables for later execution on the thread that "owns" the executor (t...
Definition executor.hpp:247
void clear() noexcept
Drop all pending callables without running them. Owner-thread-only.
Definition executor.hpp:363
void post(std::function< void()> fn) override
Legacy / canonical executor entry point.
Definition executor.hpp:249
bool is_owner_thread() const noexcept
True if called from the thread that owns this executor (or if no owner has been bound yet).
Definition executor.hpp:352
std::size_t pending() const noexcept
Definition executor.hpp:357
void run_one()
Run exactly one callable, blocking the owner thread until one is available.
Definition executor.hpp:332
std::size_t drain()
Run callables.
Definition executor.hpp:278
bool is_main_thread() const noexcept override
True iff the calling thread is the scheduler's "main" thread.
Definition executor.hpp:268
aria::SchedulerCaps caps() const noexcept override
Main-thread executor: safe in both reactive roles, plus Pumpable (drain/pump_until/run_one) and MainT...
Definition executor.hpp:260
bool pump_until(Pred predicate, std::chrono::milliseconds timeout=std::chrono::seconds{2})
Pump until predicate() returns true OR timeout elapses, then return.
Definition executor.hpp:311
aria::SchedulerCaps caps() const noexcept override
Worker pool: NOT safe as the graph executor.
Definition executor.hpp:116
~ThreadPoolExecutor() override
Definition executor.hpp:81
void wait_idle()
Block until queue is drained AND no worker is currently running a task.
Definition executor.hpp:127
std::size_t worker_count() const noexcept
Definition executor.hpp:122
void post(std::function< void()> fn) override
Legacy / canonical executor entry point.
Definition executor.hpp:103
ThreadPoolExecutor(std::size_t threads=std::thread::hardware_concurrency())
Definition executor.hpp:73
Definition async_command.hpp:118
auto schedule_on(IExecutor &exec)
Schedule a coroutine to resume on the given executor.
Definition executor.hpp:396
bool has_caps(const IScheduler &s, SchedulerCaps required) noexcept
Definition scheduler.hpp:184
void report_callback_failure(std::string_view category, std::exception_ptr exception, std::string_view message={}) noexcept
Report a callback failure.
SchedulerCaps
Definition scheduler.hpp:83
@ Post
Can submit "fire now" work.
Definition scheduler.hpp:89
@ GraphSafe
Safe to use as the graph-thread executor — i.e.
Definition scheduler.hpp:107
@ Pumpable
Posted work is held in a queue until a pump-style call drains it (e.g.
Definition scheduler.hpp:102
@ Autonomous
Implementation does not require any external pump and runs work on background threads autonomously.
Definition scheduler.hpp:116
@ MainThread
Submitted work runs on a single, identifiable "main" thread that is consistent across calls.
Definition scheduler.hpp:97
@ WorkerSafe
Safe to host blocking worker tasks (e.g.
Definition scheduler.hpp:111