Supervision dispatch module#

The supervision dispatch module provides peer discovery, cooperative lifecycle management, failure detection, and fenced action dispatch across localities, built on top of the supervision module’s lifecycle events and admission checks. See that module’s reference for the semantics of hpx::supervision::check_admission and hpx::supervision::dispatch_outcome; this page documents only the dispatch-specific surface layered on top of them. For the narrative on initialization ordering, one-shot discovery, shadow-state semantics, and client-side filtering versus fenced-dispatch admission, see Supervision dispatch.

Overview#

Functions#

Types#

Lifecycle initialization and shutdown#

hpx::shared_future<hpx::supervision::registry> hpx::supervision::init(hpx::chrono::steady_duration const &discovery_timeout = hpx::supervision::default_discovery_timeout)
hpx::supervision::registry hpx::supervision::init(hpx::launch::sync_policy, hpx::chrono::steady_duration const &discovery_timeout = hpx::supervision::default_discovery_timeout)

Performs one-shot, idempotent initialization of the supervision-dispatch runtime for this locality: creates a local registry, publishes event::started before its symbol name is registered, registers that name, and performs a single discover_and_join() pass. Calling init() while already active is a no-op; concurrent callers during initializing attach to the in-flight operation instead of racing it.

void hpx::supervision::finalize()

Performs one-shot, idempotent teardown of the runtime previously started by init(): publishes event::completed at the current/unchanged epoch, unregisters the registry’s symbol name, and releases the registry. A no-op unless the runtime is currently active. It returns promptly even while a failure detection sweep is still waiting on unresponsive peers, and hpx::disconnect() or hpx::stop() may follow right away: the sweep’s outstanding waits are resolved at the latest during runtime shutdown and do no further work once the supervision runtime has been finalized.

bool hpx::supervision::is_initialized() noexcept

Returns whether the runtime is currently active. Never blocks; safe to call from any thread, including while init()/finalize() is in flight.

Peer discovery#

constexpr std::chrono::milliseconds hpx::supervision::default_discovery_timeout#

Default timeout (60 seconds) used by init(), discover_peers(), fan_out_join(), and discover_and_join().

constexpr std::uint64_t hpx::supervision::unjoined_epoch#

Sentinel (std::numeric_limits<std::uint64_t>::max()) stored in discovered_peer::join_epoch for a peer that has not been joined yet. 0 cannot serve this purpose because it is a legitimate join epoch.

struct hpx::supervision::discovered_peer#

A peer whose registry symbol name resolved during a discover_peers() call. Holds locality, registry_client, and join_epoch. discover_peers() leaves join_epoch at hpx::supervision::unjoined_epoch (never 0, which is a legitimate join epoch); fan_out_join() fills it in with the epoch recorded by registry::join(). locality is cached for convenience but is never independently resolved or validated: it is always derived from registry_client itself (see registry::get_locality()), since exactly one registry exists per locality.

std::vector<hpx::supervision::discovered_peer> hpx::supervision::discover_peers(hpx::chrono::steady_duration const &timeout = hpx::supervision::default_discovery_timeout)#

Performs a one-time discovery pull: concurrently resolves the pinned registry name of every remote locality, bounded by a single wait for timeout. Localities that have not yet called init() are silently excluded rather than causing a hang or failure.

std::vector<hpx::supervision::discovered_peer> hpx::supervision::fan_out_join(hpx::supervision::registry const &local_registry, std::vector<hpx::supervision::discovered_peer> const &peers, hpx::chrono::steady_duration const &timeout = hpx::supervision::default_discovery_timeout)#

Fans out join() calls from local_registry to every entry in peers. Reuses registry::join()’s reservation/idempotency machinery, so repeated or overlapping calls never create more than one shadow per peer locality.

Returns a discovered_peer for each peer whose join() call settled successfully within timeout, in the same relative order as peers, with join_epoch filled in. Peers whose join() call did not settle in time, or settled with an exception, are omitted from the result (rather than left as gaps or defaulted entries), so the returned vector is a same-order subset of peers, not index-aligned with it. A join that timed out is retracted via registry::leave() if it later succeeds; a join that failed has nothing to retract. Either way, snapshot_peers() never reports a peer this call dropped.

std::vector<hpx::supervision::discovered_peer> hpx::supervision::discover_and_join(hpx::supervision::registry const &local_registry, hpx::chrono::steady_duration const &timeout = hpx::supervision::default_discovery_timeout)#

Composes a single discovery-and-join pass: one discover_peers() pull followed by one fan_out_join() call, then a short poll of local_registry.snapshot_peers() until every joined peer is visible there. timeout is a single budget shared by all three steps. Introduces no background thread, timer, or repeated broadcast.

Returns the discovered_peer vector produced by fan_out_join(). If the visibility poll runs out of time, the peers that did join are still returned, even though snapshot_peers() may not list them yet.

State query and event publication#

