pub struct MigrationTargetHandler { /* private fields */ }Expand description
Handles the target node’s role in daemon migration.
The target handler:
- Restores a daemon from a snapshot (phase 2)
- Replays buffered events in strict sequence order (phase 3)
- Activates as the authoritative copy after cutover (phase 4)
Implementations§
Source§impl MigrationTargetHandler
impl MigrationTargetHandler
Sourcepub fn new(daemon_registry: Arc<DaemonRegistry>) -> Self
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.
Sourcepub fn new_with_factories(
daemon_registry: Arc<DaemonRegistry>,
factories: Arc<DaemonFactoryRegistry>,
) -> Self
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.
Sourcepub fn factories(&self) -> &Arc<DaemonFactoryRegistry> ⓘ
pub fn factories(&self) -> &Arc<DaemonFactoryRegistry> ⓘ
Access the factory registry (for the subprotocol handler).
Sourcepub fn host_head_link(
&self,
daemon_origin: u64,
) -> Result<CausalLink, MigrationError>
pub fn host_head_link( &self, daemon_origin: u64, ) -> Result<CausalLink, MigrationError>
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.
Sourcepub fn restore_snapshot<F>(
&self,
ctx: RestoreContext<'_>,
keypair: EntityKeypair,
daemon_factory: F,
config: DaemonHostConfig,
) -> Result<(), MigrationError>
pub fn restore_snapshot<F>( &self, ctx: RestoreContext<'_>, keypair: EntityKeypair, daemon_factory: F, config: DaemonHostConfig, ) -> Result<(), MigrationError>
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.
Sourcepub fn orchestrator_node(&self, daemon_origin: u64) -> Option<u64>
pub fn orchestrator_node(&self, daemon_origin: u64) -> Option<u64>
Recorded orchestrator for an active or recently-completed target-side migration.
Sourcepub fn replay_events(
&self,
daemon_origin: u64,
events: Vec<CausalEvent>,
) -> Result<u64, MigrationError>
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.
Sourcepub fn buffer_event(
&self,
daemon_origin: u64,
event: CausalEvent,
) -> Result<bool, MigrationError>
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.
Sourcepub fn activate(&self, daemon_origin: u64) -> Result<u64, MigrationError>
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.
Sourcepub fn complete(&self, daemon_origin: u64) -> Result<(), MigrationError>
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 callsmigrations.get()and blocks on the shard write lock; once we drop the entry the migration is gone butcompletedalready has the idempotency record, so the retry resolves through thecompletedlookup; - a concurrent
abort(), which would otherwise observe an emptymigrationsafter a remove-first, insert-second ordering anddaemon_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.
Sourcepub fn forget_completed(&self, daemon_origin: u64) -> bool
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.
Sourcepub fn abort(&self, daemon_origin: u64) -> Result<(), MigrationError>
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.
Sourcepub fn is_migrating(&self, daemon_origin: u64) -> bool
pub fn is_migrating(&self, daemon_origin: u64) -> bool
Check if a daemon is being migrated to this node.
Sourcepub fn phase(&self, daemon_origin: u64) -> Option<MigrationPhase>
pub fn phase(&self, daemon_origin: u64) -> Option<MigrationPhase>
Get the current phase of a target-side migration.
Sourcepub fn replayed_through(&self, daemon_origin: u64) -> Option<u64>
pub fn replayed_through(&self, daemon_origin: u64) -> Option<u64>
Get the sequence number replayed through.
Sourcepub fn active_count(&self) -> usize
pub fn active_count(&self) -> usize
Number of active target-side migrations.