Skip to main content

hataori_runtime/
object.rs

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
71/// Logical object state that can be frozen and restored at another `Place`.
72pub trait MobileObject: DistributedObject {
73    const MOBILITY: Mobility;
74    const SNAPSHOT_VERSION: u32;
75    type Snapshot: WireValue;
76
77    /// Produces a versioned logical snapshot while runtime admission is closed.
78    ///
79    /// # Errors
80    /// Returns `ActionError` when logical state cannot be snapshotted.
81    fn freeze(&mut self) -> Result<Self::Snapshot, ActionError>;
82
83    /// Restores logical state and rebuilds destination-local resources.
84    ///
85    /// # Errors
86    /// Returns `ActionError` when the snapshot is invalid or a required
87    /// destination-local resource cannot be reconstructed.
88    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, &registration.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, &registration.snapshot_version.to_le_bytes());
801            hash_bytes(&mut hashes, &registration.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, &registration.input_schema.to_le_bytes());
806            hash_bytes(&mut hashes, &registration.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, &registration.object_type.get().to_le_bytes());
811            hash_bytes(&mut hashes, &registration.input_schema.to_le_bytes());
812            hash_bytes(&mut hashes, &registration.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    // `fetch_update` is deprecated on newer toolchains in favor of `try_update`,
1173    // which does not exist at the crate's MSRV (1.85).
1174    #[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    // `fetch_update` is deprecated on newer toolchains in favor of `try_update`,
1572    // which does not exist at the crate's MSRV (1.85).
1573    #[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    // `fetch_update` is deprecated on newer toolchains in favor of `try_update`,
1688    // which does not exist at the crate's MSRV (1.85).
1689    #[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    /// Releases this strong handle. The locality lease is released when this
2691    /// is the last local clone.
2692    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    /// Starts explicit migration to an exact logical place.
2718    ///
2719    /// # Errors
2720    /// Returns typed placement, mobility, snapshot, resource, transport,
2721    /// rollback, post-commit, or shutdown errors.
2722    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;