morph
0.1.0
Asynchronous bridge between GUI thread and business models
Toggle main menu visibility
Loading...
Searching...
No Matches
owned_state.hpp
Go to the documentation of this file.
1
// SPDX-License-Identifier: Apache-2.0
2
3
#pragma once
4
#include <exception>
5
#include <memory>
6
#include <string>
7
#include <type_traits>
8
#include <utility>
9
10
#include "../../attributes.hpp"
11
#include "../completion.hpp"
12
#include "../executor.hpp"
13
#include "../logger.hpp"
14
#include "
owner_affinity.hpp
"
15
16
/// @file
17
/// @brief `OwnedState`: a component's state, and the one executor it is
18
/// touched on.
19
///
20
/// Specified in `docs/spec/core/executor.md`, "Owned state".
21
22
namespace
morph::exec::detail {
23
24
/// @brief A component's state, held by `std::shared_ptr`, and the owner every
25
/// access to it runs on.
26
///
27
/// What the storage types (`journal::SessionLog`, the action logs, the offline
28
/// queues, `offline::InMemoryReplayLedger`) are built on. Three ways in:
29
///
30
/// - `apply()` — a write. On the owner it runs now; anywhere else it is posted
31
/// to the owner, one hop, and the caller does not wait.
32
/// - `read()` — owner-only access, for a verb that returns data or a failure
33
/// the caller must see. Off the owner it is reported (asserted in a debug
34
/// build, handed to a test's probe otherwise).
35
/// - `ask()` — the same verb for a caller off the owner: runs on the owner and
36
/// answers with a `Completion` delivered on the executor the caller names.
37
///
38
/// A posted task holds the state, never the component, so the component may
39
/// be destroyed on any thread: what it posted still runs on the owner, and the
40
/// state is freed with the last task that holds it.
41
///
42
/// The owner must run one task at a time (a strand, a GUI executor, a pumped
43
/// `MainThreadExecutor`): state touched in two of its tasks at once would race.
44
/// @tparam State The component's state.
45
template
<
typename
State>
46
class
OwnedState {
47
public
:
48
/// @param owner The executor the state belongs to. Borrowed: it must
49
/// outlive this object and run what is posted to it.
50
/// @param state The state, built by the component's constructor.
51
OwnedState(IExecutor& owner MORPH_LIFETIMEBOUND, std::shared_ptr<State> state)
52
: _affinity{owner}, _state{std::move(state)} {}
53
54
/// @brief The owner.
55
/// @return The executor passed at construction.
56
[[nodiscard]] IExecutor& owner() const noexcept {
return
_affinity.owner(); }
57
58
/// @brief Whether the calling thread is on the owner (`OwnerAffinity::here`).
59
/// @return True inside an owner task, or on the constructing thread when it
60
/// was outside every task.
61
[[nodiscard]]
bool
here() const noexcept {
return
_affinity.here(); }
62
63
/// @brief Owner-only access to the state.
64
/// @param site A static string naming the verb, e.g. `"FileActionLog::flush"`.
65
/// @return The state.
66
[[nodiscard]]
State
& read(
char
const
* site)
const
noexcept
{
67
_affinity.note(site);
68
return
*_state;
69
}
70
71
/// @brief Runs @p body with the state on the owner: now when called there,
72
/// posted otherwise.
73
///
74
/// A throw out of a posted body has nowhere to go but the log, which names
75
/// @p site; on the owner it propagates to the caller.
76
/// @tparam Body Copyable callable taking `State&`.
77
/// @param site A static string naming the verb.
78
/// @param body The write.
79
template
<
typename
Body>
80
void
apply(
char
const
* site, Body body) {
81
if
(_affinity.here()) {
82
_affinity.note(site);
83
body(*_state);
84
return
;
85
}
86
_affinity.owner().post([affinity = _affinity, state = _state, site, body = std::move(body)]()
mutable
{
87
affinity.note(site);
88
try
{
89
body(*state);
90
}
catch
(
const
std::exception& exc) {
91
::morph::log::logError(
"{}: posted write failed: {}"
, site, exc.what());
92
}
catch
(...) {
93
::morph::log::logError(
"{}: posted write failed with an unknown exception"
, site);
94
}
95
});
96
}
97
98
/// @brief Answers @p body's result, computed with the state on the owner,
99
/// delivered on @p replyExec.
100
///
101
/// On the owner @p body runs now; elsewhere it is posted. Either way the
102
/// answer is delivered by a task on @p replyExec, and a throw out of
103
/// @p body rejects it.
104
/// @tparam T The answer's type.
105
/// @tparam Body Copyable callable taking `State&` and returning `T`.
106
/// @param site A static string naming the verb.
107
/// @param replyExec Where the answer is delivered and the caller attaches
108
/// its callbacks. Borrowed: it must outlive the completion.
109
/// @param body The read.
110
/// @return The answer.
111
template
<
typename
T,
typename
Body>
112
[[nodiscard]] ::morph::async::Completion<T> ask(
char
const
* site, IExecutor& replyExec MORPH_LIFETIMEBOUND,
113
Body body)
const
{
114
auto
settleable =
::morph::async::Completion<T>::makeSettleable
(&replyExec);
115
auto
answer = [affinity = _affinity, state = _state, site, body = std::move(body),
116
promise = std::make_shared<typename ::morph::async::Completion<T>::Promise>(
117
std::move(settleable.second))]()
mutable
{
118
affinity.note(site);
119
try
{
120
promise->resolve(body(*state));
121
}
catch
(...) {
122
promise->reject(std::current_exception());
123
}
124
};
125
if
(_affinity.here()) {
126
answer();
127
}
else
{
128
_affinity.owner().post(std::move(answer));
129
}
130
return
std::move(settleable.first);
131
}
132
133
private
:
134
OwnerAffinity _affinity;
135
std::shared_ptr<State> _state;
136
};
137
138
}
// namespace morph::exec::detail
morph::async::Completion::makeSettleable
static std::pair< Completion< T >, Promise > makeSettleable(::morph::exec::IExecutor *execPtr)
Constructs a Completion<T>/Promise<T> pair sharing one settleable state.
Definition
completion.hpp:738
morph::ui::detail::NameCategory::State
@ State
State.
Definition
document.hpp:2109
owner_affinity.hpp
"Am I on my owner?" for a component that belongs to one executor.
morph
core
detail
owned_state.hpp
Generated by
1.17.0