Skip to main content

hataori_runtime/
object.rs

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