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