20#include <unordered_map>
21#include <unordered_set>
25#include "../attributes.hpp"
26#include "../session/session.hpp"
27#include "completion.hpp"
28#include "detail/instance_directory.hpp"
31#include "observability.hpp"
33#include "registry.hpp"
37namespace morph::backend {
80 std::string_view modelTypeId;
85 std::string_view actionTypeId;
92 std::shared_ptr<void> action;
95 std::string (*serializeAction)(
const void* action) =
nullptr;
98 std::shared_ptr<void> (*deserializeResult)(std::string_view json) =
nullptr;
105 std::shared_ptr<void> (*localOp)(::morph::model::detail::IModelHolder& holder,
void* action) =
nullptr;
109 using LocalDone = std::function<void(std::shared_ptr<void>, std::exception_ptr)>;
119 void (*localOpAsync)(::morph::model::detail::IModelHolder& holder, std::shared_ptr<void> action,
120 const std::shared_ptr<::morph::exec::detail::TaskResumer>& executor,
121 ::core::async::StopToken token, LocalDone done) =
nullptr;
126 std::shared_ptr<::core::async::StopSource> stopSource;
132 ::morph::session::Context session;
145 [[nodiscard]] std::string serializeBody()
const {
146 if (serializeAction ==
nullptr) {
147 throw std::runtime_error{
"ActionCall::serializeAction is null: nothing to serialise"};
149 return serializeAction(action.get());
175 std::function<std::unique_ptr<::morph::model::detail::IModelHolder>()> factory;
178 std::string contextKey;
188 ::morph::exec::detail::ModelId current{};
196struct PromoteRequest {
198 ::morph::exec::detail::ModelId mid{};
222 virtual ~IBackend() =
default;
228 virtual ::morph::exec::detail::ModelId registerModel(
229 const std::string& typeId, std::function<std::unique_ptr<::morph::model::detail::IModelHolder>()> factory) = 0;
246 virtual ::morph::exec::detail::ModelId registerModelWithContext(
247 const std::string& typeId, std::function<std::unique_ptr<::morph::model::detail::IModelHolder>()> factory,
248 std::string_view contextKey) {
250 return registerModel(typeId, std::move(factory));
276 virtual void assignPrimary(::morph::exec::detail::ModelId mid,
const std::string& typeId,
277 std::string_view primary) {
306 virtual ::morph::async::Completion<::morph::exec::detail::ModelId> bindModel(BindRequest request,
307 ::morph::exec::IExecutor& cbExec) {
308 auto [completion, promise] =
311 auto const next = registerModelWithContext(
request.typeId, std::move(
request.factory),
request.contextKey);
313 deregisterModel(
request.current);
315 promise.resolve(next);
317 promise.reject(std::current_exception());
319 return std::move(completion);
341 virtual ::morph::async::Completion<::morph::exec::detail::ModelId> promoteModel(PromoteRequest request,
342 ::morph::exec::IExecutor& cbExec) {
343 auto [completion, promise] =
349 promise.reject(std::current_exception());
351 return std::move(completion);
363 virtual void setOwner(const ::morph::exec::detail::OwnerAffinity& owner) { (void)owner; }
376 virtual std::vector<std::string> listInstances(
const std::string& typeId) {
396 virtual ::morph::async::Completion<std::vector<std::string>> instances(
const std::string& typeId,
397 ::morph::exec::IExecutor& cbExec) {
398 auto [completion, promise] = ::morph::async::Completion<std::vector<std::string>>::makeSettleable(&cbExec);
400 promise.resolve(listInstances(typeId));
402 promise.reject(std::current_exception());
404 return std::move(completion);
413 virtual void deregisterModel(::morph::exec::detail::ModelId mid) = 0;
420 virtual ::morph::async::Completion<std::shared_ptr<void>> execute(::morph::exec::detail::ModelId mid,
422 ::morph::exec::IExecutor* cbExec) = 0;
457 virtual void executeInto(::morph::exec::detail::ModelId mid, ActionCall call, ::morph::exec::IExecutor* cbExec,
458 std::shared_ptr<::morph::async::detail::ISettleSink> sink) {
462 execute(mid, std::move(call), cbExec)
463 .then([sink](
const std::shared_ptr<void>& value) { sink->settleValue(value); })
464 .onError([sink = std::move(sink)](
const std::exception_ptr& exc) { sink->settleException(exc); });
468 virtual void notifyBackendChanged() = 0;
479 virtual void cancelPending(
const std::exception_ptr& exc) = 0;
504 virtual void setReconnectHandler(std::function<
void()> handler, ::morph::exec::IExecutor* exec) {
525 virtual void setEventHandler(std::function<
void(::morph::wire::EventBody)> handler,
526 ::morph::exec::IExecutor* exec) {
548 virtual void setConnectHandler(
const std::function<
void()>& handler) { (void)handler; }
563 virtual void setDisconnectHandler(
const std::function<
void()>& handler) { (void)handler; }
589 virtual void setSession(::morph::session::Context session) { (void)session; }
623 DisconnectedError() : std::runtime_error{
"transport disconnected before completion resolved"} {}
638 TimeoutError() : std::runtime_error{
"execute timed out on the server"} {}
707 : _inner{std::move(inner)}, _control{blockingExec} {
708 if (_inner ==
nullptr) {
709 throw std::invalid_argument{
"SynchronousBackendAdapter requires a backend to wrap"};
715 _control.teardown([] {});
731 [[nodiscard]] detail::IBackend&
wrapped() const noexcept {
return *_inner; }
745 note(
"SynchronousBackendAdapter::bindModel");
746 return dispatch(cbExec, [request = std::move(request)](detail::IBackend& inner,
748 return inner.bindModel(std::move(request), replyExec);
762 note(
"SynchronousBackendAdapter::promoteModel");
763 return dispatch(cbExec, [request = std::move(request)](detail::IBackend& inner,
765 return inner.promoteModel(std::move(request), replyExec);
772 void setOwner(const ::morph::exec::detail::OwnerAffinity& owner)
override { _affinity.emplace(owner); }
779 const std::string& typeId,
780 std::function<std::unique_ptr<::morph::model::detail::IModelHolder>()> factory)
override {
781 return runAndWait([typeId, factory = std::move(factory)](detail::IBackend& inner)
mutable {
782 return inner.registerModel(typeId, std::move(factory));
792 const std::string& typeId, std::function<std::unique_ptr<::morph::model::detail::IModelHolder>()> factory,
793 std::string_view contextKey)
override {
795 [typeId, factory = std::move(factory), key = std::string{contextKey}](detail::IBackend& inner)
mutable {
796 return inner.registerModelWithContext(typeId, std::move(factory), key);
804 void assignPrimary(::morph::exec::detail::ModelId mid,
const std::string& typeId,
805 std::string_view primary)
override {
806 static_cast<void>(runAndWait([mid, typeId, key = std::string{primary}](detail::IBackend& inner) {
807 inner.assignPrimary(mid, typeId, key);
815 std::vector<std::string>
listInstances(
const std::string& typeId)
override {
816 return runAndWait([typeId](detail::IBackend& inner) {
return inner.listInstances(typeId); });
822 post([mid](detail::IBackend& inner) { inner.deregisterModel(mid); });
831 detail::ActionCall call,
833 auto state = std::make_shared<::morph::async::detail::CompletionState<std::shared_ptr<void>>>();
836 std::make_shared<::morph::async::detail::CompletionSettleSink>(state));
846 std::shared_ptr<::morph::async::detail::ISettleSink> sink)
override {
847 post([mid, call = std::move(call), cbExec, sink = std::move(sink)](detail::IBackend& inner)
mutable {
849 inner.executeInto(mid, std::move(call), cbExec, sink);
851 sink->settleException(std::current_exception());
858 post([](detail::IBackend& inner) { inner.notifyBackendChanged(); });
879 note(
"SynchronousBackendAdapter::cancelPending");
880 auto snapshot = std::exchange(_pending, {});
881 _compactAt = kPendingCompactFloor;
882 for (
auto& pendingWeak : snapshot) {
883 if (
auto pending = pendingWeak.lock()) {
884 pending->cancelled.store(
true, std::memory_order_release);
885 pending->promise.reject(exc);
888 post([exc](detail::IBackend& inner) { inner.cancelPending(exc); });
895 post([handler = std::move(handler), exec](detail::IBackend& inner)
mutable {
896 inner.setReconnectHandler(std::move(handler), exec);
905 post([handler = std::move(handler), exec](detail::IBackend& inner)
mutable {
906 inner.setEventHandler(std::move(handler), exec);
913 post([handler](detail::IBackend& inner) { inner.setConnectHandler(handler); });
919 post([handler](detail::IBackend& inner) { inner.setDisconnectHandler(handler); });
926 [session = std::move(session)](detail::IBackend& inner)
mutable { inner.setSession(std::move(session)); });
935 struct PendingControl {
938 explicit PendingControl(
BindPromise dispatched) : promise{std::move(dispatched)} {}
946 std::atomic_bool cancelled{
false};
952 void note(
char const* site)
const noexcept {
954 _affinity->note(site);
961 template <
typename F>
963 _control.post(kControlStrand, [inner = _inner, task = std::move(task)]()
mutable { task(*inner); });
971 template <
typename F>
972 std::invoke_result_t<F&, detail::IBackend&> runAndWait(F task) {
973 using R = std::invoke_result_t<F&, detail::IBackend&>;
974 if (_control.runningHere(kControlStrand)) {
975 return task(*_inner);
977 auto done = std::make_shared<std::promise<R>>();
978 auto result = done->get_future();
979 post([done, task = std::move(task)](detail::IBackend& inner)
mutable {
981 done->set_value(task(inner));
983 done->set_exception(std::current_exception());
1001 template <
typename Op>
1002 ::morph::async::Completion<::morph::exec::detail::ModelId> dispatch(::morph::exec::IExecutor& cbExec, Op op) {
1003 using Settled = ::morph::async::Completion<::morph::exec::detail::ModelId>;
1004 auto [completion, promise] = Settled::makeSettleable(&cbExec);
1005 auto pending = std::make_shared<PendingControl>(std::move(promise));
1006 trackPending(pending);
1007 post([pending, op = std::move(op), replyExec = &cbExec](detail::IBackend& inner)
mutable {
1008 if (pending->cancelled.load(std::memory_order_acquire)) {
1012 auto answer = std::make_shared<Settled>(op(inner, *replyExec));
1013 replyExec->post([pending, answer] {
1014 answer->then([pending](::morph::exec::detail::ModelId mid) { pending->promise.resolve(mid); })
1015 .onError([pending](
const std::exception_ptr& failure) { pending->promise.reject(failure); });
1018 pending->promise.reject(std::current_exception());
1021 return std::move(completion);
1031 void trackPending(
const std::shared_ptr<PendingControl>& pending) {
1032 if (_pending.size() >= _compactAt) {
1033 std::erase_if(_pending, [](
const auto& weak) {
return weak.expired(); });
1034 _compactAt = std::max(kPendingCompactFloor, _pending.size() * 2);
1036 _pending.emplace_back(pending);
1042 static constexpr ::morph::exec::detail::ModelId kControlStrand{1};
1045 static constexpr std::size_t kPendingCompactFloor = 32;
1047 std::shared_ptr<detail::IBackend> _inner;
1048 ::morph::exec::detail::ModelStrands _control;
1049 std::optional<::morph::exec::detail::OwnerAffinity> _affinity;
1053 std::vector<std::weak_ptr<PendingControl>> _pending;
1055 std::size_t _compactAt = kPendingCompactFloor;
1078 : _strands{std::make_shared<::morph::exec::detail::ModelStrands>(workerPool)} {}
1113 note(
"LocalBackend::~LocalBackend");
1114 auto runs = std::exchange(_taskRuns, {});
1115 _strands->teardown([&runs] {
1116 for (
auto const& weak : runs) {
1117 if (
auto const run = weak.lock()) {
1118 run->stopSource->request_stop();
1129 std::scoped_lock
const lock{_eventMutex};
1146 std::scoped_lock
const lock{_eventMutex};
1147 handler = _eventHandler;
1150 if (!handler || exec ==
nullptr) {
1154 [handler = std::move(handler), event = std::move(event)]()
mutable { (*handler)(std::move(event)); });
1174 const std::string& ,
1175 std::function<std::unique_ptr<::morph::model::detail::IModelHolder>()> factory)
override {
1176 note(
"LocalBackend::registerModel");
1177 ::morph::observe::detail::emitMetric(::morph::observe::Metric::registerCount, 1.0);
1178 return createAndTrack(std::move(factory));
1197 note(
"LocalBackend::bindModel");
1198 auto [completion, promise] =
1201 promise.resolve(bindNow(std::move(request)));
1203 promise.reject(std::current_exception());
1205 return std::move(completion);
1210 void setOwner(const ::morph::exec::detail::OwnerAffinity& owner)
override { _affinity.emplace(owner); }
1217 void assignPrimary(::morph::exec::detail::ModelId mid,
const std::string& typeId,
1218 std::string_view primary)
override {
1219 note(
"LocalBackend::assignPrimary");
1220 if (primary.empty()) {
1226 (void)_instances.promote(mid, detail::DirectoryKey{typeId, std::string{primary}});
1233 note(
"LocalBackend::listInstances");
1234 return _instances.keysOfType(typeId);
1245 note(
"LocalBackend::deregisterModel");
1246 ::morph::observe::detail::emitMetric(::morph::observe::Metric::deregisterCount, 1.0);
1250 if (_instances.release(mid) == detail::InstanceDirectory::Release::destroyed) {
1251 _changeAware.erase(mid);
1279 note(
"LocalBackend::notifyBackendChanged");
1280 for (
auto modelId : _changeAware) {
1281 if (
const auto* inst = _instances.find(modelId)) {
1282 _strands->post(modelId, [held = inst->holder]()
mutable { held->onBackendChanged(); });
1314 detail::ActionCall call,
1316 auto compState = std::make_shared<::morph::async::detail::CompletionState<std::shared_ptr<void>>>();
1319 std::make_shared<::morph::async::detail::CompletionSettleSink>(compState));
1333 std::shared_ptr<::morph::async::detail::ISettleSink> sink)
override {
1337 note(
"LocalBackend::executeInto");
1339 std::shared_ptr<::morph::model::detail::IModelHolder> holder;
1340 std::shared_ptr<detail::HydrationState> hydration;
1344 if (
const auto* inst = _instances.find(mid)) {
1345 holder = inst->holder;
1346 hydration = inst->hydration;
1349 sink->settleException(
1350 std::make_exception_ptr(std::runtime_error(
"model not found: id=" + std::to_string(mid.v))));
1353 auto const admittedEpoch = trackPending(sink);
1355 run.localOp = call.localOp;
1356 run.localOpAsync = call.localOpAsync;
1357 run.stopSource = std::move(call.stopSource);
1362 run.action = std::move(call.action);
1363 run.session = std::move(call.session);
1364 run.modelTypeId = call.modelTypeId;
1365 run.actionTypeId = call.actionTypeId;
1366 run.holder = std::move(holder);
1367 run.sink = std::move(sink);
1368 run.hydration = std::move(hydration);
1369 run.strands = _strands;
1370 run.cancels = _cancels;
1371 run.admittedEpoch = admittedEpoch;
1378 run.inFlightCounter = _inFlight;
1379 auto const inFlightAfterInc = run.inFlightCounter->fetch_add(1, std::memory_order_relaxed) + 1;
1380 ::morph::observe::detail::emitMetric(::morph::observe::Metric::executeInFlight,
1381 static_cast<double>(inFlightAfterInc));
1385 if (run.localOpAsync !=
nullptr) {
1389 if (!run.stopSource) {
1390 run.stopSource = std::make_shared<::core::async::StopSource>();
1392 auto shared = std::make_shared<LocalRun>(std::move(run));
1393 rememberTaskRun(shared);
1395 [shared] { shared->holder->actionGate().enter([shared] { startTaskLocal(shared); }); });
1404 _strands->post(mid, [run = std::move(run)]()
mutable {
1405 auto& gate = run.holder->actionGate();
1406 if (gate.tryEnter()) {
1410 gate.enter([waiting = std::make_shared<LocalRun>(std::move(run))] { startLocal(*waiting); });
1422 note(
"LocalBackend::cancelPending");
1423 auto snapshot = std::exchange(_pending, {});
1424 _compactAt = kPendingCompactFloor;
1427 _cancels->record(exc);
1433 for (
auto& weak : snapshot) {
1434 if (
auto sink = weak.lock()) {
1439 sink->settleException(exc);
1442 for (
auto const& weak : _taskRuns) {
1443 if (
auto const run = weak.lock()) {
1444 static_cast<void>(run->stopSource->request_stop());
1459 note(
"LocalBackend::trackedPendingCount");
1460 return _pending.size();
1467 void note(
char const* site)
const noexcept {
1469 _affinity->note(site);
1476 ::morph::exec::detail::ModelId bindNow(detail::BindRequest request) {
1477 ::morph::observe::detail::emitMetric(::morph::observe::Metric::registerCount, 1.0);
1478 ::morph::exec::detail::ModelId next{};
1479 if (
request.primary.empty()) {
1480 auto [mid, holder] = createHolder(std::move(
request.factory));
1481 journalUnder(*holder,
request.contextKey);
1482 _instances.insertPrivate(mid, std::move(holder));
1486 if (
auto const attached = _instances.attach(dirKey)) {
1489 auto [mid, holder] = createHolder(std::move(
request.factory));
1491 _instances.insertShared(mid, std::move(holder), std::move(dirKey));
1497 if (
request.current.v != 0U) {
1498 deregisterModel(
request.current);
1509 static void journalUnder(::morph::model::detail::IModelHolder& holder, std::string
const& contextKey) {
1510 if (contextKey.empty()) {
1513 if (
auto log = ::morph::journal::defaultActionLog()) {
1514 holder.attachActionLog(std::move(log), contextKey);
1524 ::morph::exec::detail::ModelId createAndTrack(
1525 std::function<std::unique_ptr<::morph::model::detail::IModelHolder>()> factory) {
1526 auto [mid, holder] = createHolder(std::move(factory));
1527 _instances.insertPrivate(mid, std::move(holder));
1538 std::pair<::morph::exec::detail::ModelId, std::shared_ptr<::morph::model::detail::IModelHolder>> createHolder(
1539 std::function<std::unique_ptr<::morph::model::detail::IModelHolder>()> factory) {
1540 ::morph::exec::detail::ModelId
const mid{_nextId.fetch_add(1) + 1};
1541 std::shared_ptr<::morph::model::detail::IModelHolder> holder = factory();
1542 if (holder->isBackendChangeAware()) {
1543 _changeAware.insert(mid);
1545 return {mid, std::move(holder)};
1572 std::uint64_t trackPending(
const std::shared_ptr<::morph::async::detail::ISettleSink>& sink) {
1573 if (_pending.size() >= _compactAt) {
1574 std::erase_if(_pending, [](
const auto& weak) {
return weak.expired(); });
1575 _compactAt = std::max(kPendingCompactFloor, _pending.size() * 2);
1577 _pending.emplace_back(sink);
1578 return _cancels->epoch();
1589 class CancelRecord {
1592 void record(
const std::exception_ptr& reason) {
1593 auto node = std::make_unique<Node>(Node{.reason = reason, .previous = std::move(_chain)});
1594 _latest.store(node.get(), std::memory_order_release);
1595 _chain = std::move(node);
1596 _epoch.fetch_add(1);
1600 [[nodiscard]] std::uint64_t epoch() const noexcept {
return _epoch.load(); }
1603 [[nodiscard]] std::exception_ptr lastReason()
const {
1604 Node const*
const node = _latest.load(std::memory_order_acquire);
1605 return node ==
nullptr ? std::exception_ptr{} : node->reason;
1610 std::exception_ptr reason;
1611 std::unique_ptr<Node> previous;
1613 std::atomic<std::uint64_t> _epoch{0};
1614 std::atomic<Node const*> _latest{
nullptr};
1615 std::unique_ptr<Node> _chain;
1623 std::shared_ptr<void> (*localOp)(::morph::model::detail::IModelHolder&,
void*) =
nullptr;
1624 decltype(detail::ActionCall::localOpAsync) localOpAsync =
nullptr;
1625 std::shared_ptr<::core::async::StopSource> stopSource;
1626 std::shared_ptr<void> action;
1627 ::morph::session::Context
session;
1628 std::string_view modelTypeId;
1629 std::string_view actionTypeId;
1630 std::shared_ptr<::morph::model::detail::IModelHolder> holder;
1631 std::shared_ptr<::morph::async::detail::ISettleSink> sink;
1632 std::shared_ptr<detail::HydrationState> hydration;
1633 std::shared_ptr<std::atomic<std::size_t>> inFlightCounter;
1634 std::shared_ptr<::morph::exec::detail::ModelStrands> strands;
1635 std::shared_ptr<CancelRecord> cancels;
1636 std::uint64_t admittedEpoch = 0;
1637 ::morph::exec::detail::ModelId mid{};
1638 std::chrono::steady_clock::time_point start;
1639 ::morph::observe::SpanId spanId{};
1645 static bool admitLocal(LocalRun& run) {
1646 run.start = std::chrono::steady_clock::now();
1647 run.spanId = ::morph::observe::detail::beginSpan(
run.session.requestId,
run.modelTypeId,
run.actionTypeId);
1648 if (
run.cancels->epoch() ==
run.admittedEpoch) {
1659 finishLocal(run,
nullptr,
run.cancels->lastReason());
1665 static void startLocal(LocalRun& run) {
1668 if (!admitLocal(run)) {
1671 std::shared_ptr<void> value;
1672 std::exception_ptr error;
1674 ::morph::session::detail::ScopedContext
const scoped{
run.session};
1681 if (
run.localOp ==
nullptr) {
1682 throw std::runtime_error{
"ActionCall::localOp is null: nothing to execute"};
1684 value =
run.localOp(*
run.holder,
run.action.get());
1686 error = std::current_exception();
1688 finishLocal(run, std::move(value), error);
1694 static void startTaskLocal(
const std::shared_ptr<LocalRun>& run) {
1697 if (!admitLocal(*run)) {
1700 std::shared_ptr<::morph::exec::detail::TaskResumer> executor;
1702 ::morph::session::detail::ScopedContext
const scoped{
run->session};
1703 executor = std::make_shared<::morph::exec::detail::TaskResumer>(
run->strands,
run->mid,
run->session);
1704 run->strands->enroll(
run->mid, executor);
1705 auto token =
run->stopSource->get_token();
1706 run->localOpAsync(*
run->holder,
run->action, executor, std::move(token),
1707 [run, resumer = executor.get()](std::shared_ptr<void> value, std::exception_ptr error) {
1708 run->strands->withdraw(run->mid, resumer);
1713 run->strands->runOnStrand(run->mid,
1714 [run, value = std::move(value), error = std::move(error)] {
1715 finishLocal(*run, value, error);
1719 run->strands->withdraw(
run->mid, executor.get());
1720 finishLocal(*run,
nullptr, std::current_exception());
1726 static void finishLocal(LocalRun& run, std::shared_ptr<void> value,
const std::exception_ptr& error) {
1729 bool const succeeded = error ==
nullptr;
1746 if (
run.hydration) {
1747 run.hydration->settle(succeeded);
1749 ::morph::observe::detail::endSpan(
run.spanId, succeeded);
1750 auto const elapsedMs =
1751 std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() -
run.start).count();
1752 std::array<std::pair<std::string_view, std::string_view>, 2>
const tags{
1753 {{
"modelType",
run.modelTypeId}, {
"actionType",
run.actionTypeId}}};
1754 ::morph::observe::detail::emitMetric(::morph::observe::Metric::executeLatencyMs, elapsedMs, tags);
1756 ::morph::observe::detail::emitMetric(::morph::observe::Metric::executeErrors, 1.0, tags);
1758 auto const inFlightAfterDec =
run.inFlightCounter->fetch_sub(1, std::memory_order_relaxed) - 1;
1759 ::morph::observe::detail::emitMetric(::morph::observe::Metric::executeInFlight,
1760 static_cast<double>(inFlightAfterDec));
1764 run.sink->settleValue(std::move(value));
1766 run.sink->settleException(error);
1768 run.holder->actionGate().leave();
1773 void rememberTaskRun(
const std::shared_ptr<LocalRun>& run) {
1774 if (_taskRuns.size() >= _taskRunsCompactAt) {
1775 std::erase_if(_taskRuns, [](
const auto& weak) {
return weak.expired(); });
1776 _taskRunsCompactAt = std::max(kPendingCompactFloor, _taskRuns.size() * 2);
1778 _taskRuns.emplace_back(run);
1783 std::shared_ptr<::morph::exec::detail::ModelStrands> _strands;
1785 std::optional<::morph::exec::detail::OwnerAffinity> _affinity;
1790 detail::InstanceDirectory _instances;
1800 std::unordered_set<::morph::exec::detail::ModelId, ::morph::exec::detail::ModelIdHash> _changeAware;
1801 std::atomic<uint64_t> _nextId{0};
1805 static constexpr std::size_t kPendingCompactFloor = 32;
1809 std::vector<std::weak_ptr<::morph::async::detail::ISettleSink>> _pending;
1812 std::size_t _compactAt = kPendingCompactFloor;
1815 std::shared_ptr<CancelRecord> _cancels = std::make_shared<CancelRecord>();
1818 std::vector<std::weak_ptr<LocalRun>> _taskRuns;
1820 std::mutex _eventMutex;
1823 std::size_t _taskRunsCompactAt = kPendingCompactFloor;
1827 std::shared_ptr<std::atomic<std::size_t>> _inFlight = std::make_shared<std::atomic<std::size_t>>(0);
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}.
Producer-side handle paired with a Completion<T> by makeSettleable().
Move-only handle representing the eventual result of an asynchronous operation.
static std::pair< Completion< T >, Promise > makeSettleable(::morph::exec::IExecutor *execPtr)
Constructs a Completion<T>/Promise<T> pair sharing one settleable state.
In-process backend that executes model actions on a thread pool strand.
std::vector< std::string > listInstances(const std::string &typeId) override
Lists the primary keys of live shared instances of typeId.
LocalBackend(::morph::exec::IExecutor &workerPool)
Constructs the backend using workerPool to run model actions.
::morph::exec::detail::ModelId registerModel(const std::string &, std::function< std::unique_ptr<::morph::model::detail::IModelHolder >()> factory) override
Creates a model instance via factory and registers it.
std::size_t trackedPendingCount() const
Number of entries currently held in the pending list.
::morph::async::Completion< std::shared_ptr< void > > execute(::morph::exec::detail::ModelId mid, detail::ActionCall call, ::morph::exec::IExecutor *cbExec) final
Schedules call.localOp on the model's strand and returns a Completion.
void deliverEvent(::morph::wire::EventBody event)
Test seam: delivers event as if a server had sent it.
void setEventHandler(std::function< void(::morph::wire::EventBody)> handler, ::morph::exec::IExecutor *exec) override
Installs the event handler deliverEvent posts to.
void cancelPending(const std::exception_ptr &exc) override
Resolves every still-pending completion this backend produced with exc, and requests stop on every Ta...
void deregisterModel(::morph::exec::detail::ModelId mid) override
Removes the model with mid, or releases one attachment to it.
void assignPrimary(::morph::exec::detail::ModelId mid, const std::string &typeId, std::string_view primary) override
Enters an already-live, still-anonymous instance into the directory under primary.
void executeInto(::morph::exec::detail::ModelId mid, detail::ActionCall call, ::morph::exec::IExecutor *cbExec, std::shared_ptr<::morph::async::detail::ISettleSink > sink) override
Schedules call.localOp on the model's strand and settles sink.
void notifyBackendChanged() override
Schedules onBackendChanged() on each change-aware model's strand.
void setOwner(const ::morph::exec::detail::OwnerAffinity &owner) override
Records the owner every verb below is called from.
~LocalBackend() override
Stops the Task handlers still running, lets the strands drain, seals them, drains them again,...
::morph::async::Completion<::morph::exec::detail::ModelId > bindModel(detail::BindRequest request, ::morph::exec::IExecutor &cbExec) override
Acquires an instance for request and settles the returned Completion before returning.
Makes a blocking backend reach its caller without blocking it — and without touching the backend.
void cancelPending(const std::exception_ptr &exc) override
Rejects the binds this adapter produced, then queues the wrapped backend's own cancelPending.
SynchronousBackendAdapter(std::shared_ptr< detail::IBackend > inner, ::morph::exec::IExecutor &blockingExec)
Wraps inner, running every one of its verbs on blockingExec.
void notifyBackendChanged() override
Queues the wrapped backend's notifyBackendChanged.
~SynchronousBackendAdapter() override
Waits for every queued and in-flight call; see "Ordering" above.
void executeInto(::morph::exec::detail::ModelId mid, detail::ActionCall call, ::morph::exec::IExecutor *cbExec, std::shared_ptr<::morph::async::detail::ISettleSink > sink) override
Queues the wrapped backend's executeInto on the strand.
::morph::async::Completion<::morph::exec::detail::ModelId > promoteModel(detail::PromoteRequest request, ::morph::exec::IExecutor &cbExec) override
Files an already-live instance under a key without blocking the caller.
::morph::async::Completion<::morph::exec::detail::ModelId >::Promise BindPromise
The producer side of a bindModel/promoteModel completion.
void setConnectHandler(const std::function< void()> &handler) override
Queues the installation on the wrapped backend.
void setSession(::morph::session::Context session) override
Queues the session on the wrapped backend.
void setOwner(const ::morph::exec::detail::OwnerAffinity &owner) override
Records the caller's owner. Not forwarded: the wrapped backend's owner is this adapter's control stra...
void assignPrimary(::morph::exec::detail::ModelId mid, const std::string &typeId, std::string_view primary) override
Runs the wrapped backend's assignPrimary on the strand and waits for it.
void setEventHandler(std::function< void(::morph::wire::EventBody)> handler, ::morph::exec::IExecutor *exec) override
Queues the installation on the wrapped backend.
::morph::async::Completion< std::shared_ptr< void > > execute(::morph::exec::detail::ModelId mid, detail::ActionCall call, ::morph::exec::IExecutor *cbExec) override
Queues the dispatch on the strand and returns its Completion at once.
std::vector< std::string > listInstances(const std::string &typeId) override
Runs the wrapped backend's listInstances on the strand and waits for it.
::morph::async::Completion<::morph::exec::detail::ModelId > bindModel(detail::BindRequest request, ::morph::exec::IExecutor &cbExec) override
Acquires a model instance without blocking the calling thread.
::morph::exec::detail::ModelId registerModel(const std::string &typeId, std::function< std::unique_ptr<::morph::model::detail::IModelHolder >()> factory) override
Runs the wrapped backend's registerModel on the strand and waits for it.
void setDisconnectHandler(const std::function< void()> &handler) override
Queues the installation on the wrapped backend.
void deregisterModel(::morph::exec::detail::ModelId mid) override
Queues the wrapped backend's deregisterModel.
detail::IBackend & wrapped() const noexcept
The wrapped backend.
void setReconnectHandler(std::function< void()> handler, ::morph::exec::IExecutor *exec) override
Queues the installation on the wrapped backend.
::morph::exec::detail::ModelId registerModelWithContext(const std::string &typeId, std::function< std::unique_ptr<::morph::model::detail::IModelHolder >()> factory, std::string_view contextKey) override
Runs the wrapped backend's registerModelWithContext on the strand and waits for it.
LowerCtx attached(DetachedLower const &held)
Puts a detached context's scope back.
"Am I on my owner?" for a component that belongs to one executor.
Compile-time profiler instrumentation: Tracy zones, plots, messages and thread names,...
#define MORPH_ZONE_TEXT(text)
Attaches text to the enclosing scope's MORPH_ZONE; an empty text attaches nothing.
#define MORPH_ZONE(name)
Opens a profiler zone named name that ends with the enclosing scope. At most one per scope.
void request(Store< ViewState, Msg > &store, bridge::BridgeHandler< Model, Sharing > &handler, async::CallbackScope const &scope, Action action, ToMsg toMsg, ToFailMsg toFailMsg)
Executes action and sends its result, or its failure, to store as one message.
morph's strands: one per model instance, from core-cpp's core::async::KeyedStrands,...
BackendChangedError()
Constructs the error with a canned diagnostic message.
BridgeDestroyedError()
Constructs the error with a canned diagnostic message.
ClientTimeoutError()
Constructs the error with a canned diagnostic message.
DisconnectedError()
Constructs the error with a canned diagnostic message.
HandlerDestroyedError()
Constructs the error with a canned diagnostic message.
TimeoutError()
Constructs the error with a canned diagnostic message.
Abstract executor interface.
virtual void post(std::function< void()> task)=0
Schedules task for asynchronous execution.
Per-call session context carried through the bridge to the model.
The decoded body of an event envelope.
std::shared_ptr< NodeData const > Node
A view-tree node: immutable, shared data.