5#include <condition_variable>
6#include <core/async/ExecutorContext.hpp>
7#include <core/async/IExecutor.hpp>
8#include <core/async/ParkedWork.hpp>
18#include "../attributes.hpp"
22namespace morph::exec {
40class CoreExecutorOver final :
public ::core::async::IExecutor {
44 explicit CoreExecutorOver(::morph::exec::IExecutor& executor MORPH_LIFETIMEBOUND) : _executor{&executor} {}
46 using ::core::async::IExecutor::submit;
50 void submit(std::coroutine_handle<> handle)
override;
55 void submit(::core::async::ParkedWork work)
override;
58 ::morph::exec::IExecutor* _executor;
71 IExecutor() =
default;
72 IExecutor(
const IExecutor&) =
delete;
73 IExecutor& operator=(
const IExecutor&) =
delete;
74 IExecutor(IExecutor&&) =
delete;
75 IExecutor& operator=(IExecutor&&) =
delete;
76 virtual ~IExecutor() =
default;
84 virtual void post(std::function<
void()> task) = 0;
94 [[nodiscard]]
virtual bool isSerial() const noexcept {
return true; }
100 [[nodiscard]] ::core::async::IExecutor&
coreExecutor() noexcept {
return _core; }
105 detail::CoreExecutorOver _core{*
this};
108inline void detail::CoreExecutorOver::submit(std::coroutine_handle<> handle) {
109 _executor->post([handle] { handle.resume(); });
112inline void detail::CoreExecutorOver::submit(::core::async::ParkedWork work) {
113 _executor->post([work] {
114 work.abandon.disarm();
115 work.resume.resume();
132[[nodiscard]]
inline bool runningOn(IExecutor& executor)
noexcept {
133 auto const*
const identity = &executor.coreExecutor();
134 return ::core::async::ExecutorScope::anyInForce(
135 [identity](::core::async::ExecutorScope
const& scope) {
return &scope.executor() == identity; });
147[[nodiscard]]
inline bool runningOn(::core::async::IExecutor
const& executor)
noexcept {
148 return ::core::async::ExecutorScope::anyInForce(
149 [&executor](::core::async::ExecutorScope
const& scope) {
return &scope.executor() == &executor; });
169 std::size_t
const workers = n == 0 ? 1 : n;
170 for (std::size_t i = 0; i < workers; ++i) {
171 _workers.emplace_back([
this] { loop(); });
191 std::scoped_lock
const lock{_m};
195 for (
auto& worker : _workers) {
202 void post(std::function<
void()> task)
override {
203 std::scoped_lock
const lock{_m};
204 _q.push(std::move(task));
210 [[nodiscard]]
bool isSerial() const noexcept
override {
return false; }
216 std::function<void()> task;
218 std::unique_lock lock{_m};
219 _cv.wait(lock, [
this] {
return _stop || !_q.empty(); });
220 if (_stop && _q.empty()) {
223 task = std::move(_q.front());
230 ::core::async::ExecutorScope
const scope{coreExecutor()};
232 }
catch (
const std::exception& exc) {
235 ::morph::log::logError(
"[thread-pool] task threw: " + std::string{exc.what()});
237 ::morph::log::logError(
"[thread-pool] task threw unknown exception");
242 std::condition_variable _cv;
243 std::queue<std::function<void()>> _q;
244 std::vector<std::thread> _workers;
259 void post(std::function<
void()> task)
override {
260 std::scoped_lock
const lock{_m};
261 _q.push(std::move(task));
280 void runFor(std::chrono::milliseconds timeout) {
281 auto deadline = std::chrono::steady_clock::now() + timeout;
282 while (std::chrono::steady_clock::now() < deadline) {
283 std::function<void()> task;
285 std::unique_lock lock{_m};
286 if (!_cv.wait_until(lock, deadline, [
this] { return !_q.empty(); })) {
289 task = std::move(_q.front());
292 runTask(std::move(task));
308 std::function<void()> task;
310 std::scoped_lock
const lock{_m};
314 task = std::move(_q.front());
317 runTask(std::move(task));
338 void runTask(std::function<
void()> task) {
343 ::core::async::ExecutorScope
const scope{coreExecutor()};
345 }
catch (
const std::exception& exc) {
346 ::morph::log::logError(
"[main-thread] callback threw: " + std::string{exc.what()});
353 ::morph::log::logError(
"[main-thread] callback threw unknown exception");
358 std::condition_variable _cv;
359 std::queue<std::function<void()>> _q;
383class InlineExecutor :
public IExecutor {
390 void post(std::function<
void()> task)
override {
399 [[nodiscard]]
bool isSerial() const noexcept
override {
return false; }
407inline IExecutor& inlineExecutor() {
408 static InlineExecutor executor;
Single-thread executor intended for use on the calling (main) thread.
bool runOnce()
Dequeues and runs at most one pending task, without blocking.
void post(std::function< void()> task) override
Enqueues task to be run on the next runFor() call.
void drain()
Runs tasks until the queue is empty, with no wall-clock timeout.
void runFor(std::chrono::milliseconds timeout)
Pumps the task queue for up to timeout.
~ThreadPoolExecutor() override
Signals all workers to stop and joins them.
bool isSerial() const noexcept override
Not serial: the workers run tasks at once.
ThreadPoolExecutor(std::size_t n)
Constructs the pool with n worker threads.
void post(std::function< void()> task) override
Enqueues task for execution on one of the pool threads.
Compile-time profiler instrumentation: Tracy zones, plots, messages and thread names,...
#define MORPH_THREAD_NAME(name)
Names the calling thread in the capture.
Abstract executor interface.
::core::async::IExecutor & coreExecutor() noexcept
This executor as a core-cpp executor: the identity an core::async::ExecutorScope names.
virtual bool isSerial() const noexcept
Whether this executor runs one task at a time, each after the tasks posted before it: a strand,...
virtual void post(std::function< void()> task)=0
Schedules task for asynchronous execution.