1use crate::{
2 action::{Action, ActionRegistry, ActionValue, RegisteredAction, Segments},
3 domain::{ActionJob, ObjectJob},
4 ActionError, Place, RemoteFuture, RuntimeClient, RuntimeError, SpawnOptions, WireValue,
5};
6use hataori_runtime_foundation::protocol::{
7 ActionId, DomainId, LocalityId, ObjectId, ObjectLocation, ObjectTypeId, RequestId, RunId,
8};
9use std::{
10 any::Any,
11 collections::{BTreeMap, HashMap, HashSet, VecDeque},
12 future::Future,
13 marker::PhantomData,
14 panic::{catch_unwind, AssertUnwindSafe},
15 pin::Pin,
16 sync::{
17 atomic::{AtomicBool, AtomicU64, Ordering},
18 Arc, Mutex, RwLock, Weak,
19 },
20 task::{Context, Poll},
21 time::{Duration, Instant},
22};
23
24const OBJECT_MAGIC: [u8; 4] = *b"HOBJ";
25const OBJECT_VERSION: u8 = 1;
26const OBJECT_HEADER_BYTES: usize = 76;
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29#[repr(u8)]
30pub enum ObjectConcurrency {
31 Exclusive,
32 ReadWrite,
33}
34
35pub trait DistributedObject: WireValue {
36 const TYPE_ID: u128;
37}
38
39pub trait ObjectReadAction<T: DistributedObject>: WireValue {
40 const ID: u128;
41 type Output: WireValue;
42 fn execute(self, state: &T) -> Result<Self::Output, ActionError>;
43}
44
45pub trait ObjectWriteAction<T: DistributedObject>: WireValue {
46 const ID: u128;
47 type Output: WireValue;
48 fn execute(self, state: &mut T) -> Result<Self::Output, ActionError>;
49}
50
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
52#[repr(u8)]
53pub enum Mobility {
54 Pinned,
55 Reconstructible,
56 Migratable,
57}
58
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub struct RestoreContext {
61 destination: Place,
62}
63
64impl RestoreContext {
65 pub const fn destination(self) -> Place {
66 self.destination
67 }
68}
69
70pub trait MobileObject: DistributedObject {
72 const MOBILITY: Mobility;
73 const SNAPSHOT_VERSION: u32;
74 type Snapshot: WireValue;
75
76 fn freeze(&mut self) -> Result<Self::Snapshot, ActionError>;
81
82 fn restore(snapshot: Self::Snapshot, context: RestoreContext) -> Result<Self, ActionError>;
88}
89
90#[derive(Clone, Copy, Debug, Eq, PartialEq)]
91#[repr(u8)]
92pub enum ObjectAccess {
93 Read,
94 Write,
95}
96
97#[derive(Clone, Copy, Debug, Eq, PartialEq)]
98pub enum PlacementFallback {
99 Any,
100 Place(Place),
101 Reject,
102}
103
104#[derive(Clone, Copy, Debug, Eq, PartialEq)]
105pub struct ObjectLimits {
106 pub max_objects: usize,
107 pub max_mailbox_per_object: usize,
108 pub max_resolver_entries: usize,
109 pub max_local_leases: usize,
110 pub max_roots: usize,
111 pub max_placement_tickets: usize,
112 pub max_transfers: usize,
113 pub max_migrations: usize,
114 pub max_prepared_migrations: usize,
115 pub max_forwarders: usize,
116 pub max_redirects: usize,
117 pub max_snapshot_bytes: usize,
118 pub max_snapshot_segments: usize,
119 pub forwarding_ttl: Duration,
120 pub lease_renew_interval: Duration,
121 pub lease_ttl: Duration,
122 pub lease_grace: Duration,
123}
124
125impl Default for ObjectLimits {
126 fn default() -> Self {
127 Self {
128 max_objects: 1024,
129 max_mailbox_per_object: 64,
130 max_resolver_entries: 1024,
131 max_local_leases: 1024,
132 max_roots: 1024,
133 max_placement_tickets: 1024,
134 max_transfers: 1024,
135 max_migrations: 64,
136 max_prepared_migrations: 64,
137 max_forwarders: 1024,
138 max_redirects: 8,
139 max_snapshot_bytes: 8 * 1024 * 1024 - MIGRATION_HEADER_BYTES,
140 max_snapshot_segments: 1023,
141 forwarding_ttl: Duration::from_secs(60),
142 lease_renew_interval: Duration::from_secs(10),
143 lease_ttl: Duration::from_secs(30),
144 lease_grace: Duration::from_secs(30),
145 }
146 }
147}
148
149impl ObjectLimits {
150 pub fn validate(self) -> Result<Self, RuntimeError> {
151 if self.max_objects == 0
152 || self.max_mailbox_per_object == 0
153 || self.max_resolver_entries == 0
154 || self.max_local_leases == 0
155 || self.max_roots == 0
156 || self.max_placement_tickets == 0
157 || self.max_transfers == 0
158 || self.max_migrations == 0
159 || self.max_prepared_migrations == 0
160 || self.max_forwarders == 0
161 || self.max_redirects == 0
162 || self.max_snapshot_bytes == 0
163 || self.max_snapshot_segments == 0
164 || self.forwarding_ttl.is_zero()
165 || self.lease_renew_interval.is_zero()
166 || self.lease_ttl <= self.lease_renew_interval
167 || self.lease_grace.is_zero()
168 {
169 return Err(RuntimeError::InvalidLimits("invalid object limits"));
170 }
171 Ok(self)
172 }
173}
174
175#[derive(Clone, Copy, Debug, Eq, PartialEq)]
176pub struct MigrationReport {
177 pub object: ObjectId,
178 pub from: ObjectLocation,
179 pub to: ObjectLocation,
180 pub snapshot_bytes: usize,
181}
182
183#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
184pub struct ObjectStats {
185 pub live_objects: usize,
186 pub leases: usize,
187 pub suspect_leases: usize,
188 pub roots: usize,
189 pub queued_calls: usize,
190 pub in_flight_calls: usize,
191 pub placement_tickets: usize,
192 pub resolver_entries: usize,
193 pub resolver_bytes: usize,
194 pub transfers: usize,
195 pub active_migrations: usize,
196 pub prepared_migrations: usize,
197 pub forwarding_entries: usize,
198 pub migration_snapshot_bytes: usize,
199 pub transferred_snapshot_bytes: u64,
200 pub completed_migrations: u64,
201 pub rolled_back_migrations: u64,
202 pub failed_migrations: u64,
203 pub local_calls: u64,
204 pub remote_calls: u64,
205}
206
207#[derive(Clone, Copy, Debug, Eq, PartialEq)]
208pub(crate) struct ObjectEnvelope {
209 pub(crate) object: ObjectId,
210 pub(crate) object_type: ObjectTypeId,
211 pub(crate) epoch: u64,
212 pub(crate) lease_locality: LocalityId,
213 pub(crate) domain: DomainId,
214 pub(crate) rooted: bool,
215}
216
217impl ObjectEnvelope {
218 pub(crate) fn encode(self, mut payload: Segments) -> Segments {
219 let mut header = Vec::with_capacity(OBJECT_HEADER_BYTES);
220 header.extend_from_slice(&OBJECT_MAGIC);
221 header.push(OBJECT_VERSION);
222 header.push(u8::from(self.rooted));
223 header.extend_from_slice(&[0; 2]);
224 header.extend_from_slice(&self.object.run().get().to_le_bytes());
225 header.extend_from_slice(&self.object.unique().to_le_bytes());
226 header.extend_from_slice(&self.object_type.get().to_le_bytes());
227 header.extend_from_slice(&self.epoch.to_le_bytes());
228 header.extend_from_slice(&self.lease_locality.get().to_le_bytes());
229 header.extend_from_slice(&self.domain.get().to_le_bytes());
230 debug_assert_eq!(header.len(), OBJECT_HEADER_BYTES);
231 let mut result = Vec::with_capacity(payload.len() + 1);
232 result.push(header);
233 result.append(&mut payload);
234 result
235 }
236
237 fn inspect(segments: &Segments) -> Result<Self, ActionError> {
238 let header = segments
239 .first()
240 .ok_or_else(|| ActionError::codec("invalid object envelope length"))?;
241 Self::decode_header(header)
242 }
243
244 fn decode(mut segments: Segments) -> Result<(Self, Segments), ActionError> {
245 if segments.is_empty() || segments[0].len() != OBJECT_HEADER_BYTES {
246 return Err(ActionError::codec("invalid object envelope length"));
247 }
248 let header = segments.remove(0);
249 Ok((Self::decode_header(&header)?, segments))
250 }
251
252 fn decode_header(header: &[u8]) -> Result<Self, ActionError> {
253 if header.len() != OBJECT_HEADER_BYTES {
254 return Err(ActionError::codec("invalid object envelope length"));
255 }
256 if header[..4] != OBJECT_MAGIC || header[4] != OBJECT_VERSION || header[6..8] != [0; 2] {
257 return Err(ActionError::codec("invalid object envelope header"));
258 }
259 let rooted = match header[5] {
260 0 => false,
261 1 => true,
262 _ => return Err(ActionError::codec("invalid object root flag")),
263 };
264 let run = RunId::new(u128::from_le_bytes(header[8..24].try_into().unwrap()))
265 .map_err(|_| ActionError::codec("invalid object run id"))?;
266 let object = ObjectId::new(run, u128::from_le_bytes(header[24..40].try_into().unwrap()))
267 .map_err(|_| ActionError::codec("invalid object id"))?;
268 let object_type =
269 ObjectTypeId::new(u128::from_le_bytes(header[40..56].try_into().unwrap()))
270 .map_err(|_| ActionError::codec("invalid object type"))?;
271 let epoch = u64::from_le_bytes(header[56..64].try_into().unwrap());
272 if epoch == 0 {
273 return Err(ActionError::codec("invalid object epoch"));
274 }
275 Ok(Self {
276 object,
277 object_type,
278 epoch,
279 lease_locality: LocalityId::new(u64::from_le_bytes(header[64..72].try_into().unwrap())),
280 domain: DomainId::new(u32::from_le_bytes(header[72..76].try_into().unwrap())),
281 rooted,
282 })
283 }
284}
285
286mod migration;
287pub use migration::MigrationFuture;
288use migration::MIGRATION_SCHEMA_ID;
289pub(crate) use migration::{
290 MigrationPacket, MIGRATION_ACTIVATE_ACTION_ID, MIGRATION_COMMIT_ACTION_ID,
291 MIGRATION_FREEZE_ACTION_ID, MIGRATION_HEADER_BYTES, MIGRATION_PREPARE_ACTION_ID,
292 MIGRATION_RETIRE_ACTION_ID, MIGRATION_ROLLBACK_ACTION_ID,
293};
294
295type Decoder = dyn Fn(Segments) -> Result<ErasedState, ActionError> + Send + Sync;
296type LocalWrapper = dyn Fn(Box<dyn Any + Send>) -> Result<ErasedState, RuntimeError> + Send + Sync;
297type ObjectHandler = dyn Fn(&ObjectEntry, Segments) -> Result<Segments, ActionError> + Send + Sync;
298type Freezer = dyn Fn(&ErasedState) -> Result<Segments, ActionError> + Send + Sync;
299type Restorer = dyn Fn(Segments, RestoreContext) -> Result<ErasedState, ActionError> + Send + Sync;
300
301#[derive(Clone)]
302struct ObjectActionRegistration {
303 object_type: ObjectTypeId,
304 input_schema: u64,
305 output_schema: u64,
306 access: ObjectAccess,
307 handler: Arc<ObjectHandler>,
308}
309
310#[derive(Clone)]
311struct MigrationRegistration {
312 mobility: Mobility,
313 snapshot_version: u32,
314 snapshot_schema: u64,
315 freezer: Option<Arc<Freezer>>,
316 restorer: Option<Arc<Restorer>>,
317}
318
319#[derive(Clone)]
320struct PlacementActionRegistration {
321 input_schema: u64,
322 output_schema: u64,
323 handler: RegisteredAction,
324}
325
326#[derive(Clone)]
327struct ObjectTypeRegistration {
328 schema: u64,
329 concurrency: ObjectConcurrency,
330 mobility: Mobility,
331 snapshot_version: u32,
332 snapshot_schema: u64,
333 decoder: Arc<Decoder>,
334 local_wrapper: Arc<LocalWrapper>,
335 freezer: Option<Arc<Freezer>>,
336 restorer: Option<Arc<Restorer>>,
337}
338
339#[derive(Clone, Default)]
340pub(crate) struct ObjectRegistry {
341 types: BTreeMap<ObjectTypeId, ObjectTypeRegistration>,
342 actions: BTreeMap<ActionId, ObjectActionRegistration>,
343 placements: BTreeMap<ActionId, PlacementActionRegistration>,
344}
345
346impl std::fmt::Debug for ObjectRegistry {
347 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348 f.debug_struct("ObjectRegistry")
349 .field("types", &self.types.len())
350 .field("actions", &self.actions.len())
351 .field("placements", &self.placements.len())
352 .finish()
353 }
354}
355
356impl ObjectRegistry {
357 pub(crate) fn register_exclusive<T: DistributedObject>(&mut self) -> Result<(), RuntimeError> {
358 self.register_type::<T>(
359 ObjectConcurrency::Exclusive,
360 MigrationRegistration {
361 mobility: Mobility::Pinned,
362 snapshot_version: 0,
363 snapshot_schema: 0,
364 freezer: None,
365 restorer: None,
366 },
367 |segments| {
368 Ok(ErasedState::Exclusive(Mutex::new(Box::new(T::decode(
369 segments,
370 )?))))
371 },
372 |state| {
373 let state = state.downcast::<T>().map_err(|_| {
374 RuntimeError::UnknownObjectType(ObjectTypeId::new(T::TYPE_ID).unwrap())
375 })?;
376 Ok(ErasedState::Exclusive(Mutex::new(state)))
377 },
378 )
379 }
380
381 pub(crate) fn register_read_write<T: DistributedObject + Sync>(
382 &mut self,
383 ) -> Result<(), RuntimeError> {
384 self.register_type::<T>(
385 ObjectConcurrency::ReadWrite,
386 MigrationRegistration {
387 mobility: Mobility::Pinned,
388 snapshot_version: 0,
389 snapshot_schema: 0,
390 freezer: None,
391 restorer: None,
392 },
393 |segments| {
394 Ok(ErasedState::ReadWrite(RwLock::new(Box::new(T::decode(
395 segments,
396 )?))))
397 },
398 |state| {
399 let state = state.downcast::<T>().map_err(|_| {
400 RuntimeError::UnknownObjectType(ObjectTypeId::new(T::TYPE_ID).unwrap())
401 })?;
402 let state: Box<dyn Any + Send + Sync> = state;
403 Ok(ErasedState::ReadWrite(RwLock::new(state)))
404 },
405 )
406 }
407
408 pub(crate) fn register_mobile_exclusive<T: MobileObject>(
409 &mut self,
410 ) -> Result<(), RuntimeError> {
411 validate_mobile::<T>()?;
412 self.register_type::<T>(
413 ObjectConcurrency::Exclusive,
414 MigrationRegistration {
415 mobility: T::MOBILITY,
416 snapshot_version: T::SNAPSHOT_VERSION,
417 snapshot_schema: T::Snapshot::SCHEMA_ID,
418 freezer: Some(Arc::new(|state| {
419 let ErasedState::Exclusive(state) = state else {
420 return Err(ActionError::user("object concurrency mismatch"));
421 };
422 let mut state = state
423 .try_lock()
424 .map_err(|_| ActionError::user("object admission saturated"))?;
425 let typed = state
426 .downcast_mut::<T>()
427 .ok_or_else(|| ActionError::user("object type mismatch"))?;
428 typed.freeze()?.encode()
429 })),
430 restorer: Some(Arc::new(|segments, context| {
431 let state = T::restore(T::Snapshot::decode(segments)?, context)?;
432 Ok(ErasedState::Exclusive(Mutex::new(Box::new(state))))
433 })),
434 },
435 |segments| {
436 Ok(ErasedState::Exclusive(Mutex::new(Box::new(T::decode(
437 segments,
438 )?))))
439 },
440 |state| {
441 let state = state.downcast::<T>().map_err(|_| {
442 RuntimeError::UnknownObjectType(ObjectTypeId::new(T::TYPE_ID).unwrap())
443 })?;
444 Ok(ErasedState::Exclusive(Mutex::new(state)))
445 },
446 )
447 }
448
449 pub(crate) fn register_mobile_read_write<T: MobileObject + Sync>(
450 &mut self,
451 ) -> Result<(), RuntimeError> {
452 validate_mobile::<T>()?;
453 self.register_type::<T>(
454 ObjectConcurrency::ReadWrite,
455 MigrationRegistration {
456 mobility: T::MOBILITY,
457 snapshot_version: T::SNAPSHOT_VERSION,
458 snapshot_schema: T::Snapshot::SCHEMA_ID,
459 freezer: Some(Arc::new(|state| {
460 let ErasedState::ReadWrite(state) = state else {
461 return Err(ActionError::user("object concurrency mismatch"));
462 };
463 let mut state = state
464 .try_write()
465 .map_err(|_| ActionError::user("object admission saturated"))?;
466 let typed = state
467 .downcast_mut::<T>()
468 .ok_or_else(|| ActionError::user("object type mismatch"))?;
469 typed.freeze()?.encode()
470 })),
471 restorer: Some(Arc::new(|segments, context| {
472 let state = T::restore(T::Snapshot::decode(segments)?, context)?;
473 let state: Box<dyn Any + Send + Sync> = Box::new(state);
474 Ok(ErasedState::ReadWrite(RwLock::new(state)))
475 })),
476 },
477 |segments| {
478 Ok(ErasedState::ReadWrite(RwLock::new(Box::new(T::decode(
479 segments,
480 )?))))
481 },
482 |state| {
483 let state = state.downcast::<T>().map_err(|_| {
484 RuntimeError::UnknownObjectType(ObjectTypeId::new(T::TYPE_ID).unwrap())
485 })?;
486 let state: Box<dyn Any + Send + Sync> = state;
487 Ok(ErasedState::ReadWrite(RwLock::new(state)))
488 },
489 )
490 }
491
492 fn register_type<T: DistributedObject>(
493 &mut self,
494 concurrency: ObjectConcurrency,
495 migration: MigrationRegistration,
496 decoder: impl Fn(Segments) -> Result<ErasedState, ActionError> + Send + Sync + 'static,
497 local_wrapper: impl Fn(Box<dyn Any + Send>) -> Result<ErasedState, RuntimeError>
498 + Send
499 + Sync
500 + 'static,
501 ) -> Result<(), RuntimeError> {
502 let id = ObjectTypeId::new(T::TYPE_ID).map_err(|_| RuntimeError::InvalidObjectTypeId)?;
503 if T::SCHEMA_ID == 0 {
504 return Err(RuntimeError::InvalidSchema);
505 }
506 if self.types.contains_key(&id) {
507 return Err(RuntimeError::DuplicateObjectType(id));
508 }
509 let create = create_action_id(id)?;
510 if self.reserves_action(create) {
511 return Err(RuntimeError::DuplicateAction(create));
512 }
513 self.types.insert(
514 id,
515 ObjectTypeRegistration {
516 schema: T::SCHEMA_ID,
517 concurrency,
518 mobility: migration.mobility,
519 snapshot_version: migration.snapshot_version,
520 snapshot_schema: migration.snapshot_schema,
521 decoder: Arc::new(decoder),
522 local_wrapper: Arc::new(local_wrapper),
523 freezer: migration.freezer,
524 restorer: migration.restorer,
525 },
526 );
527 Ok(())
528 }
529
530 pub(crate) fn register_read<T, A>(&mut self) -> Result<(), RuntimeError>
531 where
532 T: DistributedObject + Sync,
533 A: ObjectReadAction<T>,
534 {
535 let object_type = self.require_type::<T>(ObjectConcurrency::ReadWrite)?;
536 self.insert_action(
537 A::ID,
538 A::SCHEMA_ID,
539 A::Output::SCHEMA_ID,
540 object_type,
541 ObjectAccess::Read,
542 |entry, input| {
543 let action = A::decode(input)?;
544 let erased = entry.resident_state()?;
545 let ErasedState::ReadWrite(state) = &*erased else {
546 return Err(ActionError::user("object does not admit reads"));
547 };
548 let state = state
549 .try_read()
550 .map_err(|_| ActionError::user("object admission saturated"))?;
551 let typed = state
552 .downcast_ref::<T>()
553 .ok_or_else(|| ActionError::user("object type mismatch"))?;
554 action.execute(typed)?.encode()
555 },
556 )
557 }
558
559 pub(crate) fn register_write<T, A>(&mut self) -> Result<(), RuntimeError>
560 where
561 T: DistributedObject,
562 A: ObjectWriteAction<T>,
563 {
564 let object_type =
565 ObjectTypeId::new(T::TYPE_ID).map_err(|_| RuntimeError::InvalidObjectTypeId)?;
566 if !self.types.contains_key(&object_type) {
567 return Err(RuntimeError::UnknownObjectType(object_type));
568 }
569 self.insert_action(
570 A::ID,
571 A::SCHEMA_ID,
572 A::Output::SCHEMA_ID,
573 object_type,
574 ObjectAccess::Write,
575 |entry, input| {
576 let action = A::decode(input)?;
577 let erased = entry.resident_state()?;
578 match &*erased {
579 ErasedState::Exclusive(state) => {
580 let mut state = state
581 .try_lock()
582 .map_err(|_| ActionError::user("object admission saturated"))?;
583 let typed = state
584 .downcast_mut::<T>()
585 .ok_or_else(|| ActionError::user("object type mismatch"))?;
586 action.execute(typed)?.encode()
587 }
588 ErasedState::ReadWrite(state) => {
589 let mut state = state
590 .try_write()
591 .map_err(|_| ActionError::user("object admission saturated"))?;
592 let typed = state
593 .downcast_mut::<T>()
594 .ok_or_else(|| ActionError::user("object type mismatch"))?;
595 action.execute(typed)?.encode()
596 }
597 }
598 },
599 )
600 }
601
602 fn require_type<T: DistributedObject>(
603 &self,
604 concurrency: ObjectConcurrency,
605 ) -> Result<ObjectTypeId, RuntimeError> {
606 let id = ObjectTypeId::new(T::TYPE_ID).map_err(|_| RuntimeError::InvalidObjectTypeId)?;
607 let registration = self
608 .types
609 .get(&id)
610 .ok_or(RuntimeError::UnknownObjectType(id))?;
611 if registration.concurrency != concurrency {
612 return Err(RuntimeError::InvalidObjectConcurrency);
613 }
614 Ok(id)
615 }
616
617 fn insert_action(
618 &mut self,
619 raw_id: u128,
620 input_schema: u64,
621 output_schema: u64,
622 object_type: ObjectTypeId,
623 access: ObjectAccess,
624 handler: impl Fn(&ObjectEntry, Segments) -> Result<Segments, ActionError>
625 + Send
626 + Sync
627 + 'static,
628 ) -> Result<(), RuntimeError> {
629 let id = ActionId::new(raw_id).map_err(|_| RuntimeError::InvalidActionId)?;
630 if input_schema == 0 || output_schema == 0 || self.reserves_action(id) {
631 return Err(RuntimeError::DuplicateAction(id));
632 }
633 self.actions.insert(
634 id,
635 ObjectActionRegistration {
636 object_type,
637 input_schema,
638 output_schema,
639 access,
640 handler: Arc::new(handler),
641 },
642 );
643 Ok(())
644 }
645
646 pub(crate) fn register_placement<A: Action>(
647 &mut self,
648 handler: RegisteredAction,
649 ) -> Result<ActionId, RuntimeError> {
650 let id =
651 placement_action_id(ActionId::new(A::ID).map_err(|_| RuntimeError::InvalidActionId)?)?;
652 if A::SCHEMA_ID == 0 || A::Output::SCHEMA_ID == 0 || self.reserves_action(id) {
653 return Err(RuntimeError::DuplicateAction(id));
654 }
655 self.placements.insert(
656 id,
657 PlacementActionRegistration {
658 input_schema: A::SCHEMA_ID,
659 output_schema: A::Output::SCHEMA_ID,
660 handler,
661 },
662 );
663 Ok(id)
664 }
665
666 pub(crate) fn reserves_action(&self, id: ActionId) -> bool {
667 matches!(
668 id.get(),
669 LEASE_RENEW_ACTION_ID
670 | LEASE_RELEASE_ACTION_ID
671 | LEASE_ACQUIRE_ACTION_ID
672 | ROOT_RELEASE_ACTION_ID
673 | TRANSFER_ACK_ACTION_ID
674 | MIGRATION_FREEZE_ACTION_ID
675 | MIGRATION_PREPARE_ACTION_ID
676 | MIGRATION_COMMIT_ACTION_ID
677 | MIGRATION_ACTIVATE_ACTION_ID
678 | MIGRATION_ROLLBACK_ACTION_ID
679 | MIGRATION_RETIRE_ACTION_ID
680 ) || self.actions.contains_key(&id)
681 || self.placements.contains_key(&id)
682 || self
683 .types
684 .keys()
685 .any(|object_type| create_action_id(*object_type).is_ok_and(|create| create == id))
686 }
687
688 pub(crate) fn install(
689 &self,
690 service: &Arc<ObjectService>,
691 actions: &mut ActionRegistry,
692 ) -> Result<(), RuntimeError> {
693 actions.insert_erased(
694 ActionId::new(LEASE_RENEW_ACTION_ID).unwrap(),
695 1,
696 1,
697 service.lease_handler(true),
698 )?;
699 actions.insert_erased(
700 ActionId::new(LEASE_RELEASE_ACTION_ID).unwrap(),
701 1,
702 1,
703 service.lease_handler(false),
704 )?;
705 actions.insert_erased(
706 ActionId::new(LEASE_ACQUIRE_ACTION_ID).unwrap(),
707 1,
708 1,
709 service.acquire_lease_handler(),
710 )?;
711 actions.insert_erased(
712 ActionId::new(ROOT_RELEASE_ACTION_ID).unwrap(),
713 1,
714 1,
715 service.root_release_handler(),
716 )?;
717 actions.insert_erased(
718 ActionId::new(TRANSFER_ACK_ACTION_ID).unwrap(),
719 1,
720 1,
721 service.transfer_ack_handler(),
722 )?;
723 for (id, handler) in [
724 (
725 MIGRATION_FREEZE_ACTION_ID,
726 service.migration_freeze_handler(),
727 ),
728 (
729 MIGRATION_PREPARE_ACTION_ID,
730 service.migration_prepare_handler(),
731 ),
732 (
733 MIGRATION_COMMIT_ACTION_ID,
734 service.migration_commit_handler(),
735 ),
736 (
737 MIGRATION_ACTIVATE_ACTION_ID,
738 service.migration_activate_handler(),
739 ),
740 (
741 MIGRATION_ROLLBACK_ACTION_ID,
742 service.migration_rollback_handler(),
743 ),
744 (
745 MIGRATION_RETIRE_ACTION_ID,
746 service.migration_retire_handler(),
747 ),
748 ] {
749 actions.insert_erased(
750 ActionId::new(id).unwrap(),
751 MIGRATION_SCHEMA_ID,
752 MIGRATION_SCHEMA_ID,
753 handler,
754 )?;
755 }
756 for (object_type, registration) in &self.types {
757 actions.insert_erased(
758 create_action_id(*object_type)?,
759 registration.schema,
760 LOCATION_SCHEMA_ID,
761 service.create_handler(*object_type)?,
762 )?;
763 }
764 for (id, registration) in &self.placements {
765 let handler = registration.handler.clone();
766 actions.insert_erased(
767 *id,
768 registration.input_schema,
769 registration.output_schema,
770 RegisteredAction::new(move |segments| {
771 let (_, payload) = ObjectEnvelope::decode(segments)?;
772 handler.execute_encoded(payload)
773 }),
774 )?;
775 }
776 for (id, registration) in &self.actions {
777 actions.insert_erased(
778 *id,
779 registration.input_schema,
780 registration.output_schema,
781 service.action_handler(*id)?,
782 )?;
783 }
784 Ok(())
785 }
786
787 pub(crate) fn fingerprint(&self) -> [u8; 32] {
788 let mut hashes = [
789 0xcbf2_9ce4_8422_2325_u64,
790 0x8422_2325_cbf2_9ce4,
791 0x9e37_79b9_7f4a_7c15,
792 0xd6e8_feb8_6659_fd93,
793 ];
794 for (id, registration) in &self.types {
795 hash_bytes(&mut hashes, &id.get().to_le_bytes());
796 hash_bytes(&mut hashes, ®istration.schema.to_le_bytes());
797 hash_bytes(&mut hashes, &[registration.concurrency as u8]);
798 hash_bytes(&mut hashes, &[registration.mobility as u8]);
799 hash_bytes(&mut hashes, ®istration.snapshot_version.to_le_bytes());
800 hash_bytes(&mut hashes, ®istration.snapshot_schema.to_le_bytes());
801 }
802 for (id, registration) in &self.placements {
803 hash_bytes(&mut hashes, &id.get().to_le_bytes());
804 hash_bytes(&mut hashes, ®istration.input_schema.to_le_bytes());
805 hash_bytes(&mut hashes, ®istration.output_schema.to_le_bytes());
806 }
807 for (id, registration) in &self.actions {
808 hash_bytes(&mut hashes, &id.get().to_le_bytes());
809 hash_bytes(&mut hashes, ®istration.object_type.get().to_le_bytes());
810 hash_bytes(&mut hashes, ®istration.input_schema.to_le_bytes());
811 hash_bytes(&mut hashes, ®istration.output_schema.to_le_bytes());
812 hash_bytes(&mut hashes, &[registration.access as u8]);
813 }
814 let mut result = [0; 32];
815 for (index, hash) in hashes.into_iter().enumerate() {
816 result[index * 8..index * 8 + 8].copy_from_slice(&hash.to_le_bytes());
817 }
818 result
819 }
820}
821
822fn validate_mobile<T: MobileObject>() -> Result<(), RuntimeError> {
823 if T::MOBILITY == Mobility::Pinned {
824 return Err(RuntimeError::PinnedObjectType(
825 ObjectTypeId::new(T::TYPE_ID).map_err(|_| RuntimeError::InvalidObjectTypeId)?,
826 ));
827 }
828 if T::SNAPSHOT_VERSION == 0 || T::Snapshot::SCHEMA_ID == 0 {
829 return Err(RuntimeError::InvalidSchema);
830 }
831 Ok(())
832}
833
834const PLACEMENT_ACTION_MASK: u128 = 0xc4ea_7e03_0000_0000_0000_0000_0000_0000;
835
836pub(crate) fn placement_action_id(action: ActionId) -> Result<ActionId, RuntimeError> {
837 ActionId::new(action.get() ^ PLACEMENT_ACTION_MASK).map_err(|_| RuntimeError::InvalidActionId)
838}
839
840const CREATE_ACTION_MASK: u128 = 0xc4ea_7e00_0000_0000_0000_0000_0000_0000;
841const LOCATION_SCHEMA_ID: u64 = 3;
842pub(crate) const LEASE_RENEW_ACTION_ID: u128 = 0xc4ea_7e01_0000_0000_0000_0000_0000_0001;
843pub(crate) const LEASE_RELEASE_ACTION_ID: u128 = 0xc4ea_7e01_0000_0000_0000_0000_0000_0002;
844pub(crate) const LEASE_ACQUIRE_ACTION_ID: u128 = 0xc4ea_7e01_0000_0000_0000_0000_0000_0003;
845pub(crate) const ROOT_RELEASE_ACTION_ID: u128 = 0xc4ea_7e01_0000_0000_0000_0000_0000_0004;
846pub(crate) const TRANSFER_ACK_ACTION_ID: u128 = 0xc4ea_7e01_0000_0000_0000_0000_0000_0005;
847
848pub(crate) fn create_action_id(object_type: ObjectTypeId) -> Result<ActionId, RuntimeError> {
849 ActionId::new(object_type.get() ^ CREATE_ACTION_MASK).map_err(|_| RuntimeError::InvalidActionId)
850}
851
852fn hash_bytes(hashes: &mut [u64; 4], bytes: &[u8]) {
853 for byte in bytes {
854 for (index, hash) in hashes.iter_mut().enumerate() {
855 *hash ^= u64::from(*byte).wrapping_add(index as u64);
856 *hash = hash.wrapping_mul(0x100_0000_01b3);
857 }
858 }
859}
860
861enum ErasedState {
862 Exclusive(Mutex<Box<dyn Any + Send>>),
863 ReadWrite(RwLock<Box<dyn Any + Send + Sync>>),
864}
865
866struct LocalAdmissionPin<'a> {
867 service: &'a ObjectService,
868 job: ObjectJob,
869}
870
871impl Drop for LocalAdmissionPin<'_> {
872 fn drop(&mut self) {
873 self.service.complete(self.job);
874 self.service.collect(self.job.object, Instant::now());
875 }
876}
877
878struct LeaseRecord {
879 expires_at: Instant,
880 suspect_since: Option<Instant>,
881}
882
883struct Admission {
884 readers: usize,
885 writer: bool,
886 queue: VecDeque<ActionJob>,
887}
888
889#[derive(Clone, Copy, Debug, Eq, PartialEq)]
890pub(crate) enum MigrationOutcome {
891 Completed,
892 RolledBack,
893 Failed,
894}
895
896#[derive(Clone, Copy, Debug, Eq, PartialEq)]
897enum ObjectLifecycle {
898 Resident,
899 Freezing {
900 migration: RequestId,
901 expires_at: Instant,
902 },
903 Prepared {
904 migration: RequestId,
905 expires_at: Instant,
906 },
907 Forwarding {
908 location: ObjectLocation,
909 expires_at: Instant,
910 },
911 Retiring {
912 location: ObjectLocation,
913 },
914}
915
916#[derive(Clone, Copy, Debug)]
917pub(crate) struct Retirement {
918 pub(crate) object: ObjectId,
919 pub(crate) object_type: ObjectTypeId,
920 pub(crate) authority: LocalityId,
921 pub(crate) location: ObjectLocation,
922 pub(crate) snapshot_version: u32,
923 pub(crate) snapshot_schema: u64,
924}
925
926struct PreparedState {
927 migration: RequestId,
928 location: ObjectLocation,
929 state: Arc<ErasedState>,
930 snapshot_bytes: usize,
931 expires_at: Instant,
932}
933
934struct ObjectEntry {
935 object_type: ObjectTypeId,
936 authority: LocalityId,
937 location: Mutex<ObjectLocation>,
938 state: Mutex<Option<Arc<ErasedState>>>,
939 lifecycle: Mutex<ObjectLifecycle>,
940 prepared: Mutex<Option<PreparedState>>,
941 snapshot_bytes: Mutex<usize>,
942 leases: Mutex<HashMap<LocalityId, LeaseRecord>>,
943 roots: Mutex<usize>,
944 in_flight: Mutex<usize>,
945 placement_tickets: Mutex<usize>,
946 admission: Mutex<Admission>,
947}
948
949impl ObjectEntry {
950 fn location(&self) -> ObjectLocation {
951 *self.location.lock().unwrap()
952 }
953
954 fn resident_state(&self) -> Result<Arc<ErasedState>, ActionError> {
955 self.state
956 .lock()
957 .unwrap()
958 .clone()
959 .ok_or_else(|| ActionError::user("object moved"))
960 }
961}
962
963pub(crate) struct ObjectService {
964 run_id: RunId,
965 local_id: LocalityId,
966 pub(crate) limits: ObjectLimits,
967 registry: ObjectRegistry,
968 entries: Mutex<HashMap<ObjectId, Arc<ObjectEntry>>>,
969 next_slot: Mutex<u64>,
970 resolver: Mutex<(HashMap<ObjectId, ObjectLocation>, VecDeque<ObjectId>)>,
971 local_calls: AtomicU64,
972 remote_calls: AtomicU64,
973 root_count: AtomicU64,
974 transfer_count: AtomicU64,
975 placement_count: AtomicU64,
976 pending_transfers: Mutex<HashSet<(ObjectId, LocalityId)>>,
977 active_migrations: Mutex<HashSet<RequestId>>,
978 prepared_count: AtomicU64,
979 completed_migrations: AtomicU64,
980 rolled_back_migrations: AtomicU64,
981 failed_migrations: AtomicU64,
982 migrated_bytes: AtomicU64,
983 retirements: Mutex<VecDeque<Retirement>>,
984}
985
986impl std::fmt::Debug for ObjectService {
987 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
988 let stats = self.stats(Instant::now());
989 f.debug_struct("ObjectService")
990 .field("local_id", &self.local_id)
991 .field("stats", &stats)
992 .finish()
993 }
994}
995
996impl ObjectService {
997 pub(crate) fn new(
998 run_id: RunId,
999 local_id: LocalityId,
1000 limits: ObjectLimits,
1001 registry: ObjectRegistry,
1002 ) -> Self {
1003 Self {
1004 run_id,
1005 local_id,
1006 limits,
1007 registry,
1008 entries: Mutex::new(HashMap::new()),
1009 next_slot: Mutex::new(1),
1010 resolver: Mutex::new((HashMap::new(), VecDeque::new())),
1011 local_calls: AtomicU64::new(0),
1012 remote_calls: AtomicU64::new(0),
1013 root_count: AtomicU64::new(0),
1014 transfer_count: AtomicU64::new(0),
1015 placement_count: AtomicU64::new(0),
1016 pending_transfers: Mutex::new(HashSet::new()),
1017 active_migrations: Mutex::new(HashSet::new()),
1018 prepared_count: AtomicU64::new(0),
1019 completed_migrations: AtomicU64::new(0),
1020 rolled_back_migrations: AtomicU64::new(0),
1021 failed_migrations: AtomicU64::new(0),
1022 migrated_bytes: AtomicU64::new(0),
1023 retirements: Mutex::new(VecDeque::new()),
1024 }
1025 }
1026
1027 pub(crate) fn create_local<T: DistributedObject>(
1028 &self,
1029 object: ObjectId,
1030 object_type: ObjectTypeId,
1031 domain: DomainId,
1032 state: T,
1033 lease_locality: LocalityId,
1034 rooted: bool,
1035 ) -> Result<ObjectLocation, RuntimeError> {
1036 let registration = self
1037 .registry
1038 .types
1039 .get(&object_type)
1040 .ok_or(RuntimeError::UnknownObjectType(object_type))?;
1041 let state = (registration.local_wrapper)(Box::new(state))?;
1042 self.insert(
1043 ObjectEnvelope {
1044 object,
1045 object_type,
1046 epoch: 1,
1047 lease_locality,
1048 domain,
1049 rooted,
1050 },
1051 state,
1052 Instant::now(),
1053 )
1054 .map_err(|error| RuntimeError::ObjectAction(error.message()))
1055 }
1056
1057 pub(crate) fn create_handler(
1058 self: &Arc<Self>,
1059 object_type: ObjectTypeId,
1060 ) -> Result<RegisteredAction, RuntimeError> {
1061 let registration = self
1062 .registry
1063 .types
1064 .get(&object_type)
1065 .cloned()
1066 .ok_or(RuntimeError::UnknownObjectType(object_type))?;
1067 let service = Arc::clone(self);
1068 Ok(RegisteredAction::new(move |segments| {
1069 let (envelope, state) = ObjectEnvelope::decode(segments)?;
1070 if envelope.object_type != object_type || envelope.object.run() != service.run_id {
1071 return Err(ActionError::user("object identity mismatch"));
1072 }
1073 let state = (registration.decoder)(state)?;
1074 let location = service.insert(envelope, state, Instant::now())?;
1075 encode_location(location)
1076 }))
1077 }
1078
1079 pub(crate) fn acquire_lease_handler(self: &Arc<Self>) -> RegisteredAction {
1080 let service = Arc::clone(self);
1081 RegisteredAction::new(move |segments| {
1082 let (envelope, payload) = ObjectEnvelope::decode(segments)?;
1083 let acquired = if envelope.rooted {
1084 service.begin_transfer(envelope.object, envelope.lease_locality, Instant::now())
1085 } else {
1086 service.acquire(envelope.object, envelope.lease_locality, Instant::now())
1087 };
1088 if !payload.is_empty() || !acquired {
1089 return Err(ActionError::user("object lease is unavailable"));
1090 }
1091 Ok(Vec::new())
1092 })
1093 }
1094
1095 pub(crate) fn transfer_ack_handler(self: &Arc<Self>) -> RegisteredAction {
1096 let service = Arc::clone(self);
1097 RegisteredAction::new(move |segments| {
1098 let (envelope, payload) = ObjectEnvelope::decode(segments)?;
1099 if !payload.is_empty() {
1100 return Err(ActionError::codec(
1101 "transfer acknowledgement payload must be empty",
1102 ));
1103 }
1104 service.ack_transfer(envelope.object, envelope.lease_locality);
1105 Ok(Vec::new())
1106 })
1107 }
1108
1109 pub(crate) fn root_release_handler(self: &Arc<Self>) -> RegisteredAction {
1110 let service = Arc::clone(self);
1111 RegisteredAction::new(move |segments| {
1112 let (envelope, payload) = ObjectEnvelope::decode(segments)?;
1113 if !payload.is_empty() {
1114 return Err(ActionError::codec("root control payload must be empty"));
1115 }
1116 service.release_root(envelope.object);
1117 Ok(Vec::new())
1118 })
1119 }
1120
1121 pub(crate) fn lease_handler(self: &Arc<Self>, renew: bool) -> RegisteredAction {
1122 let service = Arc::clone(self);
1123 RegisteredAction::new(move |segments| {
1124 let (envelope, payload) = ObjectEnvelope::decode(segments)?;
1125 if !payload.is_empty() {
1126 return Err(ActionError::codec("lease control payload must be empty"));
1127 }
1128 if renew {
1129 if !service.renew(envelope.object, envelope.lease_locality, Instant::now()) {
1130 return Err(ActionError::user("object lease is unavailable"));
1131 }
1132 } else {
1133 service.release(envelope.object, envelope.lease_locality);
1134 }
1135 Ok(Vec::new())
1136 })
1137 }
1138
1139 pub(crate) fn is_local_resident(&self, object: ObjectId, location: ObjectLocation) -> bool {
1140 self.entries
1141 .lock()
1142 .unwrap()
1143 .get(&object)
1144 .is_some_and(|entry| {
1145 let lifecycle = entry.lifecycle.lock().unwrap();
1146 matches!(*lifecycle, ObjectLifecycle::Resident)
1147 && entry.location() == location
1148 && entry.state.lock().unwrap().is_some()
1149 })
1150 }
1151
1152 pub(crate) fn request_targets_current(&self, action: ActionId, payload: &Segments) -> bool {
1153 if !self.registry.actions.contains_key(&action)
1154 && !self.registry.placements.contains_key(&action)
1155 {
1156 return false;
1157 }
1158 let Ok(envelope) = ObjectEnvelope::inspect(payload) else {
1159 return false;
1160 };
1161 self.entries
1162 .lock()
1163 .unwrap()
1164 .get(&envelope.object)
1165 .is_some_and(|entry| {
1166 entry.location().epoch() == envelope.epoch
1167 && matches!(*entry.lifecycle.lock().unwrap(), ObjectLifecycle::Resident)
1168 })
1169 }
1170
1171 pub(crate) fn admit(
1172 &self,
1173 action: ActionId,
1174 mut job: ActionJob,
1175 domain_available: bool,
1176 ) -> Result<Option<ActionJob>, RuntimeError> {
1177 let registration = self.registry.actions.get(&action);
1178 let placement = self.registry.placements.contains_key(&action);
1179 let freeze = action.get() == MIGRATION_FREEZE_ACTION_ID;
1180 if registration.is_none() && !placement && !freeze {
1181 return Ok(Some(job));
1182 }
1183 let encoded = match &job.input {
1184 ActionValue::Encoded(segments) => segments,
1185 ActionValue::Typed(_) => {
1186 return Err(RuntimeError::Protocol(
1187 "object admission requires encoded input".into(),
1188 ));
1189 }
1190 };
1191 let (object, object_type, epoch, migration) = if freeze {
1192 let packet = MigrationPacket::decode(encoded.clone())
1193 .map_err(|error| RuntimeError::Protocol(error.message()))?;
1194 (
1195 packet.object,
1196 packet.object_type,
1197 packet.from.epoch(),
1198 Some(packet.migration),
1199 )
1200 } else {
1201 let envelope = ObjectEnvelope::inspect(encoded)
1202 .map_err(|error| RuntimeError::Protocol(error.message()))?;
1203 (envelope.object, envelope.object_type, envelope.epoch, None)
1204 };
1205 let entry = self
1206 .entries
1207 .lock()
1208 .unwrap()
1209 .get(&object)
1210 .cloned()
1211 .ok_or(RuntimeError::ObjectCollected(object))?;
1212 if entry.object_type != object_type {
1213 return Err(RuntimeError::UnknownObjectType(object_type));
1214 }
1215 let location = entry.location();
1216 if location.epoch() != epoch {
1217 return Err(RuntimeError::Moved { object, location });
1218 }
1219 {
1220 let mut lifecycle = entry.lifecycle.lock().unwrap();
1221 if let Some(migration) = migration {
1222 match *lifecycle {
1223 ObjectLifecycle::Resident => {
1224 *lifecycle = ObjectLifecycle::Freezing {
1225 migration,
1226 expires_at: Instant::now() + self.limits.forwarding_ttl,
1227 };
1228 }
1229 ObjectLifecycle::Freezing {
1230 migration: existing,
1231 ..
1232 } if existing == migration => {}
1233 _ => return Err(RuntimeError::MigrationInProgress(object)),
1234 }
1235 } else {
1236 match *lifecycle {
1237 ObjectLifecycle::Resident => {}
1238 ObjectLifecycle::Forwarding { location, .. }
1239 | ObjectLifecycle::Retiring { location } => {
1240 return Err(RuntimeError::Moved { object, location });
1241 }
1242 ObjectLifecycle::Freezing { .. } | ObjectLifecycle::Prepared { .. } => {
1243 return Err(RuntimeError::MigrationInProgress(object));
1244 }
1245 }
1246 }
1247 }
1248 if placement {
1249 if !domain_available {
1250 return Err(RuntimeError::Placement {
1251 request: job.request,
1252 message: "colocated domain is saturated".into(),
1253 });
1254 }
1255 let lifecycle = entry.lifecycle.lock().unwrap();
1256 if !matches!(*lifecycle, ObjectLifecycle::Resident) {
1257 return Err(RuntimeError::MigrationInProgress(object));
1258 }
1259 self.placement_count
1260 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |count| {
1261 (count < self.limits.max_placement_tickets as u64).then_some(count + 1)
1262 })
1263 .map_err(|_| RuntimeError::ResourceExhausted {
1264 resource: crate::ResourceKind::PlacementTickets,
1265 limit: self.limits.max_placement_tickets,
1266 })?;
1267 *entry.placement_tickets.lock().unwrap() += 1;
1268 *entry.in_flight.lock().unwrap() += 1;
1269 drop(lifecycle);
1270 job.object = Some(ObjectJob {
1271 object,
1272 read: false,
1273 placement: true,
1274 });
1275 return Ok(Some(job));
1276 }
1277 let read =
1278 registration.is_some_and(|registration| registration.access == ObjectAccess::Read);
1279 let mut admission = entry.admission.lock().unwrap();
1280 let available = domain_available
1281 && admission.queue.is_empty()
1282 && (!freeze || *entry.placement_tickets.lock().unwrap() == 0)
1283 && if read {
1284 !admission.writer
1285 } else {
1286 !admission.writer && admission.readers == 0
1287 };
1288 job.object = Some(ObjectJob {
1289 object,
1290 read,
1291 placement: false,
1292 });
1293 if available {
1294 if read {
1295 admission.readers += 1;
1296 } else {
1297 admission.writer = true;
1298 }
1299 *entry.in_flight.lock().unwrap() += 1;
1300 return Ok(Some(job));
1301 }
1302 if admission.queue.len() >= self.limits.max_mailbox_per_object {
1303 return Err(RuntimeError::ResourceExhausted {
1304 resource: crate::ResourceKind::ObjectMailbox,
1305 limit: self.limits.max_mailbox_per_object,
1306 });
1307 }
1308 admission.queue.push_back(job);
1309 Ok(None)
1310 }
1311
1312 pub(crate) fn complete(&self, completed: ObjectJob) -> Vec<ActionJob> {
1313 let Some(entry) = self.entries.lock().unwrap().get(&completed.object).cloned() else {
1314 return Vec::new();
1315 };
1316 let mut admission = entry.admission.lock().unwrap();
1317 if completed.placement {
1318 let mut tickets = entry.placement_tickets.lock().unwrap();
1319 *tickets = tickets.saturating_sub(1);
1320 self.placement_count.fetch_sub(1, Ordering::AcqRel);
1321 } else if completed.read {
1322 admission.readers = admission.readers.saturating_sub(1);
1323 } else {
1324 admission.writer = false;
1325 }
1326 let mut in_flight = entry.in_flight.lock().unwrap();
1327 *in_flight = in_flight.saturating_sub(1);
1328 Vec::new()
1329 }
1330
1331 pub(crate) fn rollback(&self, job: ObjectJob) -> Vec<ActionJob> {
1332 self.complete(job)
1333 }
1334
1335 pub(crate) fn retry_ready(
1336 &self,
1337 max: usize,
1338 mut domain_available: impl FnMut(DomainId) -> bool,
1339 ) -> Vec<ActionJob> {
1340 let entries: Vec<_> = self.entries.lock().unwrap().values().cloned().collect();
1341 let mut ready = Vec::new();
1342 for entry in entries {
1343 if ready.len() >= max {
1344 break;
1345 }
1346 let mut admission = entry.admission.lock().unwrap();
1347 if admission.writer || admission.readers != 0 {
1348 continue;
1349 }
1350 loop {
1351 if ready.len() >= max {
1352 break;
1353 }
1354 let Some(front) = admission.queue.front() else {
1355 break;
1356 };
1357 if !domain_available(front.domain)
1358 || (front.action_id.get() == MIGRATION_FREEZE_ACTION_ID
1359 && *entry.placement_tickets.lock().unwrap() != 0)
1360 {
1361 break;
1362 }
1363 let object = front
1364 .object
1365 .expect("queued object job has admission metadata");
1366 if object.read {
1367 admission.readers += 1;
1368 } else {
1369 admission.writer = true;
1370 }
1371 *entry.in_flight.lock().unwrap() += 1;
1372 ready.push(admission.queue.pop_front().unwrap());
1373 if !object.read {
1374 break;
1375 }
1376 }
1377 }
1378 ready
1379 }
1380
1381 fn admit_local<'a>(
1382 &'a self,
1383 entry: &ObjectEntry,
1384 object: ObjectId,
1385 read: bool,
1386 ) -> Result<LocalAdmissionPin<'a>, RuntimeError> {
1387 let lifecycle = entry.lifecycle.lock().unwrap();
1388 match *lifecycle {
1389 ObjectLifecycle::Resident => {}
1390 ObjectLifecycle::Forwarding { location, .. }
1391 | ObjectLifecycle::Retiring { location } => {
1392 return Err(RuntimeError::Moved { object, location });
1393 }
1394 ObjectLifecycle::Freezing { .. } | ObjectLifecycle::Prepared { .. } => {
1395 return Err(RuntimeError::MigrationInProgress(object));
1396 }
1397 }
1398 let mut admission = entry.admission.lock().unwrap();
1399 drop(lifecycle);
1400 let available = admission.queue.is_empty()
1401 && if read {
1402 !admission.writer
1403 } else {
1404 !admission.writer && admission.readers == 0
1405 };
1406 if !available {
1407 return Err(RuntimeError::ResourceExhausted {
1408 resource: crate::ResourceKind::ObjectMailbox,
1409 limit: self.limits.max_mailbox_per_object,
1410 });
1411 }
1412 if read {
1413 admission.readers += 1;
1414 } else {
1415 admission.writer = true;
1416 }
1417 *entry.in_flight.lock().unwrap() += 1;
1418 Ok(LocalAdmissionPin {
1419 service: self,
1420 job: ObjectJob {
1421 object,
1422 read,
1423 placement: false,
1424 },
1425 })
1426 }
1427
1428 pub(crate) fn call_read_local<T, A>(
1429 &self,
1430 object: ObjectId,
1431 object_type: ObjectTypeId,
1432 location: ObjectLocation,
1433 action: A,
1434 ) -> Result<A::Output, RuntimeError>
1435 where
1436 T: DistributedObject + Sync,
1437 A: ObjectReadAction<T>,
1438 {
1439 let entry = self.local_entry(object, object_type, location)?;
1440 let _pin = self.admit_local(&entry, object, true)?;
1441 let erased = entry
1442 .resident_state()
1443 .map_err(|error| RuntimeError::ObjectAction(error.message()))?;
1444 let ErasedState::ReadWrite(state) = &*erased else {
1445 return Err(RuntimeError::InvalidObjectConcurrency);
1446 };
1447 let state = state
1448 .try_read()
1449 .map_err(|_| RuntimeError::ResourceExhausted {
1450 resource: crate::ResourceKind::ObjectMailbox,
1451 limit: self.limits.max_mailbox_per_object,
1452 })?;
1453 let typed = state
1454 .downcast_ref::<T>()
1455 .ok_or(RuntimeError::UnknownObjectType(object_type))?;
1456 self.local_calls.fetch_add(1, Ordering::Relaxed);
1457 match catch_unwind(AssertUnwindSafe(|| action.execute(typed))) {
1458 Ok(result) => result.map_err(|error| RuntimeError::ObjectAction(error.message())),
1459 Err(_) => Err(RuntimeError::ObjectAction("object action panicked".into())),
1460 }
1461 }
1462
1463 pub(crate) fn call_write_local<T, A>(
1464 &self,
1465 object: ObjectId,
1466 object_type: ObjectTypeId,
1467 location: ObjectLocation,
1468 action: A,
1469 ) -> Result<A::Output, RuntimeError>
1470 where
1471 T: DistributedObject,
1472 A: ObjectWriteAction<T>,
1473 {
1474 let entry = self.local_entry(object, object_type, location)?;
1475 let _pin = self.admit_local(&entry, object, false)?;
1476 let erased = entry
1477 .resident_state()
1478 .map_err(|error| RuntimeError::ObjectAction(error.message()))?;
1479 let output = match &*erased {
1480 ErasedState::Exclusive(state) => {
1481 let mut state = state
1482 .try_lock()
1483 .map_err(|_| RuntimeError::ResourceExhausted {
1484 resource: crate::ResourceKind::ObjectMailbox,
1485 limit: self.limits.max_mailbox_per_object,
1486 })?;
1487 let typed = state
1488 .downcast_mut::<T>()
1489 .ok_or(RuntimeError::UnknownObjectType(object_type))?;
1490 catch_unwind(AssertUnwindSafe(|| action.execute(typed)))
1491 }
1492 ErasedState::ReadWrite(state) => {
1493 let mut state = state
1494 .try_write()
1495 .map_err(|_| RuntimeError::ResourceExhausted {
1496 resource: crate::ResourceKind::ObjectMailbox,
1497 limit: self.limits.max_mailbox_per_object,
1498 })?;
1499 let typed = state
1500 .downcast_mut::<T>()
1501 .ok_or(RuntimeError::UnknownObjectType(object_type))?;
1502 catch_unwind(AssertUnwindSafe(|| action.execute(typed)))
1503 }
1504 };
1505 self.local_calls.fetch_add(1, Ordering::Relaxed);
1506 match output {
1507 Ok(result) => result.map_err(|error| RuntimeError::ObjectAction(error.message())),
1508 Err(_) => Err(RuntimeError::ObjectAction("object action panicked".into())),
1509 }
1510 }
1511
1512 fn local_entry(
1513 &self,
1514 object: ObjectId,
1515 object_type: ObjectTypeId,
1516 location: ObjectLocation,
1517 ) -> Result<Arc<ObjectEntry>, RuntimeError> {
1518 let entry = self
1519 .entries
1520 .lock()
1521 .unwrap()
1522 .get(&object)
1523 .cloned()
1524 .ok_or(RuntimeError::ObjectCollected(object))?;
1525 if entry.object_type != object_type {
1526 return Err(RuntimeError::UnknownObjectType(object_type));
1527 }
1528 if entry.location().epoch() != location.epoch() {
1529 return Err(RuntimeError::StaleObjectLocation(object));
1530 }
1531 Ok(entry)
1532 }
1533
1534 pub(crate) fn action_handler(
1535 self: &Arc<Self>,
1536 action: ActionId,
1537 ) -> Result<RegisteredAction, RuntimeError> {
1538 let registration = self
1539 .registry
1540 .actions
1541 .get(&action)
1542 .cloned()
1543 .ok_or(RuntimeError::UnknownAction(action))?;
1544 let service = Arc::clone(self);
1545 Ok(RegisteredAction::new(move |segments| {
1546 let (envelope, input) = ObjectEnvelope::decode(segments)?;
1547 if envelope.object_type != registration.object_type {
1548 return Err(ActionError::user("object type mismatch"));
1549 }
1550 service.remote_calls.fetch_add(1, Ordering::Relaxed);
1551 let entry = service
1552 .entries
1553 .lock()
1554 .unwrap()
1555 .get(&envelope.object)
1556 .cloned()
1557 .ok_or_else(|| ActionError::user("object collected"))?;
1558 if entry.object_type != envelope.object_type
1559 || entry.location().epoch() != envelope.epoch
1560 {
1561 return Err(ActionError::user("stale object location"));
1562 }
1563 (registration.handler)(&entry, input)
1564 }))
1565 }
1566
1567 fn insert(
1568 &self,
1569 envelope: ObjectEnvelope,
1570 state: ErasedState,
1571 now: Instant,
1572 ) -> Result<ObjectLocation, ActionError> {
1573 let object = envelope.object;
1574 let object_type = envelope.object_type;
1575 let mut entries = self.entries.lock().unwrap();
1576 if entries.len() >= self.limits.max_objects {
1577 return Err(ActionError::user("object store is full"));
1578 }
1579 if entries.contains_key(&object) {
1580 return Err(ActionError::user("duplicate object id"));
1581 }
1582 let mut slot = self.next_slot.lock().unwrap();
1583 let current = *slot;
1584 *slot = slot
1585 .checked_add(1)
1586 .ok_or_else(|| ActionError::user("object slot overflow"))?;
1587 let location = ObjectLocation::new(self.local_id, envelope.domain, current, 1, 1)
1588 .map_err(|_| ActionError::user("invalid object location"))?;
1589 if envelope.rooted
1590 && self
1591 .root_count
1592 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |count| {
1593 (count < self.limits.max_roots as u64).then_some(count + 1)
1594 })
1595 .is_err()
1596 {
1597 return Err(ActionError::user("object root limit reached"));
1598 }
1599 let mut leases = HashMap::new();
1600 leases.insert(
1601 envelope.lease_locality,
1602 LeaseRecord {
1603 expires_at: now + self.limits.lease_ttl,
1604 suspect_since: None,
1605 },
1606 );
1607 entries.insert(
1608 object,
1609 Arc::new(ObjectEntry {
1610 object_type,
1611 authority: self.local_id,
1612 location: Mutex::new(location),
1613 state: Mutex::new(Some(Arc::new(state))),
1614 lifecycle: Mutex::new(ObjectLifecycle::Resident),
1615 prepared: Mutex::new(None),
1616 snapshot_bytes: Mutex::new(0),
1617 leases: Mutex::new(leases),
1618 roots: Mutex::new(usize::from(envelope.rooted)),
1619 in_flight: Mutex::new(0),
1620 placement_tickets: Mutex::new(0),
1621 admission: Mutex::new(Admission {
1622 readers: 0,
1623 writer: false,
1624 queue: VecDeque::new(),
1625 }),
1626 }),
1627 );
1628 drop(entries);
1629 self.cache_location(object, location);
1630 Ok(location)
1631 }
1632
1633 pub(crate) fn cache_location(&self, object: ObjectId, location: ObjectLocation) {
1634 let mut resolver = self.resolver.lock().unwrap();
1635 if !resolver.0.contains_key(&object) {
1636 if resolver.0.len() >= self.limits.max_resolver_entries {
1637 if let Some(oldest) = resolver.1.pop_front() {
1638 resolver.0.remove(&oldest);
1639 }
1640 }
1641 resolver.1.push_back(object);
1642 }
1643 resolver.0.insert(object, location);
1644 }
1645
1646 pub(crate) fn resolve(&self, object: ObjectId) -> Option<ObjectLocation> {
1647 self.resolver
1648 .lock()
1649 .unwrap()
1650 .0
1651 .get(&object)
1652 .copied()
1653 .or_else(|| {
1654 self.entries
1655 .lock()
1656 .unwrap()
1657 .get(&object)
1658 .map(|entry| entry.location())
1659 })
1660 }
1661
1662 pub(crate) fn clear_all(&self) {
1663 self.entries.lock().unwrap().clear();
1664 self.root_count.store(0, Ordering::Release);
1665 self.transfer_count.store(0, Ordering::Release);
1666 self.placement_count.store(0, Ordering::Release);
1667 self.pending_transfers.lock().unwrap().clear();
1668 self.active_migrations.lock().unwrap().clear();
1669 self.prepared_count.store(0, Ordering::Release);
1670 self.retirements.lock().unwrap().clear();
1671 self.clear_resolver();
1672 }
1673
1674 pub(crate) fn clear_resolver(&self) {
1675 let mut resolver = self.resolver.lock().unwrap();
1676 resolver.0.clear();
1677 resolver.1.clear();
1678 }
1679
1680 pub(crate) fn begin_transfer(
1681 &self,
1682 object: ObjectId,
1683 locality: LocalityId,
1684 now: Instant,
1685 ) -> bool {
1686 let key = (object, locality);
1687 if self
1688 .entries
1689 .lock()
1690 .unwrap()
1691 .get(&object)
1692 .is_none_or(|entry| entry.leases.lock().unwrap().contains_key(&locality))
1693 {
1694 return false;
1695 }
1696 {
1697 let mut transfers = self.pending_transfers.lock().unwrap();
1698 if transfers.contains(&key) {
1699 return false;
1700 }
1701 if self
1702 .transfer_count
1703 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |count| {
1704 (count < self.limits.max_transfers as u64).then_some(count + 1)
1705 })
1706 .is_err()
1707 {
1708 return false;
1709 }
1710 transfers.insert(key);
1711 }
1712 if self.acquire(object, locality, now) {
1713 true
1714 } else {
1715 self.pending_transfers.lock().unwrap().remove(&key);
1716 self.transfer_count.fetch_sub(1, Ordering::AcqRel);
1717 false
1718 }
1719 }
1720
1721 pub(crate) fn ack_transfer(&self, object: ObjectId, locality: LocalityId) -> bool {
1722 if self
1723 .pending_transfers
1724 .lock()
1725 .unwrap()
1726 .remove(&(object, locality))
1727 {
1728 self.transfer_count.fetch_sub(1, Ordering::AcqRel);
1729 true
1730 } else {
1731 false
1732 }
1733 }
1734
1735 pub(crate) fn acquire(&self, object: ObjectId, locality: LocalityId, now: Instant) -> bool {
1736 let Some(entry) = self.entries.lock().unwrap().get(&object).cloned() else {
1737 return false;
1738 };
1739 let mut leases = entry.leases.lock().unwrap();
1740 if leases.len() >= self.limits.max_local_leases && !leases.contains_key(&locality) {
1741 return false;
1742 }
1743 leases.insert(
1744 locality,
1745 LeaseRecord {
1746 expires_at: now + self.limits.lease_ttl,
1747 suspect_since: None,
1748 },
1749 );
1750 true
1751 }
1752
1753 pub(crate) fn release_root(&self, object: ObjectId) -> bool {
1754 let Some(entry) = self.entries.lock().unwrap().get(&object).cloned() else {
1755 return false;
1756 };
1757 let mut roots = entry.roots.lock().unwrap();
1758 if *roots == 0 {
1759 return false;
1760 }
1761 *roots -= 1;
1762 self.root_count.fetch_sub(1, Ordering::AcqRel);
1763 drop(roots);
1764 self.collect(object, Instant::now());
1765 true
1766 }
1767
1768 pub(crate) fn renew(&self, object: ObjectId, locality: LocalityId, now: Instant) -> bool {
1769 let Some(entry) = self.entries.lock().unwrap().get(&object).cloned() else {
1770 return false;
1771 };
1772 let mut leases = entry.leases.lock().unwrap();
1773 let Some(lease) = leases.get_mut(&locality) else {
1774 return false;
1775 };
1776 lease.expires_at = now + self.limits.lease_ttl;
1777 lease.suspect_since = None;
1778 true
1779 }
1780
1781 pub(crate) fn release(&self, object: ObjectId, locality: LocalityId) -> bool {
1782 let Some(entry) = self.entries.lock().unwrap().get(&object).cloned() else {
1783 return false;
1784 };
1785 let removed = entry.leases.lock().unwrap().remove(&locality).is_some();
1786 self.ack_transfer(object, locality);
1787 self.collect(object, Instant::now());
1788 removed
1789 }
1790
1791 pub(crate) fn expire(&self, now: Instant) {
1792 let ids: Vec<_> = self.entries.lock().unwrap().keys().copied().collect();
1793 for id in ids {
1794 let Some(entry) = self.entries.lock().unwrap().get(&id).cloned() else {
1795 continue;
1796 };
1797 {
1798 let mut lifecycle = entry.lifecycle.lock().unwrap();
1799 match *lifecycle {
1800 ObjectLifecycle::Freezing { expires_at, .. } if expires_at <= now => {
1801 *lifecycle = ObjectLifecycle::Resident;
1802 *entry.snapshot_bytes.lock().unwrap() = 0;
1803 }
1804 ObjectLifecycle::Prepared { expires_at, .. } if expires_at <= now => {
1805 if entry.prepared.lock().unwrap().take().is_some() {
1806 self.prepared_count.fetch_sub(1, Ordering::AcqRel);
1807 }
1808 *lifecycle = ObjectLifecycle::Forwarding {
1809 location: entry.location(),
1810 expires_at: now + self.limits.forwarding_ttl,
1811 };
1812 }
1813 ObjectLifecycle::Forwarding { expires_at, .. }
1814 if expires_at <= now && entry.authority != self.local_id =>
1815 {
1816 *entry.state.lock().unwrap() = None;
1817 }
1818 _ => {}
1819 }
1820 let expired_prepared = entry
1821 .prepared
1822 .lock()
1823 .unwrap()
1824 .as_ref()
1825 .is_some_and(|prepared| prepared.expires_at <= now);
1826 if expired_prepared && entry.prepared.lock().unwrap().take().is_some() {
1827 self.prepared_count.fetch_sub(1, Ordering::AcqRel);
1828 }
1829 }
1830 let mut leases = entry.leases.lock().unwrap();
1831 for lease in leases.values_mut() {
1832 if lease.expires_at <= now && lease.suspect_since.is_none() {
1833 lease.suspect_since = Some(now);
1834 }
1835 }
1836 leases.retain(|_, lease| {
1837 lease.suspect_since.is_none_or(|since| {
1838 now.saturating_duration_since(since) < self.limits.lease_grace
1839 })
1840 });
1841 let active: HashSet<_> = leases.keys().copied().collect();
1842 drop(leases);
1843 let mut transfers = self.pending_transfers.lock().unwrap();
1844 let before = transfers.len();
1845 transfers.retain(|(object, locality)| *object != id || active.contains(locality));
1846 let removed = before - transfers.len();
1847 if removed != 0 {
1848 self.transfer_count
1849 .fetch_sub(removed as u64, Ordering::AcqRel);
1850 }
1851 drop(transfers);
1852 self.collect(id, now);
1853 }
1854 }
1855
1856 fn collect(&self, object: ObjectId, _now: Instant) {
1857 let Some(entry) = self.entries.lock().unwrap().get(&object).cloned() else {
1858 return;
1859 };
1860 let idle = *entry.in_flight.lock().unwrap() == 0
1861 && *entry.placement_tickets.lock().unwrap() == 0
1862 && entry.admission.lock().unwrap().queue.is_empty()
1863 && entry.prepared.lock().unwrap().is_none();
1864 if !idle {
1865 return;
1866 }
1867 if entry.authority != self.local_id {
1868 let lifecycle = entry.lifecycle.lock().unwrap();
1869 if matches!(
1870 *lifecycle,
1871 ObjectLifecycle::Forwarding { .. } | ObjectLifecycle::Retiring { .. }
1872 ) && entry.state.lock().unwrap().is_none()
1873 {
1874 drop(lifecycle);
1875 self.entries.lock().unwrap().remove(&object);
1876 self.resolver.lock().unwrap().0.remove(&object);
1877 }
1878 return;
1879 }
1880 if !entry.leases.lock().unwrap().is_empty() || *entry.roots.lock().unwrap() != 0 {
1881 return;
1882 }
1883 let lifecycle = *entry.lifecycle.lock().unwrap();
1884 match lifecycle {
1885 ObjectLifecycle::Resident if entry.state.lock().unwrap().is_some() => {
1886 self.entries.lock().unwrap().remove(&object);
1887 self.resolver.lock().unwrap().0.remove(&object);
1888 }
1889 ObjectLifecycle::Forwarding { location, .. }
1890 if entry.state.lock().unwrap().is_none() =>
1891 {
1892 if location.locality() == self.local_id {
1893 self.entries.lock().unwrap().remove(&object);
1894 self.resolver.lock().unwrap().0.remove(&object);
1895 return;
1896 }
1897 let registration = self.registry.types.get(&entry.object_type).unwrap();
1898 let mut retirements = self.retirements.lock().unwrap();
1899 if retirements.len() >= self.limits.max_migrations {
1900 return;
1901 }
1902 *entry.lifecycle.lock().unwrap() = ObjectLifecycle::Retiring { location };
1903 retirements.push_back(Retirement {
1904 object,
1905 object_type: entry.object_type,
1906 authority: entry.authority,
1907 location,
1908 snapshot_version: registration.snapshot_version,
1909 snapshot_schema: registration.snapshot_schema,
1910 });
1911 }
1912 ObjectLifecycle::Retiring { .. } => {}
1913 _ => {}
1914 }
1915 }
1916
1917 pub(crate) fn take_retirements(&self, max: usize) -> Vec<Retirement> {
1918 let mut queue = self.retirements.lock().unwrap();
1919 (0..max).filter_map(|_| queue.pop_front()).collect()
1920 }
1921
1922 pub(crate) fn requeue_retirement(&self, retirement: Retirement) {
1923 self.retirements.lock().unwrap().push_front(retirement);
1924 }
1925
1926 pub(crate) fn finish_retirement(&self, object: ObjectId) {
1927 self.entries.lock().unwrap().remove(&object);
1928 self.resolver.lock().unwrap().0.remove(&object);
1929 }
1930
1931 pub(crate) fn stats(&self, now: Instant) -> ObjectStats {
1932 let entries = self.entries.lock().unwrap();
1933 let resolver_entries = self.resolver.lock().unwrap().0.len();
1934 let mut stats = ObjectStats {
1935 live_objects: entries.len(),
1936 resolver_entries,
1937 resolver_bytes: resolver_entries
1938 .saturating_mul(std::mem::size_of::<(ObjectId, ObjectLocation)>()),
1939 local_calls: self.local_calls.load(Ordering::Relaxed),
1940 remote_calls: self.remote_calls.load(Ordering::Relaxed),
1941 transfers: self.transfer_count.load(Ordering::Relaxed) as usize,
1942 placement_tickets: self.placement_count.load(Ordering::Relaxed) as usize,
1943 active_migrations: self.active_migrations.lock().unwrap().len(),
1944 prepared_migrations: self.prepared_count.load(Ordering::Relaxed) as usize,
1945 completed_migrations: self.completed_migrations.load(Ordering::Relaxed),
1946 rolled_back_migrations: self.rolled_back_migrations.load(Ordering::Relaxed),
1947 failed_migrations: self.failed_migrations.load(Ordering::Relaxed),
1948 transferred_snapshot_bytes: self.migrated_bytes.load(Ordering::Relaxed),
1949 ..ObjectStats::default()
1950 };
1951 for entry in entries.values() {
1952 let leases = entry.leases.lock().unwrap();
1953 stats.leases += leases.len();
1954 stats.suspect_leases += leases
1955 .values()
1956 .filter(|lease| lease.expires_at <= now || lease.suspect_since.is_some())
1957 .count();
1958 stats.roots += *entry.roots.lock().unwrap();
1959 stats.in_flight_calls += *entry.in_flight.lock().unwrap();
1960 stats.queued_calls += entry.admission.lock().unwrap().queue.len();
1961 stats.migration_snapshot_bytes = stats
1962 .migration_snapshot_bytes
1963 .saturating_add(*entry.snapshot_bytes.lock().unwrap());
1964 if matches!(
1965 *entry.lifecycle.lock().unwrap(),
1966 ObjectLifecycle::Forwarding { .. } | ObjectLifecycle::Retiring { .. }
1967 ) {
1968 stats.forwarding_entries += 1;
1969 }
1970 }
1971 stats
1972 }
1973}
1974
1975pub(crate) fn decode_location(bytes: Vec<u8>) -> Result<ObjectLocation, RuntimeError> {
1976 if bytes.len() != 32 {
1977 return Err(RuntimeError::Protocol(
1978 "invalid object location payload".into(),
1979 ));
1980 }
1981 ObjectLocation::new(
1982 LocalityId::new(u64::from_le_bytes(bytes[0..8].try_into().unwrap())),
1983 DomainId::new(u32::from_le_bytes(bytes[8..12].try_into().unwrap())),
1984 u64::from_le_bytes(bytes[12..20].try_into().unwrap()),
1985 u32::from_le_bytes(bytes[20..24].try_into().unwrap()),
1986 u64::from_le_bytes(bytes[24..32].try_into().unwrap()),
1987 )
1988 .map_err(|error| RuntimeError::Protocol(format!("invalid object location: {error}")))
1989}
1990
1991pub(crate) struct LocalLease {
1992 object: ObjectId,
1993 object_type: ObjectTypeId,
1994 pub(crate) location: ObjectLocation,
1995 client: RuntimeClient,
1996 active: AtomicBool,
1997 next_renewal: Mutex<Instant>,
1998}
1999
2000impl LocalLease {
2001 fn new(
2002 object: ObjectId,
2003 object_type: ObjectTypeId,
2004 location: ObjectLocation,
2005 client: RuntimeClient,
2006 renew_interval: Duration,
2007 ) -> Self {
2008 Self {
2009 object,
2010 object_type,
2011 location,
2012 client,
2013 active: AtomicBool::new(true),
2014 next_renewal: Mutex::new(Instant::now() + renew_interval),
2015 }
2016 }
2017
2018 pub(crate) fn renew(&self) {
2019 self.client
2020 .renew_object(self.object, self.object_type, self.location);
2021 }
2022
2023 pub(crate) fn deactivate(&self) {
2024 if self.active.swap(false, Ordering::AcqRel) {
2025 self.client
2026 .release_object(self.object, self.object_type, self.location);
2027 }
2028 }
2029}
2030
2031impl Drop for LocalLease {
2032 fn drop(&mut self) {
2033 if self.active.swap(false, Ordering::AcqRel) {
2034 self.client
2035 .release_object(self.object, self.object_type, self.location);
2036 }
2037 }
2038}
2039
2040pub(crate) struct LocalLeaseManager {
2041 limit: usize,
2042 renew_interval: Duration,
2043 entries: Mutex<HashMap<ObjectId, Weak<LocalLease>>>,
2044}
2045
2046impl LocalLeaseManager {
2047 pub(crate) fn new(limit: usize, renew_interval: Duration) -> Self {
2048 Self {
2049 limit,
2050 renew_interval,
2051 entries: Mutex::new(HashMap::new()),
2052 }
2053 }
2054
2055 pub(crate) fn attach<T: DistributedObject>(
2056 &self,
2057 object: ObjectId,
2058 object_type: ObjectTypeId,
2059 location: ObjectLocation,
2060 client: RuntimeClient,
2061 ) -> Result<Remote<T>, RuntimeError> {
2062 let mut entries = self.entries.lock().unwrap();
2063 if let Some(existing) = entries.get(&object).and_then(Weak::upgrade) {
2064 return Ok(Remote {
2065 object,
2066 object_type,
2067 location,
2068 client,
2069 lease: existing,
2070 marker: PhantomData,
2071 });
2072 }
2073 entries.retain(|_, lease| lease.strong_count() != 0);
2074 if entries.len() >= self.limit {
2075 return Err(RuntimeError::ResourceExhausted {
2076 resource: crate::ResourceKind::LocalityLeases,
2077 limit: self.limit,
2078 });
2079 }
2080 let lease = Arc::new(LocalLease::new(
2081 object,
2082 object_type,
2083 location,
2084 client.clone(),
2085 self.renew_interval,
2086 ));
2087 entries.insert(object, Arc::downgrade(&lease));
2088 Ok(Remote {
2089 object,
2090 object_type,
2091 location,
2092 client,
2093 lease,
2094 marker: PhantomData,
2095 })
2096 }
2097
2098 pub(crate) fn due(&self, now: Instant) -> Vec<Arc<LocalLease>> {
2099 let mut entries = self.entries.lock().unwrap();
2100 let mut due = Vec::new();
2101 entries.retain(|_, weak| {
2102 let Some(lease) = weak.upgrade() else {
2103 return false;
2104 };
2105 let mut next = lease.next_renewal.lock().unwrap();
2106 if lease.active.load(Ordering::Acquire) && *next <= now {
2107 *next = now + self.renew_interval;
2108 due.push(Arc::clone(&lease));
2109 }
2110 true
2111 });
2112 due
2113 }
2114
2115 pub(crate) fn clear(&self) {
2116 let leases: Vec<_> = self
2117 .entries
2118 .lock()
2119 .unwrap()
2120 .drain()
2121 .filter_map(|(_, lease)| lease.upgrade())
2122 .collect();
2123 for lease in leases {
2124 lease.deactivate();
2125 }
2126 }
2127}
2128
2129#[must_use = "a colocated future must be driven or dropped"]
2130pub struct ColocatedFuture<T: WireValue> {
2131 inner: RemoteObjectCall<T>,
2132 _lease: Arc<LocalLease>,
2133}
2134
2135impl<T: WireValue> ColocatedFuture<T> {
2136 pub(crate) fn new(inner: RemoteObjectCall<T>, lease: Arc<LocalLease>) -> Self {
2137 Self {
2138 inner,
2139 _lease: lease,
2140 }
2141 }
2142}
2143
2144impl<T: WireValue> Future for ColocatedFuture<T> {
2145 type Output = Result<T, RuntimeError>;
2146
2147 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
2148 Pin::new(&mut self.inner).poll(context)
2149 }
2150}
2151
2152impl<T: WireValue> Unpin for ColocatedFuture<T> {}
2153
2154enum PlacementState<T: WireValue> {
2155 Preferred(ColocatedFuture<T>),
2156 Fallback(RemoteFuture<T>),
2157 Done,
2158}
2159
2160#[must_use = "a placement future must be driven or dropped"]
2161pub struct PlacementFuture<T: WireValue> {
2162 state: PlacementState<T>,
2163 fallback: PlacementFallback,
2164 client: RuntimeClient,
2165 action: ActionId,
2166 payload: Option<Segments>,
2167 options: SpawnOptions,
2168}
2169
2170impl<T: WireValue> PlacementFuture<T> {
2171 pub(crate) fn new(
2172 preferred: ColocatedFuture<T>,
2173 fallback: PlacementFallback,
2174 client: RuntimeClient,
2175 action: ActionId,
2176 payload: Segments,
2177 options: SpawnOptions,
2178 ) -> Self {
2179 Self {
2180 state: PlacementState::Preferred(preferred),
2181 fallback,
2182 client,
2183 action,
2184 payload: Some(payload),
2185 options,
2186 }
2187 }
2188}
2189
2190impl<T: WireValue> Future for PlacementFuture<T> {
2191 type Output = Result<T, RuntimeError>;
2192
2193 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
2194 loop {
2195 match &mut self.state {
2196 PlacementState::Preferred(future) => match Pin::new(future).poll(context) {
2197 Poll::Pending => return Poll::Pending,
2198 Poll::Ready(Ok(value)) => {
2199 self.state = PlacementState::Done;
2200 return Poll::Ready(Ok(value));
2201 }
2202 Poll::Ready(Err(error)) if placement_saturated(&error) => {
2203 let place = match self.fallback {
2204 PlacementFallback::Any => {
2205 Place::new(self.client.local_id(), DomainId::DEFAULT)
2206 }
2207 PlacementFallback::Place(place) => place,
2208 PlacementFallback::Reject => {
2209 self.state = PlacementState::Done;
2210 return Poll::Ready(Err(error));
2211 }
2212 };
2213 let payload = self.payload.take().expect("placement fallback used once");
2214 match self
2215 .client
2216 .spawn_encoded(place, self.action, payload, self.options)
2217 {
2218 Ok(future) => self.state = PlacementState::Fallback(future),
2219 Err(error) => {
2220 self.state = PlacementState::Done;
2221 return Poll::Ready(Err(error));
2222 }
2223 }
2224 }
2225 Poll::Ready(Err(error)) => {
2226 self.state = PlacementState::Done;
2227 return Poll::Ready(Err(error));
2228 }
2229 },
2230 PlacementState::Fallback(future) => {
2231 let result = Pin::new(future).poll(context);
2232 if result.is_ready() {
2233 self.state = PlacementState::Done;
2234 }
2235 return result;
2236 }
2237 PlacementState::Done => {
2238 return Poll::Ready(Err(RuntimeError::Protocol(
2239 "placement future polled after completion".into(),
2240 )));
2241 }
2242 }
2243 }
2244 }
2245}
2246
2247fn placement_saturated(error: &RuntimeError) -> bool {
2248 matches!(
2249 error,
2250 RuntimeError::ResourceExhausted { .. } | RuntimeError::Placement { .. }
2251 ) || matches!(error, RuntimeError::RemoteAction { message, .. }
2252 if message.contains("ResourceExhausted")
2253 || message.contains("saturated")
2254 || message.contains("queue")
2255 || message.contains("MigrationInProgress")
2256 || message.contains("migration"))
2257}
2258
2259impl<T: WireValue> Unpin for PlacementFuture<T> {}
2260
2261pub(crate) struct RemoteCallSpec {
2262 pub(crate) object: ObjectId,
2263 pub(crate) object_type: ObjectTypeId,
2264 pub(crate) action: ActionId,
2265 pub(crate) payload: Segments,
2266 pub(crate) last_epoch: u64,
2267 pub(crate) max_redirects: usize,
2268 pub(crate) deadline: Duration,
2269}
2270
2271pub(crate) struct RemoteObjectCall<T: WireValue> {
2272 future: RemoteFuture<T>,
2273 client: RuntimeClient,
2274 object: ObjectId,
2275 object_type: ObjectTypeId,
2276 action: ActionId,
2277 payload: Segments,
2278 request: RequestId,
2279 last_epoch: u64,
2280 redirects: usize,
2281 max_redirects: usize,
2282 deadline: Duration,
2283}
2284
2285impl<T: WireValue> RemoteObjectCall<T> {
2286 pub(crate) fn new(
2287 future: RemoteFuture<T>,
2288 client: RuntimeClient,
2289 spec: RemoteCallSpec,
2290 ) -> Self {
2291 let request = future.request_id();
2292 Self {
2293 future,
2294 client,
2295 object: spec.object,
2296 object_type: spec.object_type,
2297 action: spec.action,
2298 payload: spec.payload,
2299 request,
2300 last_epoch: spec.last_epoch,
2301 redirects: 0,
2302 max_redirects: spec.max_redirects,
2303 deadline: spec.deadline,
2304 }
2305 }
2306}
2307
2308impl<T: WireValue> Future for RemoteObjectCall<T> {
2309 type Output = Result<T, RuntimeError>;
2310
2311 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
2312 loop {
2313 match Pin::new(&mut self.future).poll(context) {
2314 Poll::Pending => return Poll::Pending,
2315 Poll::Ready(Err(RuntimeError::Moved { object, location })) => {
2316 if object != self.object || location.epoch() <= self.last_epoch {
2317 return Poll::Ready(Err(RuntimeError::Protocol(format!(
2318 "invalid object redirect: expected {:?} after epoch {}, got {:?} at epoch {}",
2319 self.object,
2320 self.last_epoch,
2321 object,
2322 location.epoch()
2323 ))));
2324 }
2325 if self.redirects >= self.max_redirects {
2326 return Poll::Ready(Err(RuntimeError::RedirectLimit(self.object)));
2327 }
2328 self.redirects += 1;
2329 self.last_epoch = location.epoch();
2330 self.client.cache_object_location(self.object, location);
2331 let payload = ObjectEnvelope {
2332 object: self.object,
2333 object_type: self.object_type,
2334 epoch: location.epoch(),
2335 lease_locality: self.client.local_id(),
2336 domain: location.domain(),
2337 rooted: false,
2338 }
2339 .encode(self.payload.clone());
2340 match self.client.spawn_encoded_retry(
2341 Place::new(location.locality(), location.domain()),
2342 self.action,
2343 payload,
2344 SpawnOptions {
2345 deadline: self.deadline,
2346 trace_id: None,
2347 },
2348 self.request,
2349 ) {
2350 Ok(future) => self.future = future,
2351 Err(error) => return Poll::Ready(Err(error)),
2352 }
2353 }
2354 Poll::Ready(result) => return Poll::Ready(result),
2355 }
2356 }
2357 }
2358}
2359
2360impl<T: WireValue> Unpin for RemoteObjectCall<T> {}
2361
2362enum ObjectCallState<T: WireValue> {
2363 Local(Option<Result<T, RuntimeError>>),
2364 Remote(RemoteObjectCall<T>),
2365}
2366
2367#[must_use = "an object call future must be driven or dropped"]
2368pub struct ObjectCallFuture<T: WireValue> {
2369 state: ObjectCallState<T>,
2370}
2371
2372impl<T: WireValue> ObjectCallFuture<T> {
2373 pub(crate) fn local(result: Result<T, RuntimeError>) -> Self {
2374 Self {
2375 state: ObjectCallState::Local(Some(result)),
2376 }
2377 }
2378
2379 pub(crate) fn remote(future: RemoteObjectCall<T>) -> Self {
2380 Self {
2381 state: ObjectCallState::Remote(future),
2382 }
2383 }
2384}
2385
2386impl<T: WireValue> std::fmt::Debug for ObjectCallFuture<T> {
2387 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2388 f.debug_struct("ObjectCallFuture").finish_non_exhaustive()
2389 }
2390}
2391
2392impl<T: WireValue> Future for ObjectCallFuture<T> {
2393 type Output = Result<T, RuntimeError>;
2394
2395 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
2396 match &mut self.state {
2397 ObjectCallState::Local(result) => Poll::Ready(result.take().unwrap_or_else(|| {
2398 Err(RuntimeError::Protocol(
2399 "object future polled after completion".into(),
2400 ))
2401 })),
2402 ObjectCallState::Remote(future) => Pin::new(future).poll(context),
2403 }
2404 }
2405}
2406
2407impl<T: WireValue> Unpin for ObjectCallFuture<T> {}
2408
2409#[must_use = "an object creation future must be driven or dropped"]
2410pub struct CreateFuture<T: DistributedObject> {
2411 inner: Option<RemoteFuture<Vec<u8>>>,
2412 ready: Option<Result<Remote<T>, RuntimeError>>,
2413 object: ObjectId,
2414 object_type: ObjectTypeId,
2415 client: RuntimeClient,
2416}
2417
2418impl<T: DistributedObject> std::fmt::Debug for CreateFuture<T> {
2419 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2420 f.debug_struct("CreateFuture")
2421 .field("object", &self.object)
2422 .finish_non_exhaustive()
2423 }
2424}
2425
2426impl<T: DistributedObject> CreateFuture<T> {
2427 pub(crate) fn new(
2428 inner: RemoteFuture<Vec<u8>>,
2429 object: ObjectId,
2430 object_type: ObjectTypeId,
2431 client: RuntimeClient,
2432 ) -> Self {
2433 Self {
2434 inner: Some(inner),
2435 ready: None,
2436 object,
2437 object_type,
2438 client,
2439 }
2440 }
2441
2442 pub(crate) fn ready(
2443 result: Result<Remote<T>, RuntimeError>,
2444 object: ObjectId,
2445 object_type: ObjectTypeId,
2446 client: RuntimeClient,
2447 ) -> Self {
2448 Self {
2449 inner: None,
2450 ready: Some(result),
2451 object,
2452 object_type,
2453 client,
2454 }
2455 }
2456}
2457
2458impl<T: DistributedObject> Future for CreateFuture<T> {
2459 type Output = Result<Remote<T>, RuntimeError>;
2460
2461 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
2462 if let Some(result) = self.ready.take() {
2463 return Poll::Ready(result);
2464 }
2465 let poll = Pin::new(
2466 self.inner
2467 .as_mut()
2468 .expect("create future polled after completion"),
2469 )
2470 .poll(context);
2471 match poll {
2472 Poll::Ready(Ok(bytes)) => {
2473 self.inner = None;
2474 Poll::Ready(decode_location(bytes).and_then(|location| {
2475 self.client
2476 .attach_object(self.object, self.object_type, location)
2477 }))
2478 }
2479 Poll::Ready(Err(error)) => {
2480 self.inner = None;
2481 Poll::Ready(Err(error))
2482 }
2483 Poll::Pending => Poll::Pending,
2484 }
2485 }
2486}
2487
2488impl<T: DistributedObject> Unpin for CreateFuture<T> {}
2489
2490#[must_use = "a weak upgrade future must be driven or dropped"]
2491pub struct UpgradeFuture<T: DistributedObject> {
2492 inner: RemoteFuture<()>,
2493 object: ObjectId,
2494 object_type: ObjectTypeId,
2495 location: ObjectLocation,
2496 client: RuntimeClient,
2497 marker: PhantomData<T>,
2498}
2499
2500impl<T: DistributedObject> UpgradeFuture<T> {
2501 pub(crate) fn new(
2502 inner: RemoteFuture<()>,
2503 object: ObjectId,
2504 object_type: ObjectTypeId,
2505 location: ObjectLocation,
2506 client: RuntimeClient,
2507 ) -> Self {
2508 Self {
2509 inner,
2510 object,
2511 object_type,
2512 location,
2513 client,
2514 marker: PhantomData,
2515 }
2516 }
2517}
2518
2519impl<T: DistributedObject> Future for UpgradeFuture<T> {
2520 type Output = Result<Remote<T>, RuntimeError>;
2521
2522 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
2523 match Pin::new(&mut self.inner).poll(context) {
2524 Poll::Ready(Ok(())) => Poll::Ready(self.client.attach_object(
2525 self.object,
2526 self.object_type,
2527 self.location,
2528 )),
2529 Poll::Ready(Err(error)) => Poll::Ready(Err(error)),
2530 Poll::Pending => Poll::Pending,
2531 }
2532 }
2533}
2534
2535impl<T: DistributedObject> Unpin for UpgradeFuture<T> {}
2536
2537pub struct LeaseTransfer<T: DistributedObject> {
2538 pub(crate) object: ObjectId,
2539 pub(crate) object_type: ObjectTypeId,
2540 pub(crate) location: ObjectLocation,
2541 pub(crate) destination: LocalityId,
2542 pub(crate) expires_at: Instant,
2543 source_client: RuntimeClient,
2544 pub(crate) active: bool,
2545 marker: PhantomData<T>,
2546}
2547
2548impl<T: DistributedObject> std::fmt::Debug for LeaseTransfer<T> {
2549 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2550 f.debug_struct("LeaseTransfer")
2551 .field("object", &self.object)
2552 .field("destination", &self.destination)
2553 .finish_non_exhaustive()
2554 }
2555}
2556
2557impl<T: DistributedObject> Drop for LeaseTransfer<T> {
2558 fn drop(&mut self) {
2559 if self.active {
2560 self.source_client.release_for_locality(
2561 self.object,
2562 self.object_type,
2563 self.location,
2564 self.destination,
2565 );
2566 }
2567 }
2568}
2569
2570#[must_use = "a lease transfer future must be driven or dropped"]
2571pub struct TransferFuture<T: DistributedObject> {
2572 inner: RemoteFuture<()>,
2573 object: ObjectId,
2574 object_type: ObjectTypeId,
2575 location: ObjectLocation,
2576 destination: LocalityId,
2577 expires_at: Instant,
2578 source_client: RuntimeClient,
2579 marker: PhantomData<T>,
2580}
2581
2582impl<T: DistributedObject> TransferFuture<T> {
2583 pub(crate) fn new(
2584 inner: RemoteFuture<()>,
2585 object: ObjectId,
2586 object_type: ObjectTypeId,
2587 location: ObjectLocation,
2588 destination: LocalityId,
2589 expires_at: Instant,
2590 source_client: RuntimeClient,
2591 ) -> Self {
2592 Self {
2593 inner,
2594 object,
2595 object_type,
2596 location,
2597 destination,
2598 expires_at,
2599 source_client,
2600 marker: PhantomData,
2601 }
2602 }
2603}
2604
2605impl<T: DistributedObject> Future for TransferFuture<T> {
2606 type Output = Result<LeaseTransfer<T>, RuntimeError>;
2607
2608 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
2609 match Pin::new(&mut self.inner).poll(context) {
2610 Poll::Ready(Ok(())) => Poll::Ready(Ok(LeaseTransfer {
2611 object: self.object,
2612 object_type: self.object_type,
2613 location: self.location,
2614 destination: self.destination,
2615 expires_at: self.expires_at,
2616 source_client: self.source_client.clone(),
2617 active: true,
2618 marker: PhantomData,
2619 })),
2620 Poll::Ready(Err(error)) => Poll::Ready(Err(error)),
2621 Poll::Pending => Poll::Pending,
2622 }
2623 }
2624}
2625
2626impl<T: DistributedObject> Unpin for TransferFuture<T> {}
2627
2628pub(crate) fn encode_location(location: ObjectLocation) -> Result<Segments, ActionError> {
2629 let mut bytes = Vec::with_capacity(32);
2630 bytes.extend_from_slice(&location.locality().get().to_le_bytes());
2631 bytes.extend_from_slice(&location.domain().get().to_le_bytes());
2632 bytes.extend_from_slice(&location.slot().to_le_bytes());
2633 bytes.extend_from_slice(&location.generation().to_le_bytes());
2634 bytes.extend_from_slice(&location.epoch().to_le_bytes());
2635 Ok(vec![bytes])
2636}
2637
2638pub struct Remote<T: DistributedObject> {
2639 pub(crate) object: ObjectId,
2640 pub(crate) object_type: ObjectTypeId,
2641 pub(crate) location: ObjectLocation,
2642 pub(crate) client: RuntimeClient,
2643 pub(crate) lease: Arc<LocalLease>,
2644 marker: PhantomData<T>,
2645}
2646
2647impl<T: DistributedObject> Clone for Remote<T> {
2648 fn clone(&self) -> Self {
2649 Self {
2650 object: self.object,
2651 object_type: self.object_type,
2652 location: self.location,
2653 client: self.client.clone(),
2654 lease: Arc::clone(&self.lease),
2655 marker: PhantomData,
2656 }
2657 }
2658}
2659
2660impl<T: DistributedObject> std::fmt::Debug for Remote<T> {
2661 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2662 f.debug_struct("Remote")
2663 .field("object", &self.object)
2664 .field("object_type", &self.object_type)
2665 .finish_non_exhaustive()
2666 }
2667}
2668
2669impl<T: DistributedObject> Remote<T> {
2670 pub const fn object_id(&self) -> ObjectId {
2671 self.object
2672 }
2673
2674 pub fn observed_location(&self) -> ObjectLocation {
2675 self.client
2676 .resolve_object(self.object)
2677 .unwrap_or(self.location)
2678 }
2679
2680 pub fn release(self) {
2683 drop(self);
2684 }
2685
2686 pub fn call_read<A: ObjectReadAction<T>>(
2687 &self,
2688 action: A,
2689 ) -> Result<ObjectCallFuture<A::Output>, RuntimeError>
2690 where
2691 T: Sync,
2692 {
2693 self.client.call_object_read(self, action)
2694 }
2695
2696 pub fn call_write<A: ObjectWriteAction<T>>(
2697 &self,
2698 action: A,
2699 ) -> Result<ObjectCallFuture<A::Output>, RuntimeError> {
2700 self.client.call_object_write(self, action)
2701 }
2702
2703 pub fn transfer_to(&self, destination: LocalityId) -> Result<TransferFuture<T>, RuntimeError> {
2704 self.client.transfer_object(self, destination)
2705 }
2706
2707 pub fn migrate_to(&self, destination: Place) -> Result<MigrationFuture<T>, RuntimeError>
2713 where
2714 T: MobileObject,
2715 {
2716 self.client.migrate(self, destination)
2717 }
2718
2719 pub fn downgrade(&self) -> WeakRemote<T> {
2720 WeakRemote {
2721 object: self.object,
2722 object_type: self.object_type,
2723 location: self.lease.location,
2724 marker: PhantomData,
2725 }
2726 }
2727}
2728
2729#[derive(Debug, Eq, PartialEq)]
2730pub struct WeakRemote<T: DistributedObject> {
2731 pub(crate) object: ObjectId,
2732 pub(crate) object_type: ObjectTypeId,
2733 pub(crate) location: ObjectLocation,
2734 marker: PhantomData<T>,
2735}
2736
2737impl<T: DistributedObject> Copy for WeakRemote<T> {}
2738
2739impl<T: DistributedObject> Clone for WeakRemote<T> {
2740 fn clone(&self) -> Self {
2741 *self
2742 }
2743}
2744
2745impl<T: DistributedObject> WireValue for WeakRemote<T> {
2746 const SCHEMA_ID: u64 = T::SCHEMA_ID ^ 0x5745_414b_4f42_4a00;
2747
2748 fn encode(self) -> Result<Segments, ActionError> {
2749 let mut bytes = Vec::with_capacity(80);
2750 bytes.extend_from_slice(&self.object.run().get().to_le_bytes());
2751 bytes.extend_from_slice(&self.object.unique().to_le_bytes());
2752 bytes.extend_from_slice(&self.object_type.get().to_le_bytes());
2753 bytes.extend_from_slice(&self.location.locality().get().to_le_bytes());
2754 bytes.extend_from_slice(&self.location.domain().get().to_le_bytes());
2755 bytes.extend_from_slice(&self.location.slot().to_le_bytes());
2756 bytes.extend_from_slice(&self.location.generation().to_le_bytes());
2757 bytes.extend_from_slice(&self.location.epoch().to_le_bytes());
2758 Ok(vec![bytes])
2759 }
2760
2761 fn decode(mut segments: Segments) -> Result<Self, ActionError> {
2762 if segments.len() != 1 || segments[0].len() != 80 {
2763 return Err(ActionError::codec("invalid weak remote payload"));
2764 }
2765 let bytes = segments.pop().unwrap();
2766 let run = RunId::new(u128::from_le_bytes(bytes[0..16].try_into().unwrap()))
2767 .map_err(|_| ActionError::codec("invalid weak remote run"))?;
2768 let object = ObjectId::new(run, u128::from_le_bytes(bytes[16..32].try_into().unwrap()))
2769 .map_err(|_| ActionError::codec("invalid weak remote object"))?;
2770 let object_type = ObjectTypeId::new(u128::from_le_bytes(bytes[32..48].try_into().unwrap()))
2771 .map_err(|_| ActionError::codec("invalid weak remote type"))?;
2772 if object_type.get() != T::TYPE_ID {
2773 return Err(ActionError::codec("weak remote type mismatch"));
2774 }
2775 let location = ObjectLocation::new(
2776 LocalityId::new(u64::from_le_bytes(bytes[48..56].try_into().unwrap())),
2777 DomainId::new(u32::from_le_bytes(bytes[56..60].try_into().unwrap())),
2778 u64::from_le_bytes(bytes[60..68].try_into().unwrap()),
2779 u32::from_le_bytes(bytes[68..72].try_into().unwrap()),
2780 u64::from_le_bytes(bytes[72..80].try_into().unwrap()),
2781 )
2782 .map_err(|_| ActionError::codec("invalid weak remote location"))?;
2783 Ok(Self {
2784 object,
2785 object_type,
2786 location,
2787 marker: PhantomData,
2788 })
2789 }
2790}
2791
2792impl<T: DistributedObject> WeakRemote<T> {
2793 pub const fn object_id(self) -> ObjectId {
2794 self.object
2795 }
2796
2797 pub fn upgrade(&self, client: &RuntimeClient) -> Result<UpgradeFuture<T>, RuntimeError> {
2798 client.upgrade_object(self)
2799 }
2800}
2801
2802pub struct ObjectRoot<T: DistributedObject> {
2803 object: ObjectId,
2804 object_type: ObjectTypeId,
2805 location: ObjectLocation,
2806 client: RuntimeClient,
2807 active: bool,
2808 marker: PhantomData<T>,
2809}
2810
2811impl<T: DistributedObject> std::fmt::Debug for ObjectRoot<T> {
2812 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2813 f.debug_struct("ObjectRoot")
2814 .field("object", &self.object)
2815 .field("active", &self.active)
2816 .finish_non_exhaustive()
2817 }
2818}
2819
2820impl<T: DistributedObject> ObjectRoot<T> {
2821 pub(crate) fn new(remote: &Remote<T>) -> Self {
2822 Self {
2823 object: remote.object,
2824 object_type: remote.object_type,
2825 location: remote.location,
2826 client: remote.client.clone(),
2827 active: true,
2828 marker: PhantomData,
2829 }
2830 }
2831
2832 pub const fn object_id(&self) -> ObjectId {
2833 self.object
2834 }
2835
2836 pub fn release(mut self) {
2837 self.release_inner();
2838 }
2839
2840 fn release_inner(&mut self) {
2841 if self.active {
2842 self.active = false;
2843 self.client
2844 .release_root(self.object, self.object_type, self.location);
2845 }
2846 }
2847}
2848
2849impl<T: DistributedObject> Drop for ObjectRoot<T> {
2850 fn drop(&mut self) {
2851 self.release_inner();
2852 }
2853}
2854
2855#[must_use = "a rooted object creation future must be driven or dropped"]
2856pub struct RootedCreateFuture<T: DistributedObject> {
2857 inner: CreateFuture<T>,
2858}
2859
2860impl<T: DistributedObject> RootedCreateFuture<T> {
2861 pub(crate) fn new(inner: CreateFuture<T>) -> Self {
2862 Self { inner }
2863 }
2864}
2865
2866impl<T: DistributedObject> Future for RootedCreateFuture<T> {
2867 type Output = Result<(Remote<T>, ObjectRoot<T>), RuntimeError>;
2868
2869 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
2870 match Pin::new(&mut self.inner).poll(context) {
2871 Poll::Ready(Ok(remote)) => {
2872 let root = ObjectRoot::new(&remote);
2873 Poll::Ready(Ok((remote, root)))
2874 }
2875 Poll::Ready(Err(error)) => Poll::Ready(Err(error)),
2876 Poll::Pending => Poll::Pending,
2877 }
2878 }
2879}
2880
2881impl<T: DistributedObject> Unpin for RootedCreateFuture<T> {}
2882
2883#[cfg(test)]
2884mod tests;