morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
owner_strand.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4#include <core/async/ExecutorContext.hpp>
5#include <core/async/Strand.hpp>
6#include <functional>
7#include <type_traits>
8#include <utility>
9
10#include "../attributes.hpp"
11#include "executor.hpp"
12#include "strand.hpp"
13
14/// @file
15/// @brief `OwnerStrand`: one core-cpp strand over a morph executor, which is
16/// itself a morph executor — the owner a component's state belongs to.
17///
18/// Specified in `docs/spec/core/executor.md`, "Owner strands".
19
20namespace morph::exec {
21
22/// @brief One strand over a morph executor, usable wherever a morph executor
23/// is: the owner a component's state belongs to.
24///
25/// Tasks run one at a time, in the order they were posted, on the threads of
26/// the base executor. A component keeps its state in tasks on one of these
27/// and needs no lock for it: `RemoteServer` keeps its registry here, and
28/// `ReconnectCoordinator` its reconnect sequence.
29///
30/// Every task runs inside an `ExecutorScope` naming this object's
31/// `coreExecutor()`, so inside a task `runningOn(*this)` is true — as is
32/// `runningOn(base)`, whose scope the strand's batch runs within — and a
33/// coroutine that suspends in a task and resumes on the current executor comes
34/// back here. Outside every task both are false.
35///
36/// Being a `morph::exec::IExecutor` is what lets a component hand its owner out
37/// as a `Completion`'s callback executor, or to a collaborator that must run
38/// on the same owner (`SyncWorker`, on the coordinator's strand).
40public:
41 /// @param base Where the strand's tasks run. Borrowed: it must outlive this
42 /// object and keep running tasks until it is closed.
43 explicit OwnerStrand(::morph::exec::IExecutor& base MORPH_LIFETIMEBOUND)
44 : _strand{base.coreExecutor(),
45 ::core::async::StrandOptions{.aroundTask = ::core::async::AroundTask::of(_hook)}} {}
46
47 OwnerStrand(const OwnerStrand&) = delete;
48 OwnerStrand& operator=(const OwnerStrand&) = delete;
49 OwnerStrand(OwnerStrand&&) = delete;
50 OwnerStrand& operator=(OwnerStrand&&) = delete;
51
52 /// @brief Closes the strand: what is still queued is dropped, and a task
53 /// running on another thread is waited for.
54 ~OwnerStrand() override { _strand.close(); }
55
56 /// @brief Queues @p task after everything posted before it. A throw out of
57 /// it is logged. Dropped once the strand is closed.
58 /// @param task Callable to run on the strand.
59 void post(std::function<void()> task) override {
60 _strand.post(detail::LoggedTask<std::function<void()>>{std::move(task)});
61 }
62
63 /// @brief Queues @p task, held by value in the strand's one allocation for
64 /// it, after everything posted before it. A throw out of it is
65 /// logged. Dropped once the strand is closed.
66 ///
67 /// What a component posts to its own strand: no `std::function` in
68 /// between, so a move-only capture is fine and a post costs one allocation.
69 /// @tparam F The callable's type.
70 /// @param task Callable to run on the strand.
71 template <typename F>
72 void postTask(F&& task) {
73 _strand.post(detail::LoggedTask<std::decay_t<F>>{std::forward<F>(task)});
74 }
75
76 /// @brief Whether the calling thread is inside one of this strand's tasks.
77 /// @return True inside a task, at any depth.
78 [[nodiscard]] bool runningHere() const noexcept { return _strand.runningHere(); }
79
80 /// @brief Whether nothing is queued or running.
81 /// @return True when idle. Racy by nature where other threads post.
82 [[nodiscard]] bool idle() const { return _strand.idle(); }
83
84 /// @brief Closes the strand, as the destructor does. Idempotent.
85 void close() { _strand.close(); }
86
87 /// @brief Refuses the strand's try-forms and keeps running what is queued;
88 /// `post` is still admitted until `close()`. Idempotent.
89 void seal() { _strand.seal(); }
90
91private:
92 /// States this strand's identity around each task it runs.
93 struct Hook {
94 OwnerStrand* self;
95 void operator()(::core::async::RunTask run) const {
96 ::core::async::ExecutorScope const scope{self->coreExecutor()};
97 run();
98 }
99 };
100
101 Hook _hook{this};
102 /// After the hook, so it is destroyed first: it calls the hook.
103 ::core::async::Strand _strand;
104};
105
106} // namespace morph::exec
One strand over a morph executor, usable wherever a morph executor is: the owner a component's state ...
bool idle() const
Whether nothing is queued or running.
void post(std::function< void()> task) override
Queues task after everything posted before it. A throw out of it is logged. Dropped once the strand i...
void close()
Closes the strand, as the destructor does. Idempotent.
void postTask(F &&task)
Queues task, held by value in the strand's one allocation for it, after everything posted before it....
bool runningHere() const noexcept
Whether the calling thread is inside one of this strand's tasks.
~OwnerStrand() override
Closes the strand: what is still queued is dropped, and a task running on another thread is waited fo...
OwnerStrand(::morph::exec::IExecutor &base)
void seal()
Refuses the strand's try-forms and keeps running what is queued; post is still admitted until close()...
morph's strands: one per model instance, from core-cpp's core::async::KeyedStrands,...
Abstract executor interface.
Definition executor.hpp:70
::core::async::IExecutor & coreExecutor() noexcept
This executor as a core-cpp executor: the identity an core::async::ExecutorScope names.
Definition executor.hpp:100