hpx::future<std::uint64_t> hpx::supervision::current_epoch(hpx::id_type const &locality)#
std::uint64_t hpx::supervision::current_epoch(hpx::launch::sync_policy, hpx::id_type const &locality, hpx::error_code &ec = hpx::throws)#

Returns the dispatch-cycle epoch of the supervision-dispatch runtime on locality: the counter init() increments once per initialization sequence (before registration and discovery, so a sequence that later fails still advances it) and publishes event::started under. 0 if no initialization sequence has run on locality. This is unrelated to any target’s lifecycle_state::epoch; registry::join() uses it to seed a new peer’s shadow from the peer’s real epoch.

hpx::future<hpx::supervision::lifecycle_state> hpx::supervision::query_state(hpx::supervision::registry const &handle)#
hpx::supervision::lifecycle_state hpx::supervision::query_state(hpx::launch::sync_policy, hpx::supervision::registry const &handle, hpx::error_code &ec = hpx::throws)#
hpx::future<hpx::supervision::lifecycle_state> hpx::supervision::query_state(hpx::supervision::registry const &handle, hpx::supervision::discovered_peer const &peer)#
hpx::supervision::lifecycle_state hpx::supervision::query_state(hpx::launch::sync_policy, hpx::supervision::registry const &handle, hpx::supervision::discovered_peer const &peer, hpx::error_code &ec = hpx::throws)#

The self variants resolve both locality and target to hpx::find_here() and query it on the local locality; the peer variants resolve both locality and target from peer.locality instead - handle is accepted only for overload resolution and symmetry with the self variants, and is otherwise unused in that case. Each overload is a thin forwarder over the corresponding raw-id query_state() overload in Supervision module; see that module’s reference for the fields of the returned lifecycle_state, including the epoch value useful for recovering the epoch a handle’s locality was started at.

hpx::future<hpx::supervision::publish_result> hpx::supervision::publish_event(hpx::supervision::registry const &handle, hpx::supervision::event ev, std::uint64_t epoch = 0)#
hpx::supervision::publish_result hpx::supervision::publish_event(hpx::launch::sync_policy, hpx::supervision::registry const &handle, hpx::supervision::event ev, std::uint64_t epoch = 0, hpx::error_code &ec = hpx::throws)#

Handle-based convenience overloads of hpx::supervision::publish_event that resolve both locality and target to hpx::find_here() instead of requiring the caller to name them explicitly. Thin forwarders over the raw-id publish_event() overloads in Supervision module. There is no peer variant: a locality publishes lifecycle events only for itself, never on behalf of a peer. The default epoch value of 0 is only correct for a locality’s very first publication after init(); once the target’s real epoch has advanced, 0 is stale. Callers publishing events after the initial one should first obtain the current epoch - e.g. via query_state(hpx::launch::sync, handle).epoch - and pass it explicitly.

Registry client#

class hpx::supervision::registry

A lightweight, self-supervising client handle for pairing with peer localities; mirrors a peer’s lifecycle state locally via join() and can itself be discovered by name in AGAS.

registry() = default#

Constructs an empty client that is not connected to any component. Does not create a server-side component.

explicit registry(hpx::id_type const &target_locality)#

Creates a new registry component on target_locality.

explicit registry(std::string const &symbolic_name)#

Connects to an existing registry component registered under symbolic_name (see register_name()).

registry(hpx::future<hpx::id_type> &&f)#

Implicit conversion from a future holding a component id.

hpx::future<hpx::supervision::joined_peer> join(hpx::id_type const &peer_locality) const#
hpx::supervision::joined_peer join(hpx::launch::sync_policy, hpx::id_type const &peer_locality, hpx::error_code &ec = hpx::throws) const#

Joins a peer locality: creates (or reuses) local supervision state mirroring the peer’s lifecycle. The returned joined_peer::target is peer_locality itself (not a generated local id) and is what dispatch_work() colocates against. Joining a peer that is already fully joined returns the existing joined_peer without seeding or registering observers again.

void leave(hpx::id_type const &peer_locality, hpx::future<hpx::supervision::joined_peer> &&f) const#

Attaches a fire-and-forget continuation to a pending join() future that retracts the join once it settles. Used by fan_out_join() for joins that did not complete within its timeout. Exceptions from the underlying join() are swallowed.

hpx::future<std::vector<hpx::supervision::server::peer_snapshot>> snapshot_peers() const#
std::vector<hpx::supervision::server::peer_snapshot> snapshot_peers(hpx::launch::sync_policy, hpx::error_code &ec = hpx::throws) const#

Returns a point-in-time snapshot of all fully joined, non-evicting peers.

hpx::future<bool> register_name()#
bool register_name(hpx::launch::sync_policy, hpx::error_code &ec = hpx::throws)#
hpx::future<hpx::id_type> unregister_name() const#
hpx::id_type unregister_name(hpx::launch::sync_policy, hpx::error_code &ec = hpx::throws) const#
hpx::id_type get_locality() const#

