Skip to main content

hataori_runtime/object/
migration.rs

1use super::*;
2
3pub(super) const MIGRATION_SCHEMA_ID: u64 = 0x484d_4947;
4pub(crate) const MIGRATION_HEADER_BYTES: usize = 133;
5pub(crate) const MIGRATION_FREEZE_ACTION_ID: u128 = 0xc4ea_7e02_0000_0000_0000_0000_0000_0001;
6pub(crate) const MIGRATION_PREPARE_ACTION_ID: u128 = 0xc4ea_7e02_0000_0000_0000_0000_0000_0002;
7pub(crate) const MIGRATION_COMMIT_ACTION_ID: u128 = 0xc4ea_7e02_0000_0000_0000_0000_0000_0003;
8pub(crate) const MIGRATION_ACTIVATE_ACTION_ID: u128 = 0xc4ea_7e02_0000_0000_0000_0000_0000_0004;
9pub(crate) const MIGRATION_ROLLBACK_ACTION_ID: u128 = 0xc4ea_7e02_0000_0000_0000_0000_0000_0005;
10pub(crate) const MIGRATION_RETIRE_ACTION_ID: u128 = 0xc4ea_7e02_0000_0000_0000_0000_0000_0006;
11
12#[derive(Clone, Debug)]
13pub(crate) struct MigrationPacket {
14    pub(crate) migration: RequestId,
15    pub(crate) object: ObjectId,
16    pub(crate) object_type: ObjectTypeId,
17    pub(crate) authority: LocalityId,
18    pub(crate) from: ObjectLocation,
19    pub(crate) destination: Place,
20    pub(crate) snapshot_version: u32,
21    pub(crate) snapshot_schema: u64,
22    pub(crate) snapshot: Segments,
23}
24
25impl WireValue for MigrationPacket {
26    const SCHEMA_ID: u64 = MIGRATION_SCHEMA_ID;
27
28    fn encode(mut self) -> Result<Segments, ActionError> {
29        let mut header = Vec::with_capacity(MIGRATION_HEADER_BYTES);
30        header.extend_from_slice(b"HMIG");
31        header.push(1);
32        header.extend_from_slice(&self.migration.origin.get().to_le_bytes());
33        header.extend_from_slice(&self.migration.sequence.to_le_bytes());
34        header.extend_from_slice(&self.object.run().get().to_le_bytes());
35        header.extend_from_slice(&self.object.unique().to_le_bytes());
36        header.extend_from_slice(&self.object_type.get().to_le_bytes());
37        header.extend_from_slice(&self.authority.get().to_le_bytes());
38        header.extend_from_slice(&self.from.locality().get().to_le_bytes());
39        header.extend_from_slice(&self.from.domain().get().to_le_bytes());
40        header.extend_from_slice(&self.from.slot().to_le_bytes());
41        header.extend_from_slice(&self.from.generation().to_le_bytes());
42        header.extend_from_slice(&self.from.epoch().to_le_bytes());
43        header.extend_from_slice(&self.destination.locality.get().to_le_bytes());
44        header.extend_from_slice(&self.destination.domain.get().to_le_bytes());
45        header.extend_from_slice(&self.snapshot_version.to_le_bytes());
46        header.extend_from_slice(&self.snapshot_schema.to_le_bytes());
47        self.snapshot.insert(0, header);
48        Ok(self.snapshot)
49    }
50
51    fn decode(mut segments: Segments) -> Result<Self, ActionError> {
52        if segments.is_empty() || segments[0].len() != MIGRATION_HEADER_BYTES {
53            return Err(ActionError::codec("invalid migration header length"));
54        }
55        let header = segments.remove(0);
56        if &header[0..4] != b"HMIG" || header[4] != 1 {
57            return Err(ActionError::codec("invalid migration header"));
58        }
59        let migration = RequestId::new(
60            LocalityId::new(u64::from_le_bytes(header[5..13].try_into().unwrap())),
61            u64::from_le_bytes(header[13..21].try_into().unwrap()),
62        )
63        .map_err(|_| ActionError::codec("invalid migration id"))?;
64        let run = RunId::new(u128::from_le_bytes(header[21..37].try_into().unwrap()))
65            .map_err(|_| ActionError::codec("invalid migration run"))?;
66        let object = ObjectId::new(run, u128::from_le_bytes(header[37..53].try_into().unwrap()))
67            .map_err(|_| ActionError::codec("invalid migration object"))?;
68        let object_type =
69            ObjectTypeId::new(u128::from_le_bytes(header[53..69].try_into().unwrap()))
70                .map_err(|_| ActionError::codec("invalid migration object type"))?;
71        let authority = LocalityId::new(u64::from_le_bytes(header[69..77].try_into().unwrap()));
72        let from = ObjectLocation::new(
73            LocalityId::new(u64::from_le_bytes(header[77..85].try_into().unwrap())),
74            DomainId::new(u32::from_le_bytes(header[85..89].try_into().unwrap())),
75            u64::from_le_bytes(header[89..97].try_into().unwrap()),
76            u32::from_le_bytes(header[97..101].try_into().unwrap()),
77            u64::from_le_bytes(header[101..109].try_into().unwrap()),
78        )
79        .map_err(|_| ActionError::codec("invalid migration source"))?;
80        let destination = Place::new(
81            LocalityId::new(u64::from_le_bytes(header[109..117].try_into().unwrap())),
82            DomainId::new(u32::from_le_bytes(header[117..121].try_into().unwrap())),
83        );
84        Ok(Self {
85            migration,
86            object,
87            object_type,
88            authority,
89            from,
90            destination,
91            snapshot_version: u32::from_le_bytes(header[121..125].try_into().unwrap()),
92            snapshot_schema: u64::from_le_bytes(header[125..133].try_into().unwrap()),
93            snapshot: segments,
94        })
95    }
96}
97
98enum MigrationState {
99    Ready(Option<Result<MigrationReport, RuntimeError>>),
100    Freeze(RemoteFuture<MigrationPacket>),
101    Prepare(RemoteFuture<MigrationPacket>),
102    Commit(RemoteFuture<MigrationPacket>),
103    Activate(RemoteFuture<MigrationPacket>),
104    Retire(RemoteFuture<MigrationPacket>),
105    RollbackSource {
106        future: RemoteFuture<MigrationPacket>,
107        error: RuntimeError,
108        destination: bool,
109    },
110    RollbackDestination {
111        future: RemoteFuture<MigrationPacket>,
112        error: RuntimeError,
113    },
114    Done,
115}
116
117#[must_use = "a migration future must be driven or dropped"]
118pub struct MigrationFuture<T: MobileObject> {
119    client: Option<RuntimeClient>,
120    service: Option<Arc<ObjectService>>,
121    lease: Option<Arc<LocalLease>>,
122    packet: MigrationPacket,
123    authority: ObjectLocation,
124    original: ObjectLocation,
125    prepared: Option<ObjectLocation>,
126    snapshot_bytes: usize,
127    redirects: usize,
128    committed: bool,
129    state: MigrationState,
130    marker: PhantomData<T>,
131}
132
133impl<T: MobileObject> std::fmt::Debug for MigrationFuture<T> {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        f.debug_struct("MigrationFuture")
136            .field("object", &self.packet.object)
137            .field("migration", &self.packet.migration)
138            .field("committed", &self.committed)
139            .finish_non_exhaustive()
140    }
141}
142
143impl<T: MobileObject> MigrationFuture<T> {
144    pub(crate) fn new(
145        client: RuntimeClient,
146        service: Arc<ObjectService>,
147        lease: Arc<LocalLease>,
148        packet: MigrationPacket,
149        authority: ObjectLocation,
150        future: RemoteFuture<MigrationPacket>,
151    ) -> Self {
152        Self {
153            client: Some(client),
154            service: Some(service),
155            lease: Some(lease),
156            original: packet.from,
157            packet,
158            authority,
159            prepared: None,
160            snapshot_bytes: 0,
161            redirects: 0,
162            committed: false,
163            state: MigrationState::Freeze(future),
164            marker: PhantomData,
165        }
166    }
167
168    pub(crate) fn ready(report: MigrationReport, object_type: ObjectTypeId) -> Self {
169        // INVARIANT: this packet is an inert placeholder for the Ready state;
170        // it is never encoded, registered, or submitted to an ObjectService.
171        let packet = MigrationPacket {
172            migration: RequestId::new(report.from.locality(), 1).unwrap(),
173            object: report.object,
174            object_type,
175            authority: report.from.locality(),
176            from: report.from,
177            destination: Place::new(report.to.locality(), report.to.domain()),
178            snapshot_version: 1,
179            snapshot_schema: 1,
180            snapshot: Vec::new(),
181        };
182        Self {
183            client: None,
184            service: None,
185            lease: None,
186            packet,
187            authority: report.from,
188            original: report.from,
189            prepared: Some(report.to),
190            snapshot_bytes: report.snapshot_bytes,
191            redirects: 0,
192            committed: true,
193            state: MigrationState::Ready(Some(Ok(report))),
194            marker: PhantomData,
195        }
196    }
197
198    fn start_rollback(
199        &mut self,
200        error: RuntimeError,
201        destination: bool,
202    ) -> Result<(), RuntimeError> {
203        let error = self.migration_error(error);
204        let client = self.client.as_ref().expect("active migration has client");
205        let future = client.migration_step(
206            Place::new(self.original.locality(), self.original.domain()),
207            MIGRATION_ROLLBACK_ACTION_ID,
208            self.packet.clone(),
209        )?;
210        self.state = MigrationState::RollbackSource {
211            future,
212            error,
213            destination,
214        };
215        Ok(())
216    }
217
218    fn migration_error(&self, error: RuntimeError) -> RuntimeError {
219        match error {
220            RuntimeError::Migration { .. } => error,
221            error => RuntimeError::Migration {
222                request: self.packet.migration,
223                message: error.to_string(),
224            },
225        }
226    }
227
228    fn finish(&mut self, outcome: MigrationOutcome) {
229        if let Some(service) = self.service.take() {
230            service.finish_migration(self.packet.migration, outcome, self.snapshot_bytes);
231        }
232        self.client = None;
233        self.lease = None;
234    }
235}
236
237impl<T: MobileObject> Future for MigrationFuture<T> {
238    type Output = Result<MigrationReport, RuntimeError>;
239
240    fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
241        loop {
242            match &mut self.state {
243                MigrationState::Ready(result) => {
244                    let result = result.take().unwrap_or_else(|| {
245                        Err(RuntimeError::Protocol(
246                            "migration future polled after completion".into(),
247                        ))
248                    });
249                    self.state = MigrationState::Done;
250                    return Poll::Ready(result);
251                }
252                MigrationState::Freeze(future) => match Pin::new(future).poll(context) {
253                    Poll::Pending => return Poll::Pending,
254                    Poll::Ready(Ok(packet)) => {
255                        let Some(snapshot_bytes) = packet
256                            .snapshot
257                            .iter()
258                            .try_fold(0_usize, |total, segment| total.checked_add(segment.len()))
259                        else {
260                            let error = RuntimeError::SnapshotTooLarge {
261                                bytes: usize::MAX,
262                                limit: self.service.as_ref().unwrap().limits.max_snapshot_bytes,
263                            };
264                            if let Err(rollback) = self.start_rollback(error, false) {
265                                self.finish(MigrationOutcome::Failed);
266                                return Poll::Ready(Err(rollback));
267                            }
268                            continue;
269                        };
270                        self.snapshot_bytes = snapshot_bytes;
271                        self.packet = packet.clone();
272                        let client = self.client.as_ref().unwrap();
273                        match client.migration_step(
274                            self.packet.destination,
275                            MIGRATION_PREPARE_ACTION_ID,
276                            packet,
277                        ) {
278                            Ok(future) => self.state = MigrationState::Prepare(future),
279                            Err(error) => {
280                                if let Err(rollback) = self.start_rollback(error, false) {
281                                    self.finish(MigrationOutcome::Failed);
282                                    return Poll::Ready(Err(rollback));
283                                }
284                            }
285                        }
286                    }
287                    Poll::Ready(Err(RuntimeError::Moved { object, location }))
288                        if object == self.packet.object
289                            && location.epoch() > self.packet.from.epoch()
290                            && self.redirects
291                                < self.service.as_ref().unwrap().limits.max_redirects =>
292                    {
293                        self.redirects += 1;
294                        self.packet.from = location;
295                        self.original = location;
296                        self.service
297                            .as_ref()
298                            .unwrap()
299                            .cache_location(object, location);
300                        match self.client.as_ref().unwrap().migration_step(
301                            Place::new(location.locality(), location.domain()),
302                            MIGRATION_FREEZE_ACTION_ID,
303                            self.packet.clone(),
304                        ) {
305                            Ok(future) => self.state = MigrationState::Freeze(future),
306                            Err(error) => {
307                                self.finish(MigrationOutcome::RolledBack);
308                                return Poll::Ready(Err(error));
309                            }
310                        }
311                    }
312                    Poll::Ready(Err(error)) => {
313                        let error = self.migration_error(error);
314                        self.finish(MigrationOutcome::RolledBack);
315                        self.state = MigrationState::Done;
316                        return Poll::Ready(Err(error));
317                    }
318                },
319                MigrationState::Prepare(future) => match Pin::new(future).poll(context) {
320                    Poll::Pending => return Poll::Pending,
321                    Poll::Ready(Ok(packet)) => {
322                        self.prepared = Some(packet.from);
323                        self.packet = packet.clone();
324                        let place = Place::new(self.authority.locality(), self.authority.domain());
325                        match self.client.as_ref().unwrap().migration_step(
326                            place,
327                            MIGRATION_COMMIT_ACTION_ID,
328                            packet,
329                        ) {
330                            Ok(future) => self.state = MigrationState::Commit(future),
331                            Err(error) => {
332                                if let Err(rollback) = self.start_rollback(error, true) {
333                                    self.finish(MigrationOutcome::Failed);
334                                    return Poll::Ready(Err(rollback));
335                                }
336                            }
337                        }
338                    }
339                    Poll::Ready(Err(error)) => {
340                        if let Err(rollback) = self.start_rollback(error, false) {
341                            self.finish(MigrationOutcome::Failed);
342                            return Poll::Ready(Err(rollback));
343                        }
344                    }
345                },
346                MigrationState::Commit(future) => match Pin::new(future).poll(context) {
347                    Poll::Pending => return Poll::Pending,
348                    Poll::Ready(Ok(packet)) => {
349                        self.committed = true;
350                        self.packet = packet.clone();
351                        match self.client.as_ref().unwrap().migration_step(
352                            self.packet.destination,
353                            MIGRATION_ACTIVATE_ACTION_ID,
354                            packet,
355                        ) {
356                            Ok(future) => self.state = MigrationState::Activate(future),
357                            Err(error) => {
358                                self.client.as_ref().unwrap().fail_committed_migration();
359                                self.finish(MigrationOutcome::Failed);
360                                return Poll::Ready(Err(RuntimeError::MigrationCommittedFailure {
361                                    object: self.packet.object,
362                                    message: error.to_string(),
363                                }));
364                            }
365                        }
366                    }
367                    Poll::Ready(Err(error)) => {
368                        if let Err(rollback) = self.start_rollback(error, true) {
369                            self.finish(MigrationOutcome::Failed);
370                            return Poll::Ready(Err(rollback));
371                        }
372                    }
373                },
374                MigrationState::Activate(future) => match Pin::new(future).poll(context) {
375                    Poll::Pending => return Poll::Pending,
376                    Poll::Ready(Ok(packet)) => {
377                        self.packet = packet.clone();
378                        if self.original.locality() == packet.from.locality() {
379                            let to = self.prepared.expect("commit requires prepared location");
380                            self.service
381                                .as_ref()
382                                .unwrap()
383                                .cache_location(self.packet.object, to);
384                            let report = MigrationReport {
385                                object: self.packet.object,
386                                from: self.original,
387                                to,
388                                snapshot_bytes: self.snapshot_bytes,
389                            };
390                            self.finish(MigrationOutcome::Completed);
391                            self.state = MigrationState::Done;
392                            return Poll::Ready(Ok(report));
393                        }
394                        let source = Place::new(self.original.locality(), self.original.domain());
395                        match self.client.as_ref().unwrap().migration_step(
396                            source,
397                            MIGRATION_RETIRE_ACTION_ID,
398                            packet,
399                        ) {
400                            Ok(future) => self.state = MigrationState::Retire(future),
401                            Err(error) => {
402                                self.client.as_ref().unwrap().fail_committed_migration();
403                                self.finish(MigrationOutcome::Failed);
404                                return Poll::Ready(Err(RuntimeError::MigrationCommittedFailure {
405                                    object: self.packet.object,
406                                    message: error.to_string(),
407                                }));
408                            }
409                        }
410                    }
411                    Poll::Ready(Err(error)) => {
412                        self.client.as_ref().unwrap().fail_committed_migration();
413                        self.finish(MigrationOutcome::Failed);
414                        return Poll::Ready(Err(RuntimeError::MigrationCommittedFailure {
415                            object: self.packet.object,
416                            message: error.to_string(),
417                        }));
418                    }
419                },
420                MigrationState::Retire(future) => match Pin::new(future).poll(context) {
421                    Poll::Pending => return Poll::Pending,
422                    Poll::Ready(Ok(_)) => {
423                        let to = self.prepared.expect("commit requires prepared location");
424                        self.service
425                            .as_ref()
426                            .unwrap()
427                            .cache_location(self.packet.object, to);
428                        let report = MigrationReport {
429                            object: self.packet.object,
430                            from: self.original,
431                            to,
432                            snapshot_bytes: self.snapshot_bytes,
433                        };
434                        self.finish(MigrationOutcome::Completed);
435                        self.state = MigrationState::Done;
436                        return Poll::Ready(Ok(report));
437                    }
438                    Poll::Ready(Err(error)) => {
439                        self.client.as_ref().unwrap().fail_committed_migration();
440                        self.finish(MigrationOutcome::Failed);
441                        return Poll::Ready(Err(RuntimeError::MigrationCommittedFailure {
442                            object: self.packet.object,
443                            message: error.to_string(),
444                        }));
445                    }
446                },
447                MigrationState::RollbackSource {
448                    future,
449                    error,
450                    destination,
451                } => match Pin::new(future).poll(context) {
452                    Poll::Pending => return Poll::Pending,
453                    Poll::Ready(Err(rollback)) => {
454                        let message = format!("{}; source rollback failed: {rollback}", error);
455                        self.finish(MigrationOutcome::Failed);
456                        return Poll::Ready(Err(RuntimeError::Migration {
457                            request: self.packet.migration,
458                            message,
459                        }));
460                    }
461                    Poll::Ready(Ok(_)) if *destination => {
462                        let error = error.clone();
463                        match self.client.as_ref().unwrap().migration_step(
464                            self.packet.destination,
465                            MIGRATION_ROLLBACK_ACTION_ID,
466                            self.packet.clone(),
467                        ) {
468                            Ok(future) => {
469                                self.state = MigrationState::RollbackDestination { future, error }
470                            }
471                            Err(_) => {
472                                self.finish(MigrationOutcome::Failed);
473                                return Poll::Ready(Err(error));
474                            }
475                        }
476                    }
477                    Poll::Ready(Ok(_)) => {
478                        let error = error.clone();
479                        self.finish(MigrationOutcome::RolledBack);
480                        self.state = MigrationState::Done;
481                        return Poll::Ready(Err(error));
482                    }
483                },
484                MigrationState::RollbackDestination { future, error } => {
485                    match Pin::new(future).poll(context) {
486                        Poll::Pending => return Poll::Pending,
487                        Poll::Ready(Err(rollback)) => {
488                            let message =
489                                format!("{}; destination rollback failed: {rollback}", error);
490                            self.finish(MigrationOutcome::Failed);
491                            return Poll::Ready(Err(RuntimeError::Migration {
492                                request: self.packet.migration,
493                                message,
494                            }));
495                        }
496                        Poll::Ready(Ok(_)) => {
497                            let error = error.clone();
498                            self.finish(MigrationOutcome::RolledBack);
499                            self.state = MigrationState::Done;
500                            return Poll::Ready(Err(error));
501                        }
502                    }
503                }
504                MigrationState::Done => {
505                    return Poll::Ready(Err(RuntimeError::Protocol(
506                        "migration future polled after completion".into(),
507                    )));
508                }
509            }
510        }
511    }
512}
513
514impl<T: MobileObject> Unpin for MigrationFuture<T> {}
515
516impl<T: MobileObject> Drop for MigrationFuture<T> {
517    fn drop(&mut self) {
518        if self.service.is_some() {
519            if self.committed {
520                if let Some(client) = &self.client {
521                    client.fail_committed_migration();
522                }
523            }
524            self.finish(if self.committed {
525                MigrationOutcome::Failed
526            } else {
527                MigrationOutcome::RolledBack
528            });
529        }
530    }
531}
532
533struct PreparedPermit<'a> {
534    count: &'a AtomicU64,
535    committed: bool,
536}
537
538impl PreparedPermit<'_> {
539    fn commit(mut self) {
540        self.committed = true;
541    }
542}
543
544impl Drop for PreparedPermit<'_> {
545    fn drop(&mut self) {
546        if !self.committed {
547            self.count.fetch_sub(1, Ordering::AcqRel);
548        }
549    }
550}
551
552impl ObjectService {
553    pub(super) fn migration_freeze_handler(self: &Arc<Self>) -> RegisteredAction {
554        let service = Arc::clone(self);
555        RegisteredAction::new(move |segments| {
556            service
557                .freeze_migration(MigrationPacket::decode(segments)?)?
558                .encode()
559        })
560    }
561
562    pub(super) fn migration_prepare_handler(self: &Arc<Self>) -> RegisteredAction {
563        let service = Arc::clone(self);
564        RegisteredAction::new(move |segments| {
565            service
566                .prepare_migration(MigrationPacket::decode(segments)?)?
567                .encode()
568        })
569    }
570
571    pub(super) fn migration_commit_handler(self: &Arc<Self>) -> RegisteredAction {
572        let service = Arc::clone(self);
573        RegisteredAction::new(move |segments| {
574            service
575                .commit_migration(MigrationPacket::decode(segments)?)?
576                .encode()
577        })
578    }
579
580    pub(super) fn migration_activate_handler(self: &Arc<Self>) -> RegisteredAction {
581        let service = Arc::clone(self);
582        RegisteredAction::new(move |segments| {
583            service
584                .activate_migration(MigrationPacket::decode(segments)?)?
585                .encode()
586        })
587    }
588
589    pub(super) fn migration_rollback_handler(self: &Arc<Self>) -> RegisteredAction {
590        let service = Arc::clone(self);
591        RegisteredAction::new(move |segments| {
592            service
593                .rollback_migration(MigrationPacket::decode(segments)?)?
594                .encode()
595        })
596    }
597
598    pub(super) fn migration_retire_handler(self: &Arc<Self>) -> RegisteredAction {
599        let service = Arc::clone(self);
600        RegisteredAction::new(move |segments| {
601            service
602                .retire_migration(MigrationPacket::decode(segments)?)?
603                .encode()
604        })
605    }
606
607    pub(crate) fn migration_metadata(
608        &self,
609        object_type: ObjectTypeId,
610    ) -> Result<(Mobility, u32, u64), RuntimeError> {
611        let registration = self
612            .registry
613            .types
614            .get(&object_type)
615            .ok_or(RuntimeError::UnknownObjectType(object_type))?;
616        Ok((
617            registration.mobility,
618            registration.snapshot_version,
619            registration.snapshot_schema,
620        ))
621    }
622
623    pub(crate) fn begin_migration(&self, migration: RequestId) -> Result<(), RuntimeError> {
624        let mut active = self.active_migrations.lock().unwrap();
625        if active.len() >= self.limits.max_migrations {
626            return Err(RuntimeError::ResourceExhausted {
627                resource: crate::ResourceKind::Migrations,
628                limit: self.limits.max_migrations,
629            });
630        }
631        if !active.insert(migration) {
632            return Err(RuntimeError::Protocol("duplicate migration id".into()));
633        }
634        Ok(())
635    }
636
637    pub(crate) fn finish_migration(
638        &self,
639        migration: RequestId,
640        outcome: MigrationOutcome,
641        bytes: usize,
642    ) {
643        self.active_migrations.lock().unwrap().remove(&migration);
644        match outcome {
645            MigrationOutcome::Completed => {
646                self.completed_migrations.fetch_add(1, Ordering::Relaxed);
647                self.migrated_bytes
648                    .fetch_add(bytes as u64, Ordering::Relaxed);
649            }
650            MigrationOutcome::RolledBack => {
651                self.rolled_back_migrations.fetch_add(1, Ordering::Relaxed);
652            }
653            MigrationOutcome::Failed => {
654                self.failed_migrations.fetch_add(1, Ordering::Relaxed);
655            }
656        }
657    }
658
659    fn freeze_migration(
660        &self,
661        mut packet: MigrationPacket,
662    ) -> Result<MigrationPacket, ActionError> {
663        let entry = self.entry_for_migration(&packet)?;
664        if entry.location() != packet.from || entry.authority != packet.authority {
665            return Err(ActionError::user("migration source changed"));
666        }
667        let registration = self
668            .registry
669            .types
670            .get(&packet.object_type)
671            .ok_or_else(|| ActionError::user("unknown migration object type"))?;
672        let freezer = registration
673            .freezer
674            .as_ref()
675            .ok_or_else(|| ActionError::user("object is pinned"))?;
676        if packet.snapshot_version != registration.snapshot_version
677            || packet.snapshot_schema != registration.snapshot_schema
678        {
679            return Err(ActionError::user("migration snapshot schema mismatch"));
680        }
681        {
682            let mut lifecycle = entry.lifecycle.lock().unwrap();
683            match *lifecycle {
684                ObjectLifecycle::Resident => {
685                    *lifecycle = ObjectLifecycle::Freezing {
686                        migration: packet.migration,
687                        expires_at: Instant::now() + self.limits.forwarding_ttl,
688                    }
689                }
690                ObjectLifecycle::Freezing { migration, .. } if migration == packet.migration => {}
691                _ => return Err(ActionError::user("migration already in progress")),
692            }
693        }
694        let state = entry.resident_state()?;
695        let snapshot = match catch_unwind(AssertUnwindSafe(|| freezer(&state))) {
696            Ok(Ok(snapshot)) => snapshot,
697            Ok(Err(error)) => {
698                *entry.lifecycle.lock().unwrap() = ObjectLifecycle::Resident;
699                return Err(error);
700            }
701            Err(_) => {
702                *entry.lifecycle.lock().unwrap() = ObjectLifecycle::Resident;
703                return Err(ActionError::Panic);
704            }
705        };
706        let bytes = match self.validate_snapshot(&snapshot) {
707            Ok(bytes) => bytes,
708            Err(error) => {
709                *entry.lifecycle.lock().unwrap() = ObjectLifecycle::Resident;
710                return Err(error);
711            }
712        };
713        *entry.snapshot_bytes.lock().unwrap() = bytes;
714        packet.snapshot = snapshot;
715        Ok(packet)
716    }
717
718    fn prepare_migration(
719        &self,
720        mut packet: MigrationPacket,
721    ) -> Result<MigrationPacket, ActionError> {
722        if packet.destination.locality != self.local_id {
723            return Err(ActionError::user("migration prepared at wrong locality"));
724        }
725        let bytes = self.validate_snapshot(&packet.snapshot)?;
726        let registration = self
727            .registry
728            .types
729            .get(&packet.object_type)
730            .ok_or_else(|| ActionError::user("unknown migration object type"))?;
731        if registration.mobility == Mobility::Pinned
732            || packet.snapshot_version != registration.snapshot_version
733            || packet.snapshot_schema != registration.snapshot_schema
734        {
735            return Err(ActionError::user("migration snapshot schema mismatch"));
736        }
737        self.prepared_count
738            .fetch_update(Ordering::AcqRel, Ordering::Acquire, |count| {
739                (count < self.limits.max_prepared_migrations as u64).then_some(count + 1)
740            })
741            .map_err(|_| ActionError::user("prepared migration limit reached"))?;
742        let permit = PreparedPermit {
743            count: &self.prepared_count,
744            committed: false,
745        };
746        {
747            let entries = self.entries.lock().unwrap();
748            if entries.len() >= self.limits.max_objects && !entries.contains_key(&packet.object) {
749                return Err(ActionError::user("object store is full"));
750            }
751        }
752        let epoch = packet
753            .from
754            .epoch()
755            .checked_add(1)
756            .ok_or_else(|| ActionError::user("object epoch overflow"))?;
757        let mut slot = self.next_slot.lock().unwrap();
758        let location =
759            ObjectLocation::new(self.local_id, packet.destination.domain, *slot, 1, epoch)
760                .map_err(|_| ActionError::user("invalid migration destination"))?;
761        *slot = slot
762            .checked_add(1)
763            .ok_or_else(|| ActionError::user("object slot overflow"))?;
764        drop(slot);
765        let restore = registration
766            .restorer
767            .as_ref()
768            .ok_or_else(|| ActionError::user("object is pinned"))?;
769        let snapshot = std::mem::take(&mut packet.snapshot);
770        let restored = Arc::new(restore(
771            snapshot,
772            RestoreContext {
773                destination: packet.destination,
774            },
775        )?);
776        let mut entries = self.entries.lock().unwrap();
777        if entries.len() >= self.limits.max_objects && !entries.contains_key(&packet.object) {
778            return Err(ActionError::user("object store is full"));
779        }
780        let entry = entries.entry(packet.object).or_insert_with(|| {
781            Arc::new(ObjectEntry {
782                object_type: packet.object_type,
783                authority: packet.authority,
784                location: Mutex::new(packet.from),
785                state: Mutex::new(None),
786                lifecycle: Mutex::new(ObjectLifecycle::Prepared {
787                    migration: packet.migration,
788                    expires_at: Instant::now() + self.limits.forwarding_ttl,
789                }),
790                prepared: Mutex::new(None),
791                snapshot_bytes: Mutex::new(0),
792                leases: Mutex::new(HashMap::new()),
793                roots: Mutex::new(0),
794                in_flight: Mutex::new(0),
795                placement_tickets: Mutex::new(0),
796                admission: Mutex::new(Admission {
797                    readers: 0,
798                    writer: false,
799                    queue: VecDeque::new(),
800                }),
801            })
802        });
803        if entry.object_type != packet.object_type || entry.authority != packet.authority {
804            return Err(ActionError::user("migration destination conflict"));
805        }
806        let mut prepared = entry.prepared.lock().unwrap();
807        if let Some(existing) = prepared.as_ref() {
808            if existing.migration != packet.migration || existing.location != location {
809                return Err(ActionError::user("conflicting prepared migration"));
810            }
811        } else {
812            *prepared = Some(PreparedState {
813                migration: packet.migration,
814                location,
815                state: restored,
816                snapshot_bytes: bytes,
817                expires_at: Instant::now() + self.limits.forwarding_ttl,
818            });
819            permit.commit();
820        }
821        packet.from = location;
822        Ok(packet)
823    }
824
825    fn commit_migration(&self, packet: MigrationPacket) -> Result<MigrationPacket, ActionError> {
826        if packet.authority != self.local_id {
827            return Err(ActionError::user("migration committed at wrong authority"));
828        }
829        let entry = self.entry_for_migration(&packet)?;
830        // INVARIANT: the scan is capped by ObjectLimits::max_objects.
831        let forwarders = self
832            .entries
833            .lock()
834            .unwrap()
835            .values()
836            .filter(|entry| {
837                matches!(
838                    *entry.lifecycle.lock().unwrap(),
839                    ObjectLifecycle::Forwarding { .. } | ObjectLifecycle::Retiring { .. }
840                )
841            })
842            .count();
843        if !matches!(
844            *entry.lifecycle.lock().unwrap(),
845            ObjectLifecycle::Forwarding { .. } | ObjectLifecycle::Retiring { .. }
846        ) && forwarders >= self.limits.max_forwarders
847        {
848            return Err(ActionError::user("forwarding entry limit reached"));
849        }
850        let old = entry.location();
851        if old.epoch().checked_add(1) != Some(packet.from.epoch()) {
852            return Err(ActionError::user("migration epoch conflict"));
853        }
854        *entry.location.lock().unwrap() = packet.from;
855        *entry.lifecycle.lock().unwrap() = ObjectLifecycle::Forwarding {
856            location: packet.from,
857            expires_at: Instant::now() + self.limits.forwarding_ttl,
858        };
859        self.cache_location(packet.object, packet.from);
860        Ok(packet)
861    }
862
863    fn activate_migration(&self, packet: MigrationPacket) -> Result<MigrationPacket, ActionError> {
864        let entry = self.entry_for_migration(&packet)?;
865        let mut lifecycle = entry.lifecycle.lock().unwrap();
866        let prepared = entry
867            .prepared
868            .lock()
869            .unwrap()
870            .take()
871            .ok_or_else(|| ActionError::user("migration destination was not prepared"))?;
872        self.prepared_count.fetch_sub(1, Ordering::AcqRel);
873        if prepared.migration != packet.migration || prepared.location != packet.from {
874            return Err(ActionError::user("prepared migration mismatch"));
875        }
876        *entry.location.lock().unwrap() = prepared.location;
877        *entry.state.lock().unwrap() = Some(prepared.state);
878        *entry.snapshot_bytes.lock().unwrap() = prepared.snapshot_bytes;
879        *lifecycle = ObjectLifecycle::Resident;
880        drop(lifecycle);
881        self.cache_location(packet.object, prepared.location);
882        Ok(packet)
883    }
884
885    fn rollback_migration(&self, packet: MigrationPacket) -> Result<MigrationPacket, ActionError> {
886        let entry = self.entries.lock().unwrap().get(&packet.object).cloned();
887        if let Some(entry) = entry {
888            let mut prepared = entry.prepared.lock().unwrap();
889            if prepared
890                .as_ref()
891                .is_some_and(|state| state.migration == packet.migration)
892            {
893                *prepared = None;
894                self.prepared_count.fetch_sub(1, Ordering::AcqRel);
895            }
896            drop(prepared);
897            let mut lifecycle = entry.lifecycle.lock().unwrap();
898            if matches!(
899                *lifecycle,
900                ObjectLifecycle::Freezing { migration, .. } if migration == packet.migration
901            ) {
902                *lifecycle = ObjectLifecycle::Resident;
903                *entry.snapshot_bytes.lock().unwrap() = 0;
904            }
905        }
906        Ok(packet)
907    }
908
909    fn retire_migration(&self, packet: MigrationPacket) -> Result<MigrationPacket, ActionError> {
910        let entry = self.entry_for_migration(&packet)?;
911        if entry.location().epoch() > packet.from.epoch() {
912            return Ok(packet);
913        }
914        *entry.state.lock().unwrap() = None;
915        *entry.snapshot_bytes.lock().unwrap() = 0;
916        *entry.location.lock().unwrap() = packet.from;
917        *entry.lifecycle.lock().unwrap() = ObjectLifecycle::Forwarding {
918            location: packet.from,
919            expires_at: Instant::now() + self.limits.forwarding_ttl,
920        };
921        self.cache_location(packet.object, packet.from);
922        self.collect(packet.object, Instant::now());
923        Ok(packet)
924    }
925
926    fn entry_for_migration(
927        &self,
928        packet: &MigrationPacket,
929    ) -> Result<Arc<ObjectEntry>, ActionError> {
930        let entry = self
931            .entries
932            .lock()
933            .unwrap()
934            .get(&packet.object)
935            .cloned()
936            .ok_or_else(|| ActionError::user("migration object not found"))?;
937        if entry.object_type != packet.object_type {
938            return Err(ActionError::user("migration object type mismatch"));
939        }
940        Ok(entry)
941    }
942
943    pub(super) fn validate_snapshot(&self, snapshot: &Segments) -> Result<usize, ActionError> {
944        if snapshot.len() > self.limits.max_snapshot_segments {
945            return Err(ActionError::user(
946                "migration snapshot segment limit exceeded",
947            ));
948        }
949        let bytes = snapshot
950            .iter()
951            .try_fold(0_usize, |total, segment| total.checked_add(segment.len()))
952            .ok_or_else(|| ActionError::user("migration snapshot size overflow"))?;
953        if bytes > self.limits.max_snapshot_bytes {
954            return Err(ActionError::user("migration snapshot byte limit exceeded"));
955        }
956        Ok(bytes)
957    }
958}