5#include <core/async/ExecutorContext.hpp>
6#include <core/async/IExecutor.hpp>
7#include <core/async/KeyedStrands.hpp>
8#include <core/async/ParkedWork.hpp>
9#include <core/async/Strand.hpp>
17#include <shared_mutex>
20#include <unordered_map>
23#include "../attributes.hpp"
24#include "../session/session.hpp"
25#include "executor.hpp"
35namespace morph::exec::detail {
46 auto operator<=>(
const ModelId&)
const =
default;
52 std::size_t operator()(ModelId mid)
const noexcept {
return std::hash<uint64_t>{}(mid.v); }
70 }
catch (
const std::exception& exc) {
71 ::morph::log::logError(
"[strand] task threw: " + std::string{exc.what()});
73 ::morph::log::logError(
"[strand] task threw unknown exception");
82enum class TeardownOrder : std::uint8_t {
98inline constexpr TeardownOrder buildTeardownOrder =
99 CORE_CPP_ASYNC_HAS_THREADS ? TeardownOrder::StopThenSeal : TeardownOrder::SealThenStop;
118class ModelStrands final {
125 explicit ModelStrands(::morph::exec::IExecutor& base MORPH_LIFETIMEBOUND,
126 ::core::async::StrandOptions options = {})
127 : _strands{base.coreExecutor(), options, ::core::async::KeyedAroundTask<ModelId>::of(_hook)} {}
129 ModelStrands(
const ModelStrands&) =
delete;
130 ModelStrands& operator=(
const ModelStrands&) =
delete;
131 ModelStrands(ModelStrands&&) =
delete;
132 ModelStrands& operator=(ModelStrands&&) =
delete;
133 ~ModelStrands() =
default;
140 template <
typename F>
141 void post(ModelId key, F&& task) {
142 _strands.post(key, LoggedTask<std::decay_t<F>>{std::forward<F>(task)});
154 template <
typename F>
155 void runOnStrand(ModelId key, F task) {
156 if (runningHere(key)) {
160 LoggedTask<F> logged{std::move(task)};
161 if (!_strands.tryPost(key, logged)) {
170 [[nodiscard]]
bool trySubmit(ModelId key, std::coroutine_handle<> handle) {
171 return _strands.trySubmit(key, handle);
180 [[nodiscard]]
bool trySubmit(ModelId key, ::core::async::ParkedWork& work) {
181 return _strands.trySubmit(key, work);
187 [[nodiscard]]
bool runningHere(ModelId key)
const noexcept {
return _strands.runningHere(key); }
191 [[nodiscard]]
bool runningAnyHere() const noexcept {
return _strands.runningAnyHere(); }
195 [[nodiscard]]
bool idle()
const {
return _strands.idle(); }
207#if CORE_CPP_ASYNC_HAS_THREADS
215 void close() { _strands.close(); }
221 void seal() { _strands.seal(); }
240 template <
typename Stop>
241 void teardown(Stop&& stopHandlers, TeardownOrder order = buildTeardownOrder) {
242 if (order == TeardownOrder::SealThenStop) {
244 std::forward<Stop>(stopHandlers)();
246 std::forward<Stop>(stopHandlers)();
264 void enroll(ModelId key,
const std::shared_ptr<TaskResumer>& resumer) {
265 std::unique_lock
const lock{_enrolledMtx};
266 _enrolled.insert_or_assign(key, resumer);
267 _enrolledCount.store(_enrolled.size(), std::memory_order_release);
274 void withdraw(ModelId key,
const TaskResumer* resumer) {
275 std::unique_lock
const lock{_enrolledMtx};
276 if (
auto const found = _enrolled.find(key);
277 found != _enrolled.end() && found->second.lock().get() == resumer) {
278 _enrolled.erase(found);
280 _enrolledCount.store(_enrolled.size(), std::memory_order_release);
292 void operator()(
const ModelId& key, ::core::async::RunTask run)
const;
297 std::shared_mutex _enrolledMtx;
299 std::atomic<std::size_t> _enrolledCount{0};
300 std::unordered_map<ModelId, std::weak_ptr<TaskResumer>, ModelIdHash> _enrolled;
301 AroundTask _hook{
this};
303 ::core::async::KeyedStrands<ModelId, ModelIdHash> _strands;
322class TaskResumer final :
public ::core::async::IExecutor,
public std::enable_shared_from_this<TaskResumer> {
327 TaskResumer(std::shared_ptr<ModelStrands> strands, ModelId key, ::morph::session::Context session)
328 : _strands{std::move(strands)}, _key{key}, _session{std::move(
session)} {}
330 using ::core::async::IExecutor::submit;
336 void submit(std::coroutine_handle<> handle)
override {
337 if (!_strands->trySubmit(_key, handle)) {
350 void submit(::core::async::ParkedWork work)
override {
351 if (_strands->trySubmit(_key, work)) {
356 work.abandon.disarm();
357 resumeHere(work.resume);
362 void resumeHere(std::coroutine_handle<> handle) {
363 within([handle] { handle.resume(); });
372 template <
typename Body>
373 void within(Body&& body) {
374 std::shared_ptr<void>
const keep = shared_from_this();
375 ::morph::session::detail::ScopedContext
const scoped{_session};
376 ::core::async::ExecutorScope
const scope{*
this, &keep,
nullptr};
377 std::forward<Body>(body)();
381 std::shared_ptr<ModelStrands> _strands;
383 ::morph::session::Context _session;
386inline void ModelStrands::AroundTask::operator()(
const ModelId& key, ::core::async::RunTask run)
const {
390 if (
run.kind() != ::core::async::TaskKind::Resumption ||
391 self->_enrolledCount.load(std::memory_order_acquire) == 0) {
395 std::shared_ptr<TaskResumer> resumer;
397 std::shared_lock
const lock{self->_enrolledMtx};
398 if (
auto const found = self->_enrolled.find(key); found != self->_enrolled.end()) {
399 resumer = found->second.lock();
406 resumer->within(run);
Command run(MutationHandle< Action > const &mutation, Body< Action > const &body={}, RunOptions const &options={})
run: runs a mutation.
AnyHandle session(std::string const &name)
A session attribute, {"session": name}.
Compile-time profiler instrumentation: Tracy zones, plots, messages and thread names,...
#define MORPH_ZONE(name)
Opens a profiler zone named name that ends with the enclosing scope. At most one per scope.