7#include <condition_variable>
43 virtual void post(std::function<
void()> fn) = 0;
51 void schedule(std::function<
void()> fn)
override {
76 if (threads == 0) threads = 1;
78 for (std::size_t i = 0; i < threads; ++i) {
79 workers_.emplace_back([
this]() { worker_loop_(); });
85 std::scoped_lock lk(mutex_);
88 wake_sequence_.fetch_add(1, std::memory_order_release);
89 wake_sequence_.notify_all();
90 for (
auto& worker : workers_) {
91 if (worker.joinable()) { worker.join(); }
112 std::lock_guard lk(mutex_);
115 wake_sequence_.fetch_add(1, std::memory_order_release);
116 wake_sequence_.notify_all();
117 for (
auto& t : workers_)
if (t.joinable()) t.join();
120 void post(std::function<
void()> fn)
override {
122 std::lock_guard lk(mutex_);
123 queue_.push(std::move(fn));
124 inflight_.fetch_add(1, std::memory_order_relaxed);
126 wake_sequence_.fetch_add(1, std::memory_order_release);
127 wake_sequence_.notify_one();
140 [[nodiscard]] std::size_t
worker_count() const noexcept {
return workers_.size(); }
150 std::lock_guard lk(mutex_);
151 pending = inflight_.load(std::memory_order_acquire);
152 if (queue_.empty() && pending == 0) {
return; }
156 inflight_.wait(pending, std::memory_order_acquire);
161 void worker_loop_() {
163 std::function<void()> fn;
165 std::unique_lock lk(mutex_);
166 while (!stop_ && queue_.empty()) {
167 const auto sequence = wake_sequence_.load(std::memory_order_acquire);
169 wake_sequence_.wait(sequence, std::memory_order_acquire);
172 if (stop_ && queue_.empty())
return;
173 fn = std::move(queue_.front());
180 std::string_view{
"executor.thread_pool.worker"},
181 std::current_exception());
184 if (inflight_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
186 inflight_.notify_all();
191 std::vector<std::thread> workers_;
192 std::queue<std::function<void()>> queue_;
197 std::atomic<std::uint64_t> wake_sequence_{0};
199 [[maybe_unused]] std::condition_variable idle_cv_;
200 std::atomic<int> inflight_{0};
224 void post(std::function<
void()> fn)
override {
230 std::string_view{
"executor.inline.post"},
231 std::current_exception());
290 std::deque<std::function<void()>> retired;
292 std::scoped_lock lk(m_);
294 retired.swap(queue_);
299 void post(std::function<
void()> fn)
override {
302 std::lock_guard lk(m_);
303 if (closed_) {
return; }
304 queue_.push_back(std::move(fn));
308 if (owner_.load(std::memory_order_acquire) != std::this_thread::get_id()) {
336 std::size_t total = 0;
338 std::function<void()> single;
339 std::vector<std::function<void()>> local;
341 std::lock_guard lk(m_);
342 if (queue_.empty()) {
break; }
343 if (queue_.size() == 1) {
344 single = std::move(queue_.front());
347 local.reserve(queue_.size());
348 std::move(queue_.begin(), queue_.end(), std::back_inserter(local));
356 invoke_drained(single);
359 for (
auto& fn : local) {
374 template<
typename Pred>
376 std::chrono::milliseconds timeout = std::chrono::seconds{2}) {
378 const auto deadline = std::chrono::steady_clock::now() + timeout;
381 if (predicate())
return true;
382 const auto now = std::chrono::steady_clock::now();
383 if (now >= deadline)
return false;
384 std::unique_lock lk(m_);
385 cv_.wait_until(lk, deadline, [
this]{
return !queue_.empty(); });
398 std::function<void()> fn;
400 std::unique_lock lk(m_);
401 cv_.wait(lk, [
this]{
return !queue_.empty(); });
402 fn = std::move(queue_.front());
409 std::string_view{
"executor.main_thread.run_one"},
410 std::current_exception());
417 const auto id = owner_.load(std::memory_order_acquire);
418 return id == std::thread::id{} ||
id == std::this_thread::get_id();
421 [[nodiscard]] std::size_t
pending() const noexcept {
422 std::lock_guard lk(m_);
423 return queue_.size();
429 std::deque<std::function<void()>> retired;
431 std::lock_guard lk(m_);
432 retired.swap(queue_);
439 static void invoke_drained(std::function<
void()>& fn) {
444 std::string_view{
"executor.main_thread.drain"},
445 std::current_exception());
450 const auto self = std::this_thread::get_id();
451 if (owner_.load(std::memory_order_acquire) == self) {
return; }
452 std::thread::id expected{};
453 if (owner_.compare_exchange_strong(expected, self,
454 std::memory_order_acq_rel)) {
458 if (expected != self) {
459 throw std::logic_error(
460 "MainThreadExecutor must be pumped or cleared on its owner thread");
464 mutable std::mutex m_;
465 std::condition_variable cv_;
466 std::deque<std::function<void()>> queue_;
467 std::atomic<std::thread::id> owner_{};
468 bool closed_ =
false;
475 bool await_ready()
const noexcept {
return false; }
476 void await_suspend(std::coroutine_handle<> h)
const {
477 exec.
post([h]()
mutable { h.resume(); });
479 void await_resume()
const noexcept {}
481 return Awaiter{exec};
Definition scheduler.hpp:152
Abstract executor interface — schedules a callable to run "somewhere".
Definition executor.hpp:37
~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:60
void schedule(std::function< void()> fn) override
Submit fn for execution "soon". Defines Caps::Post.
Definition executor.hpp:51
virtual void post(std::function< void()> fn)=0
Legacy / canonical executor entry point.
aria::SchedulerCaps caps() const noexcept override
Capability bitmask.
Definition executor.hpp:46
virtual bool is_safe_worker_executor() const noexcept
True iff this executor can host worker tasks.
Definition executor.hpp:66
Inline executor — runs callable synchronously on the calling thread.
Definition executor.hpp:218
void post(std::function< void()> fn) override
Synchronously runs fn on the caller's thread.
Definition executor.hpp:224
aria::SchedulerCaps caps() const noexcept override
Capability bitmask.
Definition executor.hpp:240
MainThreadExecutor & operator=(const MainThreadExecutor &)=delete
void clear()
Drop all pending callables without running them. Owner-thread-only.
Definition executor.hpp:427
~MainThreadExecutor() override
Definition executor.hpp:289
void post(std::function< void()> fn) override
Legacy / canonical executor entry point.
Definition executor.hpp:299
MainThreadExecutor & operator=(MainThreadExecutor &&)=delete
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:416
std::size_t pending() const noexcept
Definition executor.hpp:421
void run_one()
Run exactly one callable, blocking the owner thread until one is available.
Definition executor.hpp:396
MainThreadExecutor()=default
MainThreadExecutor(MainThreadExecutor &&)=delete
std::size_t drain()
Run callables.
Definition executor.hpp:334
MainThreadExecutor(const MainThreadExecutor &)=delete
bool is_main_thread() const noexcept override
True iff the calling thread is the scheduler's "main" thread.
Definition executor.hpp:324
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:316
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:375
aria::SchedulerCaps caps() const noexcept override
Worker pool: NOT safe as the graph executor.
Definition executor.hpp:134
~ThreadPoolExecutor() override
Definition executor.hpp:97
void wait_idle()
Block until queue is drained AND no worker is currently running a task.
Definition executor.hpp:144
std::size_t worker_count() const noexcept
Definition executor.hpp:140
void post(std::function< void()> fn) override
Legacy / canonical executor entry point.
Definition executor.hpp:120
ThreadPoolExecutor(std::size_t threads=std::thread::hardware_concurrency())
Definition executor.hpp:74
Definition async_command.hpp:118
auto schedule_on(IExecutor &exec)
Schedule a coroutine to resume on the given executor.
Definition executor.hpp:472
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