morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
morph::backend::LocalBackend Class Reference

In-process backend that executes model actions on a thread pool strand. More...

#include <backend.hpp>

Inheritance diagram for morph::backend::LocalBackend:

Public Member Functions

 LocalBackend (::morph::exec::IExecutor &workerPool)
 Constructs the backend using workerPool to run model actions.
 ~LocalBackend () override
 Stops the Task handlers still running, lets the strands drain, seals them, drains them again, and only then closes them (ModelStrands::teardown). See docs/spec/core/coroutines.md, "Teardown".
void setEventHandler (std::function< void(::morph::wire::EventBody)> handler, ::morph::exec::IExecutor *exec) override
 Installs the event handler deliverEvent posts to.
void deliverEvent (::morph::wire::EventBody event)
 Test seam: delivers event as if a server had sent it.
 LocalBackend (const LocalBackend &)=delete
LocalBackend & operator= (const LocalBackend &)=delete
 LocalBackend (LocalBackend &&)=delete
LocalBackend & operator= (LocalBackend &&)=delete
::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.
::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.
void setOwner (const ::morph::exec::detail::OwnerAffinity &owner) override
 Records the owner every verb below is called from.
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.
std::vector< std::string > listInstances (const std::string &typeId) override
 Lists the primary keys of live shared instances of typeId.
void deregisterModel (::morph::exec::detail::ModelId mid) override
 Removes the model with mid, or releases one attachment to it.
void notifyBackendChanged () override
 Schedules onBackendChanged() on each change-aware model's strand.
::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 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 cancelPending (const std::exception_ptr &exc) override
 Resolves every still-pending completion this backend produced with exc, and requests stop on every Task handler still running.
std::size_t trackedPendingCount () const
 Number of entries currently held in the pending list.

Detailed Description

In-process backend that executes model actions on a thread pool strand.

Each model instance gets its own strand so actions are serialised per-model without a global lock on the pool.

Its registry, its pending list and its record of Task runs belong to the caller's owner — the Bridge's, given through setOwner — and are touched only there, without a lock. Only the strand tasks it posts run elsewhere, and they reach nothing of the backend but what each carries.

Definition at line 1067 of file backend.hpp.

Constructor & Destructor Documentation

◆ LocalBackend()

morph::backend::LocalBackend::LocalBackend ( ::morph::exec::IExecutor & workerPool)
inlineexplicit

Constructs the backend using workerPool to run model actions.

Parameters
workerPoolExecutor (typically a ThreadPoolExecutor) for model work. Borrowed, not owned: this backend's strands run on it, so it must outlive the backend and keep running tasks until teardown completes — destroying it first deadlocks (see docs/spec/concurrency_and_lifetimes.md, "Destruction ordering").

Definition at line 1077 of file backend.hpp.

◆ ~LocalBackend()

morph::backend::LocalBackend::~LocalBackend ( )
inlineoverride

Stops the Task handlers still running, lets the strands drain, seals them, drains them again, and only then closes them (ModelStrands::teardown). See docs/spec/core/coroutines.md, "Teardown".

Where threads exist, in that order:

  1. Every live Task run's stop is requested. A handler suspended in an awaitable that resumes on the current executor – morph's own, core::async::AsyncQueue::pop – resumes, cancelled, through its strand, which still admits it. One suspended on a core::net socket or timer resumes on that loop instead, and unwinds there; its end is posted to the strand.
  2. The strands are drained: the resumptions and ends queued on them, and every queued action – skipped, if cancelPending already failed it. This drain comes before the seal because a handler's end that arrives from a loop thread now is still queued behind its instance's other tasks; sealed, it would run inline on the loop thread while this drain ran one of those tasks on a pool thread, two threads inside the instance's action gate.
  3. The strands are sealed: from here on a resumption or a handler's end is refused and runs inline, where it arrives, rather than queued on a strand step 5 would drop.
  4. The strands are drained again, for what reached them between steps 2 and 3. It does not wait for a handler still unwinding on another executor.
  5. The strands are closed.

Must not run on one of this backend's strand threads, whose drain it would wait for; a debug build asserts that. The single-threaded WebAssembly build seals, stops the handlers, then closes: sealed first, each stopped handler unwinds inline in the stop, and the drains wait for nothing, since nothing else could run the strands.

