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 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 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}