Skip to main content

MigrationTargetHandler

Struct MigrationTargetHandler 

Source
pub struct MigrationTargetHandler { /* private fields */ }
Expand description

Handles the target node’s role in daemon migration.

The target handler:

  1. Restores a daemon from a snapshot (phase 2)
  2. Replays buffered events in strict sequence order (phase 3)
  3. Activates as the authoritative copy after cutover (phase 4)

Implementations§

Source§

impl MigrationTargetHandler

Source

pub fn new(daemon_registry: Arc<DaemonRegistry>) -> Self

Create a new target handler with no daemon factories registered.

Use this on nodes that are source-only, or in unit tests that call restore_snapshot directly with an inline factory closure. For a node that the subprotocol handler should auto-restore onto, use MigrationTargetHandler::new_with_factories instead.

Source

pub fn new_with_factories( daemon_registry: Arc<DaemonRegistry>, factories: Arc<DaemonFactoryRegistry>, ) -> Self

Create a target handler backed by a shared factory registry.

The subprotocol handler resolves restore inputs through this registry; if a migration arrives for an origin that hasn’t been registered, the handler fails the migration instead of silently ignoring it.

Source

pub fn factories(&self) -> &Arc<DaemonFactoryRegistry>

Access the factory registry (for the subprotocol handler).

Fetch the locally-restored daemon’s chain head. Used by the subprotocol handler to stamp ReplayComplete.target_head so the orchestrator’s continuity-proof anchor carries the real cryptographic head even when the orchestrator lives on a third node and has no local registry entry to consult.

Source

pub fn restore_snapshot<F>( &self, ctx: RestoreContext<'_>, keypair: EntityKeypair, daemon_factory: F, config: DaemonHostConfig, ) -> Result<(), MigrationError>
where F: FnOnce() -> Box<dyn MeshDaemon>,

Phase 2: Restore a daemon from a snapshot.

Creates a new DaemonHost from the snapshot and registers it in the local daemon registry. The daemon is not yet authoritative — events will be replayed before cutover.

The daemon_factory closure creates the daemon implementation that will be restored from the snapshot. The caller must provide the correct daemon type matching the origin hash. orchestrator_node is the node that initiated this migration; reply messages route here, not to the immediate wire hop.

Source

pub fn orchestrator_node(&self, daemon_origin: u64) -> Option<u64>

Recorded orchestrator for an active or recently-completed target-side migration.

Source

pub fn replay_events( &self, daemon_origin: u64, events: Vec<CausalEvent>, ) -> Result<u64, MigrationError>

Phase 3: Replay buffered events from the source.

Events are inserted into a BTreeMap keyed by sequence and replayed in strict order. Returns the sequence number replayed through.

Source

pub fn buffer_event( &self, daemon_origin: u64, event: CausalEvent, ) -> Result<bool, MigrationError>

Buffer an event arriving during migration (before cutover).

Events that arrive out-of-order are buffered in the BTreeMap and will be replayed in sequence order.

Phase guard: once activate() has flipped the state to Cutover, the normal delivery path is authoritative for this daemon — a stale migration-path event arriving here would be inserted into pending_events and drain_pending would re-deliver it through the registry, producing duplicate execution alongside the post-cutover normal-path delivery of the same sequence. Reject Cutover events with Ok(false) (same surface as a not-found origin) so the caller treats the event as already-handled rather than retrying.

MigrationPhase::Complete is not checked here because complete() removes the entry from self.migrations rather than advancing the phase: a Complete-phased entry never exists in this map, and the migrations.get miss above returns Ok(false) first.

Source

pub fn activate(&self, daemon_origin: u64) -> Result<u64, MigrationError>

Phase 4: Activate — daemon goes live on this node.

Drains any remaining pending events and marks the daemon as the authoritative copy. Idempotent for a retried ActivateTarget after a lost ActivateAck: if no active migration exists but a completed record does, returns the stored replayed_through so the subprotocol handler can re-emit the same ack.

An active migration in self.migrations always takes precedence over a completed record for the same origin: a new migration for the same daemon (e.g., migrated back to us later) must not be skipped just because we still remember the previous completion.

Source

pub fn complete(&self, daemon_origin: u64) -> Result<(), MigrationError>

Mark migration as complete and move tracking state into the completed index so that a retried ActivateTarget after a lost ActivateAck can be handled idempotently.

The daemon remains registered in the daemon registry — it’s now the authoritative copy. Also removes the factory entry, since the target won’t need to re-restore from an orchestrator retry once the migration has successfully completed.

Atomicity vs activate() and abort(): the migrations write entry is held across the entire operation. That guard serializes us against:

  • a retried activate(), which calls migrations.get() and blocks on the shard write lock; once we drop the entry the migration is gone but completed already has the idempotency record, so the retry resolves through the completed lookup;
  • a concurrent abort(), which would otherwise observe an empty migrations after a remove-first, insert-second ordering and daemon_registry.unregister() a daemon we just promoted to authoritative. Holding the entry forces abort to wait, and it then finds nothing and no-ops — which matches the legacy semantics where a successful complete makes a racing abort a no-op.

completed.insert happens while the entry is held, so a third thread observing both maps still sees the migration in at least one of them at every instant — closing the original DaemonNotFound gap on activate() retries.

Source

pub fn forget_completed(&self, daemon_origin: u64) -> bool

Forget a completed migration’s retry-idempotency record. Safe to call at any time; a subsequent retried ActivateTarget would then fail normally with DaemonNotFound.

Source

pub fn abort(&self, daemon_origin: u64) -> Result<(), MigrationError>

Abort migration — unregister daemon and clean up.

Also clears any idempotency record in completed. Pre-fix only the active-migration entry was removed; a daemon that had been complete-d (and thus already had a completed idempotency record) and was THEN aborted would leak the completed entry indefinitely. The leak was minor (one 32-bit key + a CompletedTargetState per affected daemon) but unbounded — every aborted post-completion migration accumulated forever, since the only other clearance path (forget_completed) is keyed off a successful source-side cleanup that never arrives in the abort path. Clearing both indices makes abort idempotent in the strong sense: post-abort state matches pre-start_restore state.

Source

pub fn is_migrating(&self, daemon_origin: u64) -> bool

Check if a daemon is being migrated to this node.

Source

pub fn phase(&self, daemon_origin: u64) -> Option<MigrationPhase>

Get the current phase of a target-side migration.

Source

pub fn replayed_through(&self, daemon_origin: u64) -> Option<u64>

Get the sequence number replayed through.

Source

pub fn active_count(&self) -> usize

Number of active target-side migrations.

Trait Implementations§

Source§

impl Debug for MigrationTargetHandler

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more