Definition at line 1112 of file backend.hpp.

Member Function Documentation

◆ setEventHandler()

void morph::backend::LocalBackend::setEventHandler ( std::function< void(::morph::wire::EventBody)> handler,
::morph::exec::IExecutor * exec )
inlineoverride

Installs the event handler deliverEvent posts to.

Parameters
handlerCallable posted to exec once per delivered event; nullptr clears.
execExecutor the handler is posted to. Borrowed: it must outlive the installation.

Definition at line 1127 of file backend.hpp.

◆ deliverEvent()

void morph::backend::LocalBackend::deliverEvent ( ::morph::wire::EventBody event)
inline

Test seam: delivers event as if a server had sent it.

A local backend has no server and produces no events itself; this lets a test drive the client's event path without one. Thread-safe. The handler is posted to the executor given to setEventHandler; with no handler installed the event is dropped.

Parameters
eventThe event to deliver.

Definition at line 1142 of file backend.hpp.

◆ registerModel()

::morph::exec::detail::ModelId morph::backend::LocalBackend::registerModel ( const std::string & ,
std::function< std::unique_ptr<::morph::model::detail::IModelHolder >()> factory )
inlineoverride

Creates a model instance via factory and registers it.

The typeId parameter is accepted for interface compatibility but not used — it is unnamed in the signature below, and the concrete type is captured by the factory closure. If the new holder's isBackendChangeAware() returns true, the new id (a local in createAndTrack, not a parameter here) is also recorded in _changeAware so notifyBackendChanged() finds it without a dynamic_cast sweep.

Parameters
factoryCallable that constructs the IModelHolder.
Returns
Newly assigned ModelId.

Definition at line 1173 of file backend.hpp.

◆ bindModel()

::morph::async::Completion<::morph::exec::detail::ModelId > morph::backend::LocalBackend::bindModel ( detail::BindRequest request,
::morph::exec::IExecutor & cbExec )
inlineoverride

Acquires an instance for request and settles the returned Completion before returning.

Every shape of BindRequest is answered here, against this backend's own directory: an empty primary creates a private instance; a non-empty one registers-or-attaches the shared instance for (typeId, primary), lazily evicting one whose first action failed (InstanceDirectory::attach); a non-zero current is released once the replacement is acquired, so a same-key re-attach keeps the instance it already holds and a throwing acquire never strands the caller.

Parameters
requestOwning bind request; moved from.
cbExecExecutor the continuation is delivered on. Borrowed: it must outlive the returned Completion.
Returns
A settled Completion carrying the bound id or the failure.

Definition at line 1195 of file backend.hpp.

◆ setOwner()

void morph::backend::LocalBackend::setOwner ( const ::morph::exec::detail::OwnerAffinity & owner)
inlineoverride

Records the owner every verb below is called from.

Parameters
ownerThe caller's owner.

Definition at line 1210 of file backend.hpp.

◆ assignPrimary()

void morph::backend::LocalBackend::assignPrimary ( ::morph::exec::detail::ModelId mid,
const std::string & typeId,
std::string_view primary )
inlineoverride

Enters an already-live, still-anonymous instance into the directory under primary.

Parameters
midLive instance to promote.
typeIdModel type id — the directory's first key component.
primaryCanonical string encoding of the key to file it under.

Definition at line 1217 of file backend.hpp.

◆ listInstances()

std::vector< std::string > morph::backend::LocalBackend::listInstances ( const std::string & typeId)
inlineoverride

Lists the primary keys of live shared instances of typeId.

Parameters
typeIdString type-id to enumerate.
Returns
Canonical key strings of the live shared instances, in unspecified order.

Definition at line 1232 of file backend.hpp.

◆ deregisterModel()

void morph::backend::LocalBackend::deregisterModel ( ::morph::exec::detail::ModelId mid)
inlineoverride

Removes the model with mid, or releases one attachment to it.

A private instance is erased outright. A shared instance has its attach count decremented and is erased — and removed from the directory — only when that count reaches zero, so releasing one handler never destroys an instance another handler still holds.

Parameters
midId returned by a prior registerModel()/bindModel() call.

Definition at line 1244 of file backend.hpp.

◆ notifyBackendChanged()

void morph::backend::LocalBackend::notifyBackendChanged ( )
inlineoverride