Returns the locality hosting this registry’s component.

struct hpx::supervision::joined_peer

Returned by registry::join(). Holds target, the peer’s locality (the value passed to join()), and join_epoch, the epoch the peer was joined at. join_epoch is used by registry::leave() to tell a join apart from a later re-join of the same locality. Note that joined_peer::join_epoch defaults to 0, while discovered_peer::join_epoch defaults to unjoined_epoch.

struct hpx::supervision::server::peer_snapshot

Plain-data view of a single joined peer: peer_locality and join_epoch.

Fenced dispatch#

template<typename Action, typename ...Ts>
decltype(auto) hpx::supervision::dispatch_work(hpx::id_type const &target, std::uint64_t epoch, Ts&&... ts)#

Dispatches Action to target under supervision fencing. Performs a cheap, non-authoritative admission check on the caller’s locality to short-circuit an already-known-fenced target, then dispatches to the target’s own locality (via hpx::colocated) where an authoritative re-check against the same latch consulted by hpx::supervision::check_admission runs immediately before the wrapped action executes, on the same thread, closing the admission/invocation race. See Supervision dispatch for the full client-side-filtering versus fenced-admission narrative and worked examples.

Returns:

A future holding the result of the dispatched action. If either admission check rejects the call, the future becomes exceptional with hpx::error::target_fenced (surfacing at get()), and the wrapped action is not invoked. dispatch_work() itself does not throw for a fenced target.

template<typename Action, typename ...Ts>
decltype(auto) hpx::supervision::dispatch_work(Action, hpx::id_type const &target, std::uint64_t epoch, Ts&&... ts)#

Same as above, with the action passed as an instance instead of a template argument. Constrained to hpx::traits::is_action_v<Action>.

template<typename Action, typename ...Ts>
decltype(auto) hpx::supervision::dispatch_work(Action, hpx::supervision::discovered_peer const &peer, Ts&&... ts)#

Dispatches against a peer returned by fan_out_join() or discover_and_join(); forwards to dispatch_work<Action>(peer.locality, peer.join_epoch, ts...). A peer whose join_epoch is still unjoined_epoch (e.g. one returned by discover_peers() alone) is rejected up front with the same target_fenced exceptional future.

template<std::meta::info F, typename ...Ts>
decltype(auto) hpx::supervision::dispatch_work(hpx::id_type const &target, std::uint64_t epoch, Ts&&... ts)#
template<std::meta::info F, typename ...Ts>
decltype(auto) hpx::supervision::dispatch_work(hpx::supervision::discovered_peer const &peer, Ts&&... ts)#

Reflection-based overloads, available only when HPX_HAVE_CXX26_REFLECTION is defined. F is a reflection of a namespace-scope free function (^^fn); these forward to the corresponding overloads above with hpx::actions::reflect_action<F> as the action.

template<typename Action, typename IdType, typename Epoch, typename ...Ts>
decltype(auto) hpx::supervision::invoke_fenced_action(Action act, IdType target, Epoch const epoch, Ts... ts)

The body that runs on the target’s locality once dispatch_work() has dispatched. Performs the authoritative check_admission() re-check and, if admitted, invokes act via hpx::sync with target and ts... (epoch is not forwarded).

Throws:

hpx::exception with hpx::error::target_fenced if the target has latched a terminal event for epoch; act is not invoked in that case. The caller of dispatch_work() sees this as the exceptional state of the returned future.

template<typename Action, typename IdType, typename Epoch, typename ...Ts>
struct hpx::supervision::fenced_action#

HPX action wrapper for invoke_fenced_action<Action, IdType, Epoch, Ts...>, dispatched by dispatch_work() via hpx::colocated.

template<typename Action>
struct hpx::supervision::make_fenced_action#

Type trait yielding the fenced_action specialization for Action, with IdType fixed to hpx::id_type, Epoch to std::uint64_t, and the remaining arguments taken from Action::arguments_type. Constrained to hpx::traits::is_action_v<Action>. make_fenced_action_t<Action> is shorthand for make_fenced_action<Action>::type.

Testing support#

The following are declared in hpx/supervision_dispatch/testing.hpp and are not part of the stable public dispatch API; they exist solely to make unit tests for this module deterministic:

std::vector<hpx::supervision::server::peer_snapshot> hpx::supervision::testing::local_snapshot_peers()
void hpx::supervision::testing::set_failure_detection_poll_timeout_for_testing(hpx::chrono::steady_duration const &timeout)
hpx::id_type hpx::supervision::testing::last_join_locality()
void hpx::supervision::testing::suspend_heartbeat_for_testing()
bool hpx::supervision::testing::failure_detection_sweep_in_flight_for_testing()
void hpx::supervision::testing::stop_background_loops()

See the API reference of this module for more details, and Supervision module for the underlying lifecycle event, admission, and dispatch-outcome semantics this module builds on.