Schedules onBackendChanged() on each change-aware model's strand.

Only models recorded in _changeAware — maintained by createAndTrack/ deregisterModel from IModelHolder::isBackendChangeAware(), a compile-time answer per model type — are visited; there is no dynamic_cast and no scan of models that never opted in. Each such model's onBackendChanged() (the IModelHolder base virtual) is posted onto that model's own strand (the same per-ModelId serial queue execute uses), rather than invoked inline on the caller's thread. Two properties follow, and they are the whole point:

  • Strand-serialised, lock-free model code. onBackendChanged() runs on the pool inside the model's strand, so it never overlaps an execute() on the same model. A model reacting to a backend switch (e.g. draining an offline queue and mutating counters) needs no locking of its own state — exactly what offline.md promises.
  • Off the owner. The model body runs later on a pool thread, never inside Bridge::switchBackend. A model that wants to reach the bridge from onBackendChanged() posts to the bridge's owner.

A shared_ptr copy of each holder is captured into the posted task so a concurrent deregisterModel cannot free the model out from under its pending notification (mirrors execute's holder capture).

Definition at line 1278 of file backend.hpp.

◆ execute()

::morph::async::Completion< std::shared_ptr< void > > morph::backend::LocalBackend::execute ( ::morph::exec::detail::ModelId mid,
detail::ActionCall call,
::morph::exec::IExecutor * cbExec )
inlinefinal

Schedules call.localOp on the model's strand and returns a Completion.

The completion resolves with the opaque result on the strand thread and the callbacks are delivered via cbExec.

Expressed in terms of executeInto rather than beside it, so there is one definition of what a local dispatch does whichever entry point a caller uses. The extra CompletionState and its adapter sink are the price of the Completion-returning shape — which is exactly the cost executeInto exists to let Bridge avoid.

Why this is final
Bridge::executeVia calls executeInto, not execute. A subclass that overrode execute alone would therefore be bypassed for every bridge dispatch and intercept only the handful of direct execute callers — silently, with every test it has still passing on the paths it does reach. final turns that into a compile error naming the fix: derive from LocalBackend and override executeInto, which is the primitive both entry points now share. (A backend deriving straight from IBackend is unaffected: it overrides execute and inherits the default executeInto, which forwards to it.)
Parameters
midTarget model id.
callBundled action; localOp is the only field used here.
cbExecExecutor for delivering callbacks.
Returns
Completion that will carry the result or an exception.

Definition at line 1313 of file backend.hpp.

◆ executeInto()

void morph::backend::LocalBackend::executeInto ( ::morph::exec::detail::ModelId mid,
detail::ActionCall call,
::morph::exec::IExecutor * cbExec,
std::shared_ptr<::morph::async::detail::ISettleSink > sink )
inlineoverride

Schedules call.localOp on the model's strand and settles sink.

Parameters
midTarget model id.
callBundled action; localOp is the only field used here.
cbExecUnused here: a sink carries its own delivery. Accepted so the signature matches IBackend::executeInto, whose remote implementations do need it.
sinkSettled on the strand thread with the result or the exception, exactly once.

Definition at line 1332 of file backend.hpp.

◆ cancelPending()

void morph::backend::LocalBackend::cancelPending ( const std::exception_ptr & exc)
inlineoverride

Resolves every still-pending completion this backend produced with exc, and requests stop on every Task handler still running.

A run still waiting for its instance's action gate is failed where it waits (see admitLocal); a Task handler already running observes the stop on its token at its next co_await.

Parameters
excException delivered to every pending completion's error sink.

Definition at line 1421 of file backend.hpp.

◆ trackedPendingCount()

std::size_t morph::backend::LocalBackend::trackedPendingCount ( ) const
inlinenodiscard

Number of entries currently held in the pending list.

Not the number of calls in flight: the list is compacted amortised (see trackPending), so it also carries entries whose completion has already been destroyed and which cancelPending would skip. It is an upper bound on the in-flight count and a direct measure of what the compaction policy costs in memory — which is what it exists to make observable. For a count of in-flight calls, use Bridge::pendingCalls().

Returns
Size of the pending list, live and dead entries alike.

Definition at line 1458 of file backend.hpp.


The documentation for this class was generated from the following file: