Skip to main content

hataori_runtime/
runtime.rs

1use crate::atomic::AtomicTryUpdate;
2use crate::{
3    action::{Action, ActionRegistry, ActionValue, Segments},
4    dedup::{DedupDisposition, DedupTable},
5    domain::{ActionCompletion, ActionJob, DomainConfig, DomainRegistry, DomainStats},
6    error::{cap, ResourceKind, RuntimeError, RuntimeState},
7    object::{
8        create_action_id, decode_location, encode_location, placement_action_id, ColocatedFuture,
9        CreateFuture, DistributedObject, LeaseTransfer, LocalLeaseManager, MigrationFuture,
10        MigrationPacket, MigrationReport, MobileObject, Mobility, ObjectCallFuture, ObjectEnvelope,
11        ObjectLimits, ObjectReadAction, ObjectRegistry, ObjectService, ObjectStats,
12        ObjectWriteAction, PlacementFallback, PlacementFuture, Remote, RemoteCallSpec,
13        RemoteObjectCall, Retirement, RootedCreateFuture, TransferFuture, UpgradeFuture,
14        WeakRemote, LEASE_ACQUIRE_ACTION_ID, LEASE_RELEASE_ACTION_ID, LEASE_RENEW_ACTION_ID,
15        MIGRATION_FREEZE_ACTION_ID, MIGRATION_HEADER_BYTES, MIGRATION_RETIRE_ACTION_ID,
16        ROOT_RELEASE_ACTION_ID, TRANSFER_ACK_ACTION_ID,
17    },
18    pending::{PendingEntry, PendingTable, Promise, RemoteFuture},
19    wire::{self, RuntimeMessage, RuntimeMessageKind},
20    WireValue,
21};
22use hataori_runtime_foundation::{
23    protocol::{
24        ActionId, Channel, DomainId, Hello, LocalityId, MessageId, ObjectId, ObjectTypeId, Parcel,
25        ParcelKind, ProtocolLimits, RequestId, RunId, RuntimeVersion, TraceId,
26    },
27    transport::{SendTicket, TransportDriver, TransportEvent, TransportHandle, TransportStats},
28};
29use std::{
30    collections::{BTreeMap, HashMap, VecDeque},
31    future::Future,
32    pin::pin,
33    sync::{
34        atomic::{AtomicU64, AtomicU8, AtomicUsize, Ordering},
35        mpsc::{self, Receiver, SyncSender, TryRecvError, TrySendError},
36        Arc, Mutex,
37    },
38    task::{Context, Poll, Wake, Waker},
39    time::{Duration, Instant},
40};
41
42const RUNTIME_VERSION: RuntimeVersion = RuntimeVersion {
43    major: 0,
44    minor: 3,
45    patch: 0,
46};
47
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub struct Place {
50    pub locality: LocalityId,
51    pub domain: DomainId,
52}
53
54impl Place {
55    pub const fn new(locality: LocalityId, domain: DomainId) -> Self {
56        Self { locality, domain }
57    }
58}
59
60#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61pub struct SpawnOptions {
62    pub deadline: Duration,
63    pub trace_id: Option<TraceId>,
64}
65
66#[derive(Clone, Copy, Debug, Eq, PartialEq)]
67pub struct RuntimeLimits {
68    pub max_pending_calls: usize,
69    pub max_domains: usize,
70    pub max_workers_per_domain: usize,
71    pub max_action_queue_per_domain: usize,
72    pub max_dedup_entries: usize,
73    pub max_dedup_bytes: usize,
74    pub max_progress_events: usize,
75    pub default_deadline: Duration,
76    pub dedup_ttl: Duration,
77    pub shutdown_timeout: Duration,
78}
79
80impl Default for RuntimeLimits {
81    fn default() -> Self {
82        Self {
83            max_pending_calls: 1024,
84            max_domains: 16,
85            max_workers_per_domain: 64,
86            max_action_queue_per_domain: 256,
87            max_dedup_entries: 1024,
88            max_dedup_bytes: 16 * 1024 * 1024,
89            max_progress_events: 256,
90            default_deadline: Duration::from_secs(30),
91            dedup_ttl: Duration::from_secs(60),
92            shutdown_timeout: Duration::from_secs(10),
93        }
94    }
95}
96
97impl RuntimeLimits {
98    pub fn validate(self) -> Result<Self, RuntimeError> {
99        if self.max_pending_calls == 0
100            || self.max_domains == 0
101            || self.max_workers_per_domain == 0
102            || self.max_action_queue_per_domain == 0
103            || self.max_dedup_entries == 0
104            || self.max_dedup_bytes == 0
105            || self.max_progress_events == 0
106            || self.default_deadline.is_zero()
107            || self.dedup_ttl.is_zero()
108            || self.shutdown_timeout.is_zero()
109        {
110            return Err(RuntimeError::InvalidLimits(
111                "runtime limits and durations must be nonzero",
112            ));
113        }
114        Ok(self)
115    }
116}
117
118#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
119pub struct RuntimeProgress {
120    pub events: usize,
121    pub made_progress: bool,
122}
123
124#[derive(Clone, Debug, Eq, PartialEq)]
125pub struct RuntimeStats {
126    pub state: RuntimeState,
127    pub pending_calls: usize,
128    pub completed_calls: u64,
129    pub cancelled_calls: u64,
130    pub timed_out_calls: u64,
131    pub late_results: u64,
132    pub duplicate_requests: u64,
133    pub action_failures: u64,
134    pub local_typed_dispatches: u64,
135    pub local_action_serializations: u64,
136    pub dedup_entries: usize,
137    pub dedup_bytes: usize,
138    pub sent_by_channel: [u64; 3],
139    pub received_by_channel: [u64; 3],
140    pub domains: Vec<(DomainId, DomainStats)>,
141    pub send_tickets: usize,
142    pub queued_responses: usize,
143    pub queued_response_bytes: usize,
144    pub objects: ObjectStats,
145    pub transport: TransportStats,
146}
147
148#[derive(Clone, Debug, Eq, PartialEq)]
149pub struct ShutdownReport {
150    pub stats: RuntimeStats,
151}
152
153pub struct RuntimeBuilder {
154    run_id: RunId,
155    protocol_limits: ProtocolLimits,
156    runtime_limits: RuntimeLimits,
157    registry: ActionRegistry,
158    object_registry: ObjectRegistry,
159    object_limits: ObjectLimits,
160    domains: Vec<DomainConfig>,
161    sealed: bool,
162}
163
164impl RuntimeBuilder {
165    pub fn object_limits(&mut self, limits: ObjectLimits) -> Result<&mut Self, RuntimeError> {
166        if self.sealed {
167            return Err(RuntimeError::BuilderSealed);
168        }
169        let limits = limits.validate()?;
170        if limits.max_snapshot_bytes
171            > self
172                .protocol_limits
173                .max_payload_bytes
174                .saturating_sub(MIGRATION_HEADER_BYTES)
175            || limits.max_snapshot_segments >= self.protocol_limits.max_segments
176        {
177            return Err(RuntimeError::InvalidLimits(
178                "migration snapshot exceeds protocol payload limits",
179            ));
180        }
181        self.object_limits = limits;
182        Ok(self)
183    }
184
185    pub fn register_object_exclusive<T: DistributedObject>(
186        &mut self,
187    ) -> Result<&mut Self, RuntimeError> {
188        if self.sealed {
189            return Err(RuntimeError::BuilderSealed);
190        }
191        let object_type =
192            ObjectTypeId::new(T::TYPE_ID).map_err(|_| RuntimeError::InvalidObjectTypeId)?;
193        let create = create_action_id(object_type)?;
194        if self.registry.get(create).is_some() {
195            return Err(RuntimeError::DuplicateAction(create));
196        }
197        self.object_registry.register_exclusive::<T>()?;
198        Ok(self)
199    }
200
201    pub fn register_object_read_write<T: DistributedObject + Sync>(
202        &mut self,
203    ) -> Result<&mut Self, RuntimeError> {
204        if self.sealed {
205            return Err(RuntimeError::BuilderSealed);
206        }
207        let object_type =
208            ObjectTypeId::new(T::TYPE_ID).map_err(|_| RuntimeError::InvalidObjectTypeId)?;
209        let create = create_action_id(object_type)?;
210        if self.registry.get(create).is_some() {
211            return Err(RuntimeError::DuplicateAction(create));
212        }
213        self.object_registry.register_read_write::<T>()?;
214        Ok(self)
215    }
216
217    /// Registers an exclusive migratable or reconstructible object type.
218    ///
219    /// # Errors
220    /// Returns a sealed-builder, duplicate ID/type, pinned mobility, invalid
221    /// schema, or registry collision error.
222    pub fn register_mobile_object_exclusive<T: MobileObject>(
223        &mut self,
224    ) -> Result<&mut Self, RuntimeError> {
225        if self.sealed {
226            return Err(RuntimeError::BuilderSealed);
227        }
228        let object_type =
229            ObjectTypeId::new(T::TYPE_ID).map_err(|_| RuntimeError::InvalidObjectTypeId)?;
230        let create = create_action_id(object_type)?;
231        if self.registry.get(create).is_some() {
232            return Err(RuntimeError::DuplicateAction(create));
233        }
234        self.object_registry.register_mobile_exclusive::<T>()?;
235        Ok(self)
236    }
237
238    /// Registers a read/write migratable or reconstructible object type.
239    ///
240    /// # Errors
241    /// Returns a sealed-builder, duplicate ID/type, pinned mobility, invalid
242    /// schema, or registry collision error.
243    pub fn register_mobile_object_read_write<T: MobileObject + Sync>(
244        &mut self,
245    ) -> Result<&mut Self, RuntimeError> {
246        if self.sealed {
247            return Err(RuntimeError::BuilderSealed);
248        }
249        let object_type =
250            ObjectTypeId::new(T::TYPE_ID).map_err(|_| RuntimeError::InvalidObjectTypeId)?;
251        let create = create_action_id(object_type)?;
252        if self.registry.get(create).is_some() {
253            return Err(RuntimeError::DuplicateAction(create));
254        }
255        self.object_registry.register_mobile_read_write::<T>()?;
256        Ok(self)
257    }
258
259    pub fn register_object_read<T, A>(&mut self) -> Result<&mut Self, RuntimeError>
260    where
261        T: DistributedObject + Sync,
262        A: ObjectReadAction<T>,
263    {
264        if self.sealed {
265            return Err(RuntimeError::BuilderSealed);
266        }
267        let id = ActionId::new(A::ID).map_err(|_| RuntimeError::InvalidActionId)?;
268        if self.registry.get(id).is_some() {
269            return Err(RuntimeError::DuplicateAction(id));
270        }
271        self.object_registry.register_read::<T, A>()?;
272        Ok(self)
273    }
274
275    pub fn register_object_write<T, A>(&mut self) -> Result<&mut Self, RuntimeError>
276    where
277        T: DistributedObject,
278        A: ObjectWriteAction<T>,
279    {
280        if self.sealed {
281            return Err(RuntimeError::BuilderSealed);
282        }
283        let id = ActionId::new(A::ID).map_err(|_| RuntimeError::InvalidActionId)?;
284        if self.registry.get(id).is_some() {
285            return Err(RuntimeError::DuplicateAction(id));
286        }
287        self.object_registry.register_write::<T, A>()?;
288        Ok(self)
289    }
290
291    pub fn register<A, F>(&mut self, handler: F) -> Result<&mut Self, RuntimeError>
292    where
293        A: Action,
294        F: Fn(A) -> Result<A::Output, crate::ActionError> + Send + Sync + 'static,
295    {
296        if self.sealed {
297            return Err(RuntimeError::BuilderSealed);
298        }
299        let id = ActionId::new(A::ID).map_err(|_| RuntimeError::InvalidActionId)?;
300        let placement = placement_action_id(id)?;
301        if self.object_registry.reserves_action(id)
302            || self.object_registry.reserves_action(placement)
303            || self.registry.get(placement).is_some()
304        {
305            return Err(RuntimeError::DuplicateAction(id));
306        }
307        self.registry.register::<A, F>(handler)?;
308        let handler = self.registry.get(id).cloned().unwrap();
309        self.object_registry.register_placement::<A>(handler)?;
310        Ok(self)
311    }
312
313    pub fn domain(&mut self, config: DomainConfig) -> Result<&mut Self, RuntimeError> {
314        if self.sealed {
315            return Err(RuntimeError::BuilderSealed);
316        }
317        if config.workers == 0
318            || config.workers > self.runtime_limits.max_workers_per_domain
319            || config.queue_capacity == 0
320            || config.queue_capacity > self.runtime_limits.max_action_queue_per_domain
321        {
322            return Err(RuntimeError::InvalidLimits("invalid domain configuration"));
323        }
324        if self.domains.len() >= self.runtime_limits.max_domains {
325            return Err(RuntimeError::InvalidLimits("too many domains"));
326        }
327        if self.domains.iter().any(|domain| domain.id == config.id) {
328            return Err(RuntimeError::InvalidLimits("duplicate domain id"));
329        }
330        self.domains.push(config);
331        Ok(self)
332    }
333
334    pub fn hello(&mut self) -> Result<Hello, RuntimeError> {
335        self.sealed = true;
336        Ok(Hello {
337            run_id: self.run_id,
338            runtime_version: RUNTIME_VERSION,
339            action_registry_hash: self.registry.fingerprint(),
340            object_registry_hash: self.object_registry.fingerprint(),
341            capabilities: 0b11,
342            limits: self.protocol_limits.validate().map_err(|error| {
343                RuntimeError::Protocol(format!("invalid protocol limits: {error}"))
344            })?,
345        })
346    }
347
348    pub fn start<H, D>(mut self, handle: H, driver: D) -> Result<Runtime, RuntimeError>
349    where
350        H: TransportHandle + 'static,
351        D: TransportDriver + 'static,
352    {
353        if !self.sealed {
354            return Err(RuntimeError::HandshakeNotPrepared);
355        }
356        let local_id = driver.local_id();
357        if !driver.members().contains(&local_id) {
358            return Err(RuntimeError::InvalidMembership(local_id));
359        }
360        if self.domains.is_empty() {
361            self.domains.push(DomainConfig {
362                queue_capacity: self.runtime_limits.max_action_queue_per_domain,
363                ..DomainConfig::default()
364            });
365        }
366        let objects = Arc::new(ObjectService::new(
367            self.run_id,
368            local_id,
369            self.object_limits,
370            self.object_registry.clone(),
371        ));
372        self.object_registry.install(&objects, &mut self.registry)?;
373        let completion_capacity = self
374            .runtime_limits
375            .max_pending_calls
376            .checked_add(self.runtime_limits.max_dedup_entries)
377            .ok_or(RuntimeError::InvalidLimits("completion capacity overflow"))?;
378        let domains = DomainRegistry::new(&self.domains, completion_capacity)?;
379        let domain_pools = domains.pools();
380        let (local_tx, local_rx) = mpsc::sync_channel(self.runtime_limits.max_pending_calls);
381        let state = Arc::new(AtomicU8::new(RuntimeState::Bootstrapping as u8));
382        let shared = Arc::new(Shared {
383            state: Arc::clone(&state),
384            local_id,
385            run_id: self.run_id,
386            limits: self.runtime_limits,
387            protocol_limits: self.protocol_limits,
388            transport: Arc::new(handle),
389            registry: Arc::new(self.registry),
390            objects: Arc::clone(&objects),
391            domain_pools,
392            local_leases: LocalLeaseManager::new(
393                self.object_limits.max_local_leases,
394                self.object_limits.lease_renew_interval,
395            ),
396            pending: Arc::new(PendingTable::new(self.runtime_limits.max_pending_calls)),
397            local_tx,
398            next_request: AtomicU64::new(1),
399            next_object: AtomicU64::new(1),
400            next_message: AtomicU64::new(1),
401            tickets: Mutex::new(HashMap::new()),
402            work_gate: Mutex::new(()),
403            local_object_calls: AtomicUsize::new(0),
404            counters: SharedCounters::default(),
405        });
406        state.store(RuntimeState::Running as u8, Ordering::Release);
407        Ok(Runtime {
408            shared,
409            driver: Box::new(driver),
410            domains,
411            local_rx,
412            dedup: DedupTable::new(
413                self.runtime_limits.max_dedup_entries,
414                self.runtime_limits.max_dedup_bytes,
415                self.runtime_limits.dedup_ttl,
416            ),
417            responses: VecDeque::new(),
418            response_bytes: 0,
419            objects,
420        })
421    }
422}
423
424#[derive(Default)]
425struct SharedCounters {
426    completed: AtomicU64,
427    cancelled: AtomicU64,
428    timed_out: AtomicU64,
429    late_results: AtomicU64,
430    duplicate_requests: AtomicU64,
431    action_failures: AtomicU64,
432    local_typed_dispatches: AtomicU64,
433    local_action_serializations: AtomicU64,
434    sent: [AtomicU64; 3],
435    received: [AtomicU64; 3],
436}
437
438struct LocalObjectCall<'a>(&'a AtomicUsize);
439
440impl Drop for LocalObjectCall<'_> {
441    fn drop(&mut self) {
442        self.0.fetch_sub(1, Ordering::AcqRel);
443    }
444}
445
446enum TicketPurpose {
447    Request(RequestId),
448    RequiredResponse,
449    BestEffort,
450}
451
452struct LocalRequest {
453    message: RuntimeMessage,
454    input: ActionValue,
455    cancel_token: Arc<std::sync::atomic::AtomicBool>,
456    trace_id: Option<TraceId>,
457}
458
459struct Shared {
460    state: Arc<AtomicU8>,
461    local_id: LocalityId,
462    run_id: RunId,
463    limits: RuntimeLimits,
464    protocol_limits: ProtocolLimits,
465    transport: Arc<dyn TransportHandle>,
466    registry: Arc<ActionRegistry>,
467    objects: Arc<ObjectService>,
468    domain_pools: BTreeMap<DomainId, Arc<rayon::ThreadPool>>,
469    local_leases: LocalLeaseManager,
470    pending: Arc<PendingTable>,
471    local_tx: SyncSender<LocalRequest>,
472    next_request: AtomicU64,
473    next_object: AtomicU64,
474    next_message: AtomicU64,
475    tickets: Mutex<HashMap<SendTicket, TicketPurpose>>,
476    work_gate: Mutex<()>,
477    local_object_calls: AtomicUsize,
478    counters: SharedCounters,
479}
480
481impl Shared {
482    fn state(&self) -> RuntimeState {
483        decode_state(self.state.load(Ordering::Acquire))
484    }
485
486    fn ensure_running(&self) -> Result<(), RuntimeError> {
487        let state = self.state();
488        if state == RuntimeState::Running {
489            Ok(())
490        } else if matches!(state, RuntimeState::Draining | RuntimeState::Stopped) {
491            Err(RuntimeError::Shutdown)
492        } else {
493            Err(RuntimeError::InvalidState {
494                expected: RuntimeState::Running,
495                actual: state,
496            })
497        }
498    }
499
500    // `fetch_update` is deprecated on newer toolchains in favor of `try_update`,
501    // which does not exist at the crate's MSRV (1.85).
502    #[allow(deprecated)]
503    fn next_request(&self) -> Result<RequestId, RuntimeError> {
504        let sequence = self
505            .next_request
506            .try_update_compat(Ordering::AcqRel, Ordering::Acquire, |value| {
507                value.checked_add(1).filter(|next| *next != 0)
508            })
509            .map_err(|_| RuntimeError::ResourceExhausted {
510                resource: ResourceKind::PendingCalls,
511                limit: self.limits.max_pending_calls,
512            })?;
513        RequestId::new(self.local_id, sequence)
514            .map_err(|_| RuntimeError::Protocol("request id overflow".into()))
515    }
516
517    // `fetch_update` is deprecated on newer toolchains in favor of `try_update`,
518    // which does not exist at the crate's MSRV (1.85).
519    #[allow(deprecated)]
520    fn next_object(&self) -> Result<ObjectId, RuntimeError> {
521        let sequence = self
522            .next_object
523            .try_update_compat(Ordering::AcqRel, Ordering::Acquire, |value| {
524                value.checked_add(1).filter(|next| *next != 0)
525            })
526            .map_err(|_| RuntimeError::ResourceExhausted {
527                resource: ResourceKind::Objects,
528                limit: self.objects.limits.max_objects,
529            })?;
530        let unique = (u128::from(self.local_id.get()) << 64) | u128::from(sequence);
531        ObjectId::new(self.run_id, unique)
532            .map_err(|_| RuntimeError::Protocol("object id overflow".into()))
533    }
534
535    // `fetch_update` is deprecated on newer toolchains in favor of `try_update`,
536    // which does not exist at the crate's MSRV (1.85).
537    #[allow(deprecated)]
538    fn next_message(&self) -> Result<MessageId, RuntimeError> {
539        let value = self
540            .next_message
541            .try_update_compat(Ordering::AcqRel, Ordering::Acquire, |value| {
542                value.checked_add(1).filter(|next| *next != 0)
543            })
544            .map_err(|_| RuntimeError::Protocol("message id overflow".into()))?;
545        MessageId::new(u128::from(value))
546            .map_err(|_| RuntimeError::Protocol("zero message id".into()))
547    }
548
549    fn send(
550        &self,
551        destination: LocalityId,
552        message: RuntimeMessage,
553        purpose: TicketPurpose,
554        trace_id: Option<TraceId>,
555    ) -> Result<SendTicket, RuntimeError> {
556        let channel = if message.kind == RuntimeMessageKind::Cancel {
557            Channel::Control
558        } else {
559            Channel::Action
560        };
561        let parcel_kind = if channel == Channel::Control {
562            ParcelKind::Control
563        } else {
564            ParcelKind::Data
565        };
566        let parcel = Parcel {
567            run_id: self.run_id,
568            message_id: self.next_message()?,
569            channel,
570            kind: parcel_kind,
571            source: self.local_id,
572            destination,
573            trace_id,
574            segments: wire::encode(message),
575        };
576        parcel.validate(self.protocol_limits).map_err(|error| {
577            RuntimeError::Protocol(format!("runtime parcel exceeds limits: {error}"))
578        })?;
579        let mut tickets = self.tickets.lock().unwrap();
580        let action_limit = self
581            .limits
582            .max_pending_calls
583            .saturating_add(self.limits.max_dedup_entries)
584            .saturating_add(self.limits.max_action_queue_per_domain);
585        let total_limit = action_limit.saturating_add(self.limits.max_pending_calls);
586        let limit = if channel == Channel::Control {
587            total_limit
588        } else {
589            action_limit
590        };
591        if tickets.len() >= limit {
592            return Err(RuntimeError::ResourceExhausted {
593                resource: ResourceKind::SendTickets,
594                limit,
595            });
596        }
597        let ticket = self.transport.try_send(parcel)?;
598        tickets.insert(ticket, purpose);
599        self.counters.sent[channel as usize].fetch_add(1, Ordering::Relaxed);
600        Ok(ticket)
601    }
602
603    fn cancel(&self, request: RequestId) {
604        let Some(entry) = self.pending.remove(request) else {
605            return;
606        };
607        self.counters.cancelled.fetch_add(1, Ordering::Relaxed);
608        if let Some(token) = entry.cancel_token {
609            token.store(true, Ordering::Release);
610        }
611        if entry.destination != self.local_id {
612            let message = RuntimeMessage {
613                kind: RuntimeMessageKind::Cancel,
614                request,
615                action: entry.action,
616                domain: entry.domain,
617                deadline_ms: 0,
618                payload: Vec::new(),
619            };
620            let _ = self.send(
621                entry.destination,
622                message,
623                TicketPurpose::BestEffort,
624                entry.trace_id,
625            );
626        }
627    }
628}
629
630#[derive(Clone)]
631pub struct RuntimeClient {
632    shared: Arc<Shared>,
633}
634
635impl std::fmt::Debug for RuntimeClient {
636    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
637        formatter
638            .debug_struct("RuntimeClient")
639            .field("local_id", &self.shared.local_id)
640            .field("state", &self.shared.state())
641            .finish_non_exhaustive()
642    }
643}
644
645impl RuntimeClient {
646    pub fn local_id(&self) -> LocalityId {
647        self.shared.local_id
648    }
649
650    pub fn spawn_on<A: Action>(
651        &self,
652        place: Place,
653        action: A,
654    ) -> Result<RemoteFuture<A::Output>, RuntimeError> {
655        self.spawn_on_with(
656            place,
657            action,
658            SpawnOptions {
659                deadline: self.shared.limits.default_deadline,
660                trace_id: None,
661            },
662        )
663    }
664
665    pub fn spawn_colocated<T: DistributedObject, A: Action>(
666        &self,
667        remote: &Remote<T>,
668        action: A,
669    ) -> Result<ColocatedFuture<A::Output>, RuntimeError> {
670        let location = self
671            .shared
672            .objects
673            .resolve(remote.object)
674            .unwrap_or(remote.location);
675        let action_id = ActionId::new(A::ID).map_err(|_| RuntimeError::InvalidActionId)?;
676        let action_payload = action
677            .encode()
678            .map_err(|error| RuntimeError::Protocol(format!("action encode failed: {error}")))?;
679        let payload = ObjectEnvelope {
680            object: remote.object,
681            object_type: remote.object_type,
682            epoch: location.epoch(),
683            lease_locality: self.shared.local_id,
684            domain: location.domain(),
685            rooted: false,
686        }
687        .encode(action_payload.clone());
688        let placed_action = placement_action_id(action_id)?;
689        let deadline = self.shared.limits.default_deadline;
690        let future = self.spawn_encoded(
691            Place::new(location.locality(), location.domain()),
692            placed_action,
693            payload,
694            SpawnOptions {
695                deadline,
696                trace_id: None,
697            },
698        )?;
699        Ok(ColocatedFuture::new(
700            RemoteObjectCall::new(
701                future,
702                self.clone(),
703                RemoteCallSpec {
704                    object: remote.object,
705                    object_type: remote.object_type,
706                    action: placed_action,
707                    payload: action_payload,
708                    last_epoch: location.epoch(),
709                    max_redirects: self.shared.objects.limits.max_redirects,
710                    deadline,
711                },
712            ),
713            Arc::clone(&remote.lease),
714        ))
715    }
716
717    pub fn spawn_preferred_colocated<T, A>(
718        &self,
719        remote: &Remote<T>,
720        fallback: PlacementFallback,
721        action: A,
722    ) -> Result<PlacementFuture<A::Output>, RuntimeError>
723    where
724        T: DistributedObject,
725        A: Action,
726    {
727        let location = self
728            .shared
729            .objects
730            .resolve(remote.object)
731            .unwrap_or(remote.location);
732        let action_id = ActionId::new(A::ID).map_err(|_| RuntimeError::InvalidActionId)?;
733        let payload = action
734            .encode()
735            .map_err(|error| RuntimeError::Protocol(format!("action encode failed: {error}")))?;
736        let options = SpawnOptions {
737            deadline: self.shared.limits.default_deadline,
738            trace_id: None,
739        };
740        let preferred_payload = ObjectEnvelope {
741            object: remote.object,
742            object_type: remote.object_type,
743            epoch: location.epoch(),
744            lease_locality: self.shared.local_id,
745            domain: location.domain(),
746            rooted: false,
747        }
748        .encode(payload.clone());
749        let placed_action = placement_action_id(action_id)?;
750        let preferred = self.spawn_encoded(
751            Place::new(location.locality(), location.domain()),
752            placed_action,
753            preferred_payload,
754            options,
755        )?;
756        let preferred = RemoteObjectCall::new(
757            preferred,
758            self.clone(),
759            RemoteCallSpec {
760                object: remote.object,
761                object_type: remote.object_type,
762                action: placed_action,
763                payload: payload.clone(),
764                last_epoch: location.epoch(),
765                max_redirects: self.shared.objects.limits.max_redirects,
766                deadline: options.deadline,
767            },
768        );
769        Ok(PlacementFuture::new(
770            ColocatedFuture::new(preferred, Arc::clone(&remote.lease)),
771            fallback,
772            self.clone(),
773            action_id,
774            payload,
775            options,
776        ))
777    }
778
779    pub fn spawn_on_with<A: Action>(
780        &self,
781        place: Place,
782        action: A,
783        options: SpawnOptions,
784    ) -> Result<RemoteFuture<A::Output>, RuntimeError> {
785        let action_id = ActionId::new(A::ID).map_err(|_| RuntimeError::InvalidActionId)?;
786        if place.locality == self.shared.local_id {
787            self.spawn_inner(
788                place,
789                action_id,
790                ActionValue::Typed(Box::new(action)),
791                options,
792                None,
793            )
794        } else {
795            let payload = action.encode().map_err(|error| {
796                RuntimeError::Protocol(format!("action encode failed: {error}"))
797            })?;
798            self.spawn_encoded(place, action_id, payload, options)
799        }
800    }
801
802    pub(crate) fn spawn_encoded<T: WireValue>(
803        &self,
804        place: Place,
805        action_id: ActionId,
806        payload: Segments,
807        options: SpawnOptions,
808    ) -> Result<RemoteFuture<T>, RuntimeError> {
809        self.spawn_inner(
810            place,
811            action_id,
812            ActionValue::Encoded(payload),
813            options,
814            None,
815        )
816    }
817
818    pub(crate) fn spawn_encoded_retry<T: WireValue>(
819        &self,
820        place: Place,
821        action_id: ActionId,
822        payload: Segments,
823        options: SpawnOptions,
824        request: RequestId,
825    ) -> Result<RemoteFuture<T>, RuntimeError> {
826        self.spawn_inner(
827            place,
828            action_id,
829            ActionValue::Encoded(payload),
830            options,
831            Some(request),
832        )
833    }
834
835    fn spawn_inner<T: WireValue>(
836        &self,
837        place: Place,
838        action_id: ActionId,
839        input: ActionValue,
840        options: SpawnOptions,
841        request: Option<RequestId>,
842    ) -> Result<RemoteFuture<T>, RuntimeError> {
843        self.shared.ensure_running()?;
844        if options.deadline.is_zero() {
845            return Err(RuntimeError::InvalidDeadline);
846        }
847        if self.shared.registry.get(action_id).is_none() {
848            return Err(RuntimeError::UnknownAction(action_id));
849        }
850        let local = place.locality == self.shared.local_id;
851        if !local && matches!(&input, ActionValue::Typed(_)) {
852            return Err(RuntimeError::Protocol(
853                "typed action cannot cross a locality boundary".into(),
854            ));
855        }
856        let deadline_ms = u64::try_from(options.deadline.as_millis().max(1))
857            .map_err(|_| RuntimeError::InvalidDeadline)?;
858        let _gate = self.shared.work_gate.lock().unwrap();
859        self.shared.ensure_running()?;
860        let request = match request {
861            Some(request) => request,
862            None => self.shared.next_request()?,
863        };
864        let promise = Arc::new(Promise::new());
865        let expires_at = Instant::now()
866            .checked_add(options.deadline)
867            .ok_or(RuntimeError::InvalidDeadline)?;
868        let cancel_token = (place.locality == self.shared.local_id)
869            .then(|| Arc::new(std::sync::atomic::AtomicBool::new(false)));
870        self.shared.pending.insert(
871            request,
872            PendingEntry {
873                promise: Arc::clone(&promise),
874                destination: place.locality,
875                action: action_id,
876                domain: place.domain,
877                trace_id: options.trace_id,
878                expires_at,
879                deadline: options.deadline,
880                cancel_token: cancel_token.clone(),
881            },
882        )?;
883        let mut input = Some(input);
884        let payload = if local {
885            Vec::new()
886        } else {
887            match input.take().unwrap() {
888                ActionValue::Encoded(payload) => payload,
889                ActionValue::Typed(_) => unreachable!("typed remote input rejected above"),
890            }
891        };
892        let message = RuntimeMessage {
893            kind: RuntimeMessageKind::Request,
894            request,
895            action: action_id,
896            domain: place.domain,
897            deadline_ms,
898            payload,
899        };
900        let submitted = if local {
901            self.shared
902                .local_tx
903                .try_send(LocalRequest {
904                    message,
905                    input: input.take().unwrap(),
906                    cancel_token: cancel_token.unwrap(),
907                    trace_id: options.trace_id,
908                })
909                .map_err(|error| match error {
910                    TrySendError::Full(_) => RuntimeError::ResourceExhausted {
911                        resource: ResourceKind::ActionQueue,
912                        limit: self.shared.limits.max_pending_calls,
913                    },
914                    TrySendError::Disconnected(_) => RuntimeError::Shutdown,
915                })
916                .map(|_| ())
917        } else {
918            self.shared
919                .send(
920                    place.locality,
921                    message,
922                    TicketPurpose::Request(request),
923                    options.trace_id,
924                )
925                .map(|_| ())
926        };
927        if let Err(error) = submitted {
928            self.shared.pending.remove(request);
929            return Err(error);
930        }
931        let shared = Arc::clone(&self.shared);
932        Ok(RemoteFuture::new(
933            request,
934            promise,
935            Arc::new(move |request| shared.cancel(request)),
936        ))
937    }
938
939    pub fn create_at<T: DistributedObject>(
940        &self,
941        place: Place,
942        state: T,
943    ) -> Result<CreateFuture<T>, RuntimeError> {
944        self.create_at_inner(place, state, false)
945    }
946
947    pub fn create_rooted_at<T: DistributedObject>(
948        &self,
949        place: Place,
950        state: T,
951    ) -> Result<RootedCreateFuture<T>, RuntimeError> {
952        Ok(RootedCreateFuture::new(
953            self.create_at_inner(place, state, true)?,
954        ))
955    }
956
957    fn create_at_inner<T: DistributedObject>(
958        &self,
959        place: Place,
960        state: T,
961        rooted: bool,
962    ) -> Result<CreateFuture<T>, RuntimeError> {
963        let object_type =
964            ObjectTypeId::new(T::TYPE_ID).map_err(|_| RuntimeError::InvalidObjectTypeId)?;
965        let object = self.shared.next_object()?;
966        if place.locality == self.shared.local_id {
967            let location = self.shared.objects.create_local(
968                object,
969                object_type,
970                place.domain,
971                state,
972                self.shared.local_id,
973                rooted,
974            )?;
975            let remote = self.attach_object(object, object_type, location);
976            return Ok(CreateFuture::ready(
977                remote,
978                object,
979                object_type,
980                self.clone(),
981            ));
982        }
983        let payload = state
984            .encode()
985            .map_err(|error| RuntimeError::Protocol(format!("object encode failed: {error}")))?;
986        let payload = ObjectEnvelope {
987            object,
988            object_type,
989            epoch: 1,
990            lease_locality: self.shared.local_id,
991            domain: place.domain,
992            rooted,
993        }
994        .encode(payload);
995        let future = self.spawn_encoded::<Vec<u8>>(
996            place,
997            create_action_id(object_type)?,
998            payload,
999            SpawnOptions {
1000                deadline: self.shared.limits.default_deadline,
1001                trace_id: None,
1002            },
1003        )?;
1004        Ok(CreateFuture::new(future, object, object_type, self.clone()))
1005    }
1006
1007    fn run_local_object<O: Send>(
1008        &self,
1009        domain: DomainId,
1010        call: impl FnOnce() -> Result<O, RuntimeError> + Send,
1011    ) -> Result<O, RuntimeError> {
1012        let gate = self.shared.work_gate.lock().unwrap();
1013        self.shared.ensure_running()?;
1014        self.shared
1015            .local_object_calls
1016            .fetch_add(1, Ordering::AcqRel);
1017        let _active = LocalObjectCall(&self.shared.local_object_calls);
1018        let pool = self
1019            .shared
1020            .domain_pools
1021            .get(&domain)
1022            .cloned()
1023            .ok_or(RuntimeError::UnknownDomain(domain))?;
1024        drop(gate);
1025        pool.install(call)
1026    }
1027
1028    pub fn call_object_read<T, A>(
1029        &self,
1030        remote: &Remote<T>,
1031        action: A,
1032    ) -> Result<ObjectCallFuture<A::Output>, RuntimeError>
1033    where
1034        T: DistributedObject + Sync,
1035        A: ObjectReadAction<T>,
1036    {
1037        let location = self
1038            .shared
1039            .objects
1040            .resolve(remote.object)
1041            .unwrap_or(remote.location);
1042        if location.locality() == self.shared.local_id
1043            && self
1044                .shared
1045                .objects
1046                .is_local_resident(remote.object, location)
1047        {
1048            let object = remote.object;
1049            let object_type = remote.object_type;
1050            let objects = Arc::clone(&self.shared.objects);
1051            return Ok(ObjectCallFuture::local(
1052                self.run_local_object(location.domain(), move || {
1053                    objects.call_read_local(object, object_type, location, action)
1054                }),
1055            ));
1056        }
1057        let payload = action.encode().map_err(|error| {
1058            RuntimeError::Protocol(format!("object action encode failed: {error}"))
1059        })?;
1060        self.call_object(remote, A::ID, payload)
1061            .map(ObjectCallFuture::remote)
1062    }
1063
1064    pub fn call_object_write<T, A>(
1065        &self,
1066        remote: &Remote<T>,
1067        action: A,
1068    ) -> Result<ObjectCallFuture<A::Output>, RuntimeError>
1069    where
1070        T: DistributedObject,
1071        A: ObjectWriteAction<T>,
1072    {
1073        let location = self
1074            .shared
1075            .objects
1076            .resolve(remote.object)
1077            .unwrap_or(remote.location);
1078        if location.locality() == self.shared.local_id
1079            && self
1080                .shared
1081                .objects
1082                .is_local_resident(remote.object, location)
1083        {
1084            let object = remote.object;
1085            let object_type = remote.object_type;
1086            let objects = Arc::clone(&self.shared.objects);
1087            return Ok(ObjectCallFuture::local(
1088                self.run_local_object(location.domain(), move || {
1089                    objects.call_write_local(object, object_type, location, action)
1090                }),
1091            ));
1092        }
1093        let payload = action.encode().map_err(|error| {
1094            RuntimeError::Protocol(format!("object action encode failed: {error}"))
1095        })?;
1096        self.call_object(remote, A::ID, payload)
1097            .map(ObjectCallFuture::remote)
1098    }
1099
1100    fn call_object<T: DistributedObject, O: WireValue>(
1101        &self,
1102        remote: &Remote<T>,
1103        raw_action: u128,
1104        action_payload: Segments,
1105    ) -> Result<RemoteObjectCall<O>, RuntimeError> {
1106        let action = ActionId::new(raw_action).map_err(|_| RuntimeError::InvalidActionId)?;
1107        let location = self
1108            .shared
1109            .objects
1110            .resolve(remote.object)
1111            .unwrap_or(remote.location);
1112        let payload = ObjectEnvelope {
1113            object: remote.object,
1114            object_type: remote.object_type,
1115            epoch: location.epoch(),
1116            lease_locality: self.shared.local_id,
1117            domain: location.domain(),
1118            rooted: false,
1119        }
1120        .encode(action_payload.clone());
1121        let deadline = self.shared.limits.default_deadline;
1122        let future = self.spawn_encoded(
1123            Place::new(location.locality(), location.domain()),
1124            action,
1125            payload,
1126            SpawnOptions {
1127                deadline,
1128                trace_id: None,
1129            },
1130        )?;
1131        Ok(RemoteObjectCall::new(
1132            future,
1133            self.clone(),
1134            RemoteCallSpec {
1135                object: remote.object,
1136                object_type: remote.object_type,
1137                action,
1138                payload: action_payload,
1139                last_epoch: location.epoch(),
1140                max_redirects: self.shared.objects.limits.max_redirects,
1141                deadline,
1142            },
1143        ))
1144    }
1145
1146    pub(crate) fn resolve_object(
1147        &self,
1148        object: ObjectId,
1149    ) -> Option<hataori_runtime_foundation::protocol::ObjectLocation> {
1150        self.shared.objects.resolve(object)
1151    }
1152
1153    pub(crate) fn cache_object_location(
1154        &self,
1155        object: ObjectId,
1156        location: hataori_runtime_foundation::protocol::ObjectLocation,
1157    ) {
1158        self.shared.objects.cache_location(object, location);
1159    }
1160
1161    pub fn clear_object_resolver(&self) {
1162        self.shared.objects.clear_resolver();
1163    }
1164
1165    /// Starts explicit migration to an exact logical place.
1166    ///
1167    /// # Errors
1168    /// Returns typed placement, mobility, snapshot, resource, transport,
1169    /// rollback, post-commit, or shutdown errors.
1170    pub fn migrate<T: MobileObject>(
1171        &self,
1172        remote: &Remote<T>,
1173        destination: Place,
1174    ) -> Result<MigrationFuture<T>, RuntimeError> {
1175        {
1176            let _gate = self.shared.work_gate.lock().unwrap();
1177            self.shared.ensure_running()?;
1178        }
1179        let from = self
1180            .shared
1181            .objects
1182            .resolve(remote.object)
1183            .unwrap_or(remote.location);
1184        if from.locality() == destination.locality && from.domain() == destination.domain {
1185            return Ok(MigrationFuture::ready(
1186                MigrationReport {
1187                    object: remote.object,
1188                    from,
1189                    to: from,
1190                    snapshot_bytes: 0,
1191                },
1192                remote.object_type,
1193            ));
1194        }
1195        let (mobility, snapshot_version, snapshot_schema) =
1196            self.shared.objects.migration_metadata(remote.object_type)?;
1197        if mobility == Mobility::Pinned {
1198            return Err(RuntimeError::PinnedObjectType(remote.object_type));
1199        }
1200        let migration = self.shared.next_request()?;
1201        self.shared.objects.begin_migration(migration)?;
1202        let packet = MigrationPacket {
1203            migration,
1204            object: remote.object,
1205            object_type: remote.object_type,
1206            authority: remote.lease.location.locality(),
1207            from,
1208            destination,
1209            snapshot_version,
1210            snapshot_schema,
1211            snapshot: Vec::new(),
1212        };
1213        let future = self.migration_step(
1214            Place::new(from.locality(), from.domain()),
1215            MIGRATION_FREEZE_ACTION_ID,
1216            packet.clone(),
1217        );
1218        match future {
1219            Ok(future) => Ok(MigrationFuture::new(
1220                self.clone(),
1221                Arc::clone(&self.shared.objects),
1222                Arc::clone(&remote.lease),
1223                packet,
1224                remote.lease.location,
1225                future,
1226            )),
1227            Err(error) => {
1228                self.shared.objects.finish_migration(
1229                    migration,
1230                    crate::object::MigrationOutcome::RolledBack,
1231                    0,
1232                );
1233                Err(error)
1234            }
1235        }
1236    }
1237
1238    pub(crate) fn migration_step(
1239        &self,
1240        place: Place,
1241        action: u128,
1242        packet: MigrationPacket,
1243    ) -> Result<RemoteFuture<MigrationPacket>, RuntimeError> {
1244        self.spawn_encoded(
1245            place,
1246            ActionId::new(action).map_err(|_| RuntimeError::InvalidActionId)?,
1247            packet
1248                .encode()
1249                .map_err(|error| RuntimeError::ObjectAction(error.message()))?,
1250            SpawnOptions {
1251                deadline: self.shared.limits.default_deadline,
1252                trace_id: None,
1253            },
1254        )
1255    }
1256
1257    pub(crate) fn fail_committed_migration(&self) {
1258        self.shared
1259            .state
1260            .store(RuntimeState::Failed as u8, Ordering::Release);
1261    }
1262
1263    pub fn transfer_object<T: DistributedObject>(
1264        &self,
1265        remote: &Remote<T>,
1266        destination: LocalityId,
1267    ) -> Result<TransferFuture<T>, RuntimeError> {
1268        let payload = ObjectEnvelope {
1269            object: remote.object,
1270            object_type: remote.object_type,
1271            epoch: remote.location.epoch(),
1272            lease_locality: destination,
1273            domain: remote.location.domain(),
1274            rooted: true,
1275        }
1276        .encode(Vec::new());
1277        let inner = self.spawn_encoded::<()>(
1278            Place::new(remote.location.locality(), remote.location.domain()),
1279            ActionId::new(LEASE_ACQUIRE_ACTION_ID).unwrap(),
1280            payload,
1281            SpawnOptions {
1282                deadline: self.shared.limits.default_deadline,
1283                trace_id: None,
1284            },
1285        )?;
1286        Ok(TransferFuture::new(
1287            inner,
1288            remote.object,
1289            remote.object_type,
1290            remote.location,
1291            destination,
1292            Instant::now() + self.shared.objects.limits.lease_ttl,
1293            self.clone(),
1294        ))
1295    }
1296
1297    pub fn import_transfer<T: DistributedObject>(
1298        &self,
1299        mut transfer: LeaseTransfer<T>,
1300    ) -> Result<Remote<T>, RuntimeError> {
1301        if transfer.destination != self.shared.local_id {
1302            return Err(RuntimeError::InvalidMembership(transfer.destination));
1303        }
1304        if transfer.expires_at <= Instant::now() {
1305            return Err(RuntimeError::LeaseTransferExpired(transfer.object));
1306        }
1307        let remote =
1308            self.attach_object(transfer.object, transfer.object_type, transfer.location)?;
1309        transfer.active = false;
1310        self.object_control(
1311            transfer.object,
1312            transfer.object_type,
1313            transfer.location,
1314            transfer.destination,
1315            TRANSFER_ACK_ACTION_ID,
1316        );
1317        Ok(remote)
1318    }
1319
1320    pub fn upgrade_object<T: DistributedObject>(
1321        &self,
1322        weak: &WeakRemote<T>,
1323    ) -> Result<UpgradeFuture<T>, RuntimeError> {
1324        let payload = ObjectEnvelope {
1325            object: weak.object,
1326            object_type: weak.object_type,
1327            epoch: weak.location.epoch(),
1328            lease_locality: self.shared.local_id,
1329            domain: weak.location.domain(),
1330            rooted: false,
1331        }
1332        .encode(Vec::new());
1333        let inner = self.spawn_encoded::<()>(
1334            Place::new(weak.location.locality(), weak.location.domain()),
1335            ActionId::new(LEASE_ACQUIRE_ACTION_ID).unwrap(),
1336            payload,
1337            SpawnOptions {
1338                deadline: self.shared.limits.default_deadline,
1339                trace_id: None,
1340            },
1341        )?;
1342        Ok(UpgradeFuture::new(
1343            inner,
1344            weak.object,
1345            weak.object_type,
1346            weak.location,
1347            self.clone(),
1348        ))
1349    }
1350
1351    pub(crate) fn attach_object<T: DistributedObject>(
1352        &self,
1353        object: ObjectId,
1354        object_type: ObjectTypeId,
1355        location: hataori_runtime_foundation::protocol::ObjectLocation,
1356    ) -> Result<Remote<T>, RuntimeError> {
1357        self.shared.objects.cache_location(object, location);
1358        self.shared
1359            .local_leases
1360            .attach(object, object_type, location, self.clone())
1361    }
1362
1363    pub(crate) fn renew_object(
1364        &self,
1365        object: ObjectId,
1366        object_type: ObjectTypeId,
1367        location: hataori_runtime_foundation::protocol::ObjectLocation,
1368    ) {
1369        self.object_control(
1370            object,
1371            object_type,
1372            location,
1373            self.shared.local_id,
1374            LEASE_RENEW_ACTION_ID,
1375        );
1376    }
1377
1378    pub(crate) fn release_object(
1379        &self,
1380        object: ObjectId,
1381        object_type: ObjectTypeId,
1382        location: hataori_runtime_foundation::protocol::ObjectLocation,
1383    ) {
1384        self.object_control(
1385            object,
1386            object_type,
1387            location,
1388            self.shared.local_id,
1389            LEASE_RELEASE_ACTION_ID,
1390        );
1391    }
1392
1393    pub(crate) fn release_root(
1394        &self,
1395        object: ObjectId,
1396        object_type: ObjectTypeId,
1397        location: hataori_runtime_foundation::protocol::ObjectLocation,
1398    ) {
1399        self.object_control(
1400            object,
1401            object_type,
1402            location,
1403            self.shared.local_id,
1404            ROOT_RELEASE_ACTION_ID,
1405        );
1406    }
1407
1408    pub(crate) fn release_for_locality(
1409        &self,
1410        object: ObjectId,
1411        object_type: ObjectTypeId,
1412        location: hataori_runtime_foundation::protocol::ObjectLocation,
1413        locality: LocalityId,
1414    ) {
1415        self.object_control(
1416            object,
1417            object_type,
1418            location,
1419            locality,
1420            LEASE_RELEASE_ACTION_ID,
1421        );
1422    }
1423
1424    fn object_control(
1425        &self,
1426        object: ObjectId,
1427        object_type: ObjectTypeId,
1428        location: hataori_runtime_foundation::protocol::ObjectLocation,
1429        lease_locality: LocalityId,
1430        raw_action: u128,
1431    ) {
1432        if location.locality() == self.shared.local_id {
1433            match raw_action {
1434                LEASE_RENEW_ACTION_ID => {
1435                    self.shared
1436                        .objects
1437                        .renew(object, lease_locality, Instant::now());
1438                }
1439                ROOT_RELEASE_ACTION_ID => {
1440                    self.shared.objects.release_root(object);
1441                }
1442                TRANSFER_ACK_ACTION_ID => {
1443                    self.shared.objects.ack_transfer(object, lease_locality);
1444                }
1445                _ => {
1446                    self.shared.objects.release(object, lease_locality);
1447                }
1448            }
1449            return;
1450        }
1451        let Ok(request) = self.shared.next_request() else {
1452            return;
1453        };
1454        let Ok(action) = ActionId::new(raw_action) else {
1455            return;
1456        };
1457        let message = RuntimeMessage {
1458            kind: RuntimeMessageKind::Request,
1459            request,
1460            action,
1461            domain: location.domain(),
1462            deadline_ms: u64::try_from(self.shared.limits.default_deadline.as_millis().max(1))
1463                .unwrap_or(u64::MAX),
1464            payload: ObjectEnvelope {
1465                object,
1466                object_type,
1467                epoch: location.epoch(),
1468                lease_locality,
1469                domain: location.domain(),
1470                rooted: false,
1471            }
1472            .encode(Vec::new()),
1473        };
1474        let _ = self.shared.send(
1475            location.locality(),
1476            message,
1477            TicketPurpose::BestEffort,
1478            None,
1479        );
1480    }
1481
1482    pub(crate) fn cancel(&self, request: RequestId) {
1483        self.shared.cancel(request);
1484    }
1485}
1486
1487struct QueuedResponse {
1488    destination: LocalityId,
1489    message: RuntimeMessage,
1490    trace_id: Option<TraceId>,
1491    cache: bool,
1492    bytes: usize,
1493}
1494
1495pub struct Runtime {
1496    shared: Arc<Shared>,
1497    driver: Box<dyn TransportDriver>,
1498    domains: DomainRegistry,
1499    local_rx: Receiver<LocalRequest>,
1500    dedup: DedupTable,
1501    responses: VecDeque<QueuedResponse>,
1502    response_bytes: usize,
1503    objects: Arc<ObjectService>,
1504}
1505
1506impl std::fmt::Debug for Runtime {
1507    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1508        let stats = self.stats();
1509        formatter
1510            .debug_struct("Runtime")
1511            .field("local_id", &self.shared.local_id)
1512            .field("state", &stats.state)
1513            .field("pending_calls", &stats.pending_calls)
1514            .field("dedup_entries", &stats.dedup_entries)
1515            .field("send_tickets", &stats.send_tickets)
1516            .field("queued_responses", &stats.queued_responses)
1517            .field("queued_response_bytes", &stats.queued_response_bytes)
1518            .finish_non_exhaustive()
1519    }
1520}
1521
1522impl Runtime {
1523    pub fn builder(
1524        run_id: RunId,
1525        protocol_limits: ProtocolLimits,
1526        runtime_limits: RuntimeLimits,
1527    ) -> Result<RuntimeBuilder, RuntimeError> {
1528        protocol_limits
1529            .validate()
1530            .map_err(|error| RuntimeError::Protocol(format!("invalid protocol limits: {error}")))?;
1531        let largest_runtime_frame = protocol_limits
1532            .max_payload_bytes
1533            .checked_add(protocol_limits.max_segments.saturating_mul(8))
1534            .and_then(|bytes| bytes.checked_add(128))
1535            .ok_or(RuntimeError::InvalidLimits(
1536                "runtime frame capacity overflow",
1537            ))?;
1538        let action_byte_limit = protocol_limits
1539            .max_inflight_bytes_per_peer
1540            .checked_sub(protocol_limits.control_reserved_bytes_per_peer)
1541            .ok_or(RuntimeError::InvalidLimits(
1542                "control reservation exceeds in-flight capacity",
1543            ))?;
1544        if largest_runtime_frame > action_byte_limit {
1545            return Err(RuntimeError::InvalidLimits(
1546                "one maximum runtime parcel must fit the action byte reservation",
1547            ));
1548        }
1549        let mut object_limits = ObjectLimits::default();
1550        object_limits.max_snapshot_bytes = object_limits.max_snapshot_bytes.min(
1551            protocol_limits
1552                .max_payload_bytes
1553                .saturating_sub(MIGRATION_HEADER_BYTES),
1554        );
1555        object_limits.max_snapshot_segments = object_limits
1556            .max_snapshot_segments
1557            .min(protocol_limits.max_segments.saturating_sub(1));
1558        let object_limits = object_limits.validate()?;
1559        Ok(RuntimeBuilder {
1560            run_id,
1561            protocol_limits,
1562            runtime_limits: runtime_limits.validate()?,
1563            registry: ActionRegistry::default(),
1564            object_registry: ObjectRegistry::default(),
1565            object_limits,
1566            domains: Vec::new(),
1567            sealed: false,
1568        })
1569    }
1570
1571    pub fn client(&self) -> RuntimeClient {
1572        RuntimeClient {
1573            shared: Arc::clone(&self.shared),
1574        }
1575    }
1576
1577    pub fn local_id(&self) -> LocalityId {
1578        self.shared.local_id
1579    }
1580
1581    pub fn localities(&self) -> &[LocalityId] {
1582        self.driver.members()
1583    }
1584
1585    pub fn state(&self) -> RuntimeState {
1586        self.shared.state()
1587    }
1588
1589    pub fn spawn_on<A: Action>(
1590        &self,
1591        place: Place,
1592        action: A,
1593    ) -> Result<RemoteFuture<A::Output>, RuntimeError> {
1594        self.client().spawn_on(place, action)
1595    }
1596
1597    pub fn spawn_on_with<A: Action>(
1598        &self,
1599        place: Place,
1600        action: A,
1601        options: SpawnOptions,
1602    ) -> Result<RemoteFuture<A::Output>, RuntimeError> {
1603        self.client().spawn_on_with(place, action, options)
1604    }
1605
1606    /// Creates a pinned object at an exact logical place.
1607    ///
1608    /// # Errors
1609    /// Returns a typed registry, placement, codec, resource, transport, or
1610    /// shutdown error before retaining an unowned object.
1611    pub fn create_at<T: DistributedObject>(
1612        &self,
1613        place: Place,
1614        state: T,
1615    ) -> Result<CreateFuture<T>, RuntimeError> {
1616        self.client().create_at(place, state)
1617    }
1618
1619    /// Creates an exact pinned object and one runtime-owned root.
1620    ///
1621    /// # Errors
1622    /// Returns a typed registry, placement, codec, resource, transport, or
1623    /// shutdown error before retaining an unowned root.
1624    pub fn create_rooted_at<T: DistributedObject>(
1625        &self,
1626        place: Place,
1627        state: T,
1628    ) -> Result<RootedCreateFuture<T>, RuntimeError> {
1629        self.client().create_rooted_at(place, state)
1630    }
1631
1632    /// Spawns an independent action at an object's pinned location.
1633    ///
1634    /// # Errors
1635    /// Returns a typed resolver, placement-ticket, queue, transport, codec, or
1636    /// shutdown error.
1637    pub fn spawn_colocated<T: DistributedObject, A: Action>(
1638        &self,
1639        remote: &Remote<T>,
1640        action: A,
1641    ) -> Result<ColocatedFuture<A::Output>, RuntimeError> {
1642        self.client().spawn_colocated(remote, action)
1643    }
1644
1645    pub fn clear_object_resolver(&self) {
1646        self.shared.objects.clear_resolver();
1647    }
1648
1649    /// Starts explicit migration to an exact logical place.
1650    ///
1651    /// # Errors
1652    /// Returns typed placement, mobility, snapshot, resource, transport,
1653    /// rollback, post-commit, or shutdown errors.
1654    pub fn migrate<T: MobileObject>(
1655        &self,
1656        remote: &Remote<T>,
1657        destination: Place,
1658    ) -> Result<MigrationFuture<T>, RuntimeError> {
1659        self.client().migrate(remote, destination)
1660    }
1661
1662    fn send_retirement(&self, retirement: &Retirement) -> Result<(), RuntimeError> {
1663        let request = self.shared.next_request()?;
1664        let packet = MigrationPacket {
1665            migration: request,
1666            object: retirement.object,
1667            object_type: retirement.object_type,
1668            authority: retirement.authority,
1669            from: retirement.location,
1670            destination: Place::new(retirement.location.locality(), retirement.location.domain()),
1671            snapshot_version: retirement.snapshot_version,
1672            snapshot_schema: retirement.snapshot_schema,
1673            snapshot: Vec::new(),
1674        };
1675        let payload = match packet.encode() {
1676            Ok(payload) => payload,
1677            Err(error) => return Err(RuntimeError::ObjectAction(error.message())),
1678        };
1679        let message = RuntimeMessage {
1680            kind: RuntimeMessageKind::Request,
1681            request,
1682            action: ActionId::new(MIGRATION_RETIRE_ACTION_ID).unwrap(),
1683            domain: retirement.location.domain(),
1684            deadline_ms: u64::try_from(self.shared.limits.default_deadline.as_millis().max(1))
1685                .unwrap_or(u64::MAX),
1686            payload,
1687        };
1688        self.shared
1689            .send(
1690                retirement.location.locality(),
1691                message,
1692                TicketPurpose::BestEffort,
1693                None,
1694            )
1695            .map(|_| ())
1696    }
1697
1698    pub fn progress(&mut self, max_events: usize) -> Result<RuntimeProgress, RuntimeError> {
1699        let state = self.state();
1700        if !matches!(state, RuntimeState::Running | RuntimeState::Draining) {
1701            return Err(RuntimeError::InvalidState {
1702                expected: RuntimeState::Running,
1703                actual: state,
1704            });
1705        }
1706        let limit = max_events.min(self.shared.limits.max_progress_events);
1707        if limit == 0 {
1708            return Ok(RuntimeProgress::default());
1709        }
1710        self.expire_pending();
1711        self.dedup.expire(Instant::now());
1712        let now = Instant::now();
1713        for lease in self.shared.local_leases.due(now) {
1714            lease.renew();
1715        }
1716        self.objects.expire(now);
1717        for retirement in self.objects.take_retirements(limit) {
1718            let object = retirement.object;
1719            match self.send_retirement(&retirement) {
1720                Ok(()) => self.objects.finish_retirement(object),
1721                Err(_) => {
1722                    self.objects.requeue_retirement(retirement);
1723                    break;
1724                }
1725            }
1726        }
1727        let mut events = Vec::new();
1728        let transport = self.driver.progress(&mut events, limit).map_err(|error| {
1729            self.fail(RuntimeError::Transport(error.clone()));
1730            RuntimeError::Transport(error)
1731        })?;
1732        let mut handled = 0;
1733        for event in events {
1734            if let Err(error) = self.handle_transport_event(event) {
1735                self.fail(error.clone());
1736                return Err(error);
1737            }
1738            handled += 1;
1739        }
1740        while handled < limit {
1741            let Some(completion) = self.domains.try_completion() else {
1742                break;
1743            };
1744            if let Err(error) = self.handle_completion(completion) {
1745                self.fail(error.clone());
1746                return Err(error);
1747            }
1748            handled += 1;
1749        }
1750        if handled < limit {
1751            let ready = self
1752                .objects
1753                .retry_ready(limit - handled, |domain| self.domains.can_submit(domain));
1754            for job in ready {
1755                self.domains.submit(job)?;
1756                handled += 1;
1757            }
1758        }
1759        while handled < limit {
1760            match self.local_rx.try_recv() {
1761                Ok(request) => {
1762                    self.dispatch_local(request)?;
1763                    handled += 1;
1764                }
1765                Err(TryRecvError::Empty | TryRecvError::Disconnected) => break,
1766            }
1767        }
1768        let sent = match self.drain_responses(limit - handled) {
1769            Ok(sent) => sent,
1770            Err(error) => {
1771                self.fail(error.clone());
1772                return Err(error);
1773            }
1774        };
1775        handled += sent;
1776        Ok(RuntimeProgress {
1777            events: handled,
1778            made_progress: transport.made_progress || handled > 0,
1779        })
1780    }
1781
1782    pub fn block_on<F, T, E>(&mut self, future: F) -> Result<T, E>
1783    where
1784        F: Future<Output = Result<T, E>>,
1785        E: From<RuntimeError>,
1786    {
1787        struct ThreadWake(std::thread::Thread);
1788        impl Wake for ThreadWake {
1789            fn wake(self: Arc<Self>) {
1790                self.0.unpark();
1791            }
1792        }
1793        let waker = Waker::from(Arc::new(ThreadWake(std::thread::current())));
1794        let mut context = Context::from_waker(&waker);
1795        let mut future = pin!(future);
1796        loop {
1797            if let Poll::Ready(output) = future.as_mut().poll(&mut context) {
1798                return output;
1799            }
1800            match self.progress(self.shared.limits.max_progress_events) {
1801                Ok(progress) if progress.made_progress => {}
1802                Ok(_) => std::thread::park_timeout(Duration::from_millis(1)),
1803                Err(error) => {
1804                    self.fail(error.clone());
1805                    return Err(error.into());
1806                }
1807            }
1808        }
1809    }
1810
1811    pub fn stats(&self) -> RuntimeStats {
1812        RuntimeStats {
1813            state: self.state(),
1814            pending_calls: self.shared.pending.len(),
1815            completed_calls: self.shared.counters.completed.load(Ordering::Relaxed),
1816            cancelled_calls: self.shared.counters.cancelled.load(Ordering::Relaxed),
1817            timed_out_calls: self.shared.counters.timed_out.load(Ordering::Relaxed),
1818            late_results: self.shared.counters.late_results.load(Ordering::Relaxed),
1819            duplicate_requests: self
1820                .shared
1821                .counters
1822                .duplicate_requests
1823                .load(Ordering::Relaxed),
1824            action_failures: self.shared.counters.action_failures.load(Ordering::Relaxed),
1825            local_typed_dispatches: self
1826                .shared
1827                .counters
1828                .local_typed_dispatches
1829                .load(Ordering::Relaxed),
1830            local_action_serializations: self
1831                .shared
1832                .counters
1833                .local_action_serializations
1834                .load(Ordering::Relaxed),
1835            dedup_entries: self.dedup.len(),
1836            dedup_bytes: self.dedup.retained_bytes(),
1837            sent_by_channel: std::array::from_fn(|index| {
1838                self.shared.counters.sent[index].load(Ordering::Relaxed)
1839            }),
1840            received_by_channel: std::array::from_fn(|index| {
1841                self.shared.counters.received[index].load(Ordering::Relaxed)
1842            }),
1843            domains: self.domains.stats(),
1844            send_tickets: self.shared.tickets.lock().unwrap().len(),
1845            queued_responses: self.responses.len(),
1846            queued_response_bytes: self.response_bytes,
1847            objects: self.objects.stats(Instant::now()),
1848            transport: self.driver.stats(),
1849        }
1850    }
1851
1852    pub fn shutdown(&mut self) -> Result<ShutdownReport, RuntimeError> {
1853        let state = self.state();
1854        if state == RuntimeState::Stopped {
1855            return Ok(ShutdownReport {
1856                stats: self.stats(),
1857            });
1858        }
1859        if !matches!(state, RuntimeState::Running | RuntimeState::Failed) {
1860            return Err(RuntimeError::InvalidState {
1861                expected: RuntimeState::Running,
1862                actual: state,
1863            });
1864        }
1865        let work_gate = self.shared.work_gate.lock().unwrap();
1866        self.shared
1867            .state
1868            .store(RuntimeState::Draining as u8, Ordering::Release);
1869        self.shared.local_leases.clear();
1870        for (request, entry) in self.shared.pending.drain() {
1871            entry.promise.complete(Err(RuntimeError::Shutdown));
1872            if let Some(token) = entry.cancel_token {
1873                token.store(true, Ordering::Release);
1874            }
1875            if entry.destination != self.shared.local_id {
1876                let message = RuntimeMessage {
1877                    kind: RuntimeMessageKind::Cancel,
1878                    request,
1879                    action: entry.action,
1880                    domain: entry.domain,
1881                    deadline_ms: 0,
1882                    payload: Vec::new(),
1883                };
1884                let _ = self.shared.send(
1885                    entry.destination,
1886                    message,
1887                    TicketPurpose::BestEffort,
1888                    entry.trace_id,
1889                );
1890            }
1891        }
1892        while let Ok(request) = self.local_rx.try_recv() {
1893            request.cancel_token.store(true, Ordering::Release);
1894        }
1895        drop(work_gate);
1896        let deadline = Instant::now() + self.shared.limits.shutdown_timeout;
1897        while {
1898            let objects = self.objects.stats(Instant::now());
1899            !self.domains.idle()
1900                || objects.queued_calls != 0
1901                || objects.in_flight_calls != 0
1902                || self.shared.local_object_calls.load(Ordering::Acquire) != 0
1903        } {
1904            self.progress(self.shared.limits.max_progress_events)?;
1905            if Instant::now() >= deadline {
1906                self.shared
1907                    .state
1908                    .store(RuntimeState::Failed as u8, Ordering::Release);
1909                return Err(RuntimeError::ShutdownTimeout);
1910            }
1911            std::thread::yield_now();
1912        }
1913        while let Some(completion) = self.domains.try_completion() {
1914            self.handle_completion(completion)?;
1915        }
1916        while !self.responses.is_empty() {
1917            self.progress(self.shared.limits.max_progress_events)?;
1918            if Instant::now() >= deadline {
1919                self.shared
1920                    .state
1921                    .store(RuntimeState::Failed as u8, Ordering::Release);
1922                return Err(RuntimeError::ShutdownTimeout);
1923            }
1924        }
1925        self.domains.stop();
1926        self.dedup.clear();
1927        self.objects.clear_all();
1928        let mut events = Vec::new();
1929        self.driver.shutdown(
1930            &mut events,
1931            deadline.saturating_duration_since(Instant::now()),
1932        )?;
1933        for event in events {
1934            if let TransportEvent::LocalSendComplete { ticket }
1935            | TransportEvent::SendFailed { ticket, .. } = event
1936            {
1937                self.shared.tickets.lock().unwrap().remove(&ticket);
1938            }
1939        }
1940        self.shared
1941            .state
1942            .store(RuntimeState::Stopped as u8, Ordering::Release);
1943        let report = ShutdownReport {
1944            stats: self.stats(),
1945        };
1946        if report.stats.pending_calls != 0
1947            || report.stats.send_tickets != 0
1948            || report.stats.dedup_entries != 0
1949            || report.stats.queued_responses != 0
1950            || report.stats.queued_response_bytes != 0
1951            || report.stats.objects.live_objects != 0
1952            || report.stats.objects.leases != 0
1953            || report.stats.objects.roots != 0
1954            || report.stats.objects.placement_tickets != 0
1955            || report.stats.objects.transfers != 0
1956            || report.stats.objects.active_migrations != 0
1957            || report.stats.objects.prepared_migrations != 0
1958            || report.stats.objects.forwarding_entries != 0
1959            || report.stats.objects.migration_snapshot_bytes != 0
1960            || report.stats.transport.retained_bytes() != 0
1961        {
1962            return Err(RuntimeError::RetainedResources);
1963        }
1964        Ok(report)
1965    }
1966
1967    fn dispatch_local(&mut self, request: LocalRequest) -> Result<(), RuntimeError> {
1968        match &request.input {
1969            ActionValue::Typed(_) => self
1970                .shared
1971                .counters
1972                .local_typed_dispatches
1973                .fetch_add(1, Ordering::Relaxed),
1974            ActionValue::Encoded(_) => self
1975                .shared
1976                .counters
1977                .local_action_serializations
1978                .fetch_add(1, Ordering::Relaxed),
1979        };
1980        let request_id = request.message.request;
1981        let action_id = request.message.action;
1982        let result = self
1983            .shared
1984            .registry
1985            .get(action_id)
1986            .cloned()
1987            .ok_or(RuntimeError::UnknownAction(action_id))
1988            .and_then(|handler| {
1989                let domain = request.message.domain;
1990                let job = ActionJob {
1991                    requester: self.shared.local_id,
1992                    request: request_id,
1993                    action_id,
1994                    domain,
1995                    trace_id: request.trace_id,
1996                    input: request.input,
1997                    handler,
1998                    cancelled: request.cancel_token,
1999                    local: true,
2000                    submitted_at: Instant::now(),
2001                    object: None,
2002                };
2003                let Some(job) =
2004                    self.objects
2005                        .admit(action_id, job, self.domains.can_submit(domain))?
2006                else {
2007                    return Ok(());
2008                };
2009                let admitted = job.object;
2010                if let Err(error) = self.domains.submit(job) {
2011                    if let Some(admitted) = admitted {
2012                        self.objects.rollback(admitted);
2013                    }
2014                    return Err(error);
2015                }
2016                Ok(())
2017            });
2018        if let Err(error) = result {
2019            if let Some(entry) = self.shared.pending.remove(request_id) {
2020                entry.promise.complete(Err(error));
2021            }
2022        }
2023        Ok(())
2024    }
2025
2026    fn handle_transport_event(&mut self, event: TransportEvent) -> Result<(), RuntimeError> {
2027        match event {
2028            TransportEvent::LocalSendComplete { ticket } => {
2029                self.shared.tickets.lock().unwrap().remove(&ticket);
2030                Ok(())
2031            }
2032            TransportEvent::SendFailed { ticket, error } => {
2033                match self.shared.tickets.lock().unwrap().remove(&ticket) {
2034                    Some(TicketPurpose::Request(request)) => {
2035                        if let Some(entry) = self.shared.pending.remove(request) {
2036                            entry.promise.complete(Err(RuntimeError::Transport(error)));
2037                        }
2038                        Ok(())
2039                    }
2040                    Some(TicketPurpose::BestEffort) | None => Ok(()),
2041                    Some(TicketPurpose::RequiredResponse) => Err(RuntimeError::Transport(error)),
2042                }
2043            }
2044            TransportEvent::Incoming { parcel } => {
2045                self.shared.counters.received[parcel.channel as usize]
2046                    .fetch_add(1, Ordering::Relaxed);
2047                let source = parcel.source;
2048                let trace_id = parcel.trace_id;
2049                let message = wire::decode(parcel)
2050                    .map_err(|error| RuntimeError::Protocol(error.to_string()))?;
2051                self.handle_message(source, trace_id, message)
2052            }
2053            TransportEvent::PeerFailed { peer, .. } => {
2054                self.fail(RuntimeError::PeerFailed(peer));
2055                Err(RuntimeError::PeerFailed(peer))
2056            }
2057            TransportEvent::ShutdownComplete => Ok(()),
2058        }
2059    }
2060
2061    fn handle_message(
2062        &mut self,
2063        source: LocalityId,
2064        trace_id: Option<TraceId>,
2065        message: RuntimeMessage,
2066    ) -> Result<(), RuntimeError> {
2067        match message.kind {
2068            RuntimeMessageKind::Request => self.handle_request(source, trace_id, message),
2069            RuntimeMessageKind::Cancel => self.dedup.cancel(
2070                message.request,
2071                message.action,
2072                message.domain,
2073                Instant::now(),
2074            ),
2075            RuntimeMessageKind::Success => {
2076                self.complete_pending(source, message, |message| Ok(message.payload))
2077            }
2078            RuntimeMessageKind::Failure => self.complete_pending(source, message, |message| {
2079                let text = String::from_utf8(message.payload.into_iter().next().unwrap())
2080                    .map_err(|_| RuntimeError::Protocol("remote error is not UTF-8".into()))?;
2081                let message_text = cap(text);
2082                let category = message_text.to_ascii_lowercase();
2083                let error = if category.contains("placement")
2084                    || category.contains("admission")
2085                    || category.contains("actionqueue")
2086                    || category.contains("objectmailbox")
2087                {
2088                    RuntimeError::Placement {
2089                        request: message.request,
2090                        message: message_text,
2091                    }
2092                } else if category.contains("object") {
2093                    RuntimeError::RemoteObject {
2094                        request: message.request,
2095                        message: message_text,
2096                    }
2097                } else if category.contains("lease") || category.contains("transfer") {
2098                    RuntimeError::Lease {
2099                        request: message.request,
2100                        message: message_text,
2101                    }
2102                } else {
2103                    RuntimeError::RemoteAction {
2104                        request: message.request,
2105                        message: message_text,
2106                    }
2107                };
2108                Err(error)
2109            }),
2110            RuntimeMessageKind::Cancelled => self.complete_pending(source, message, |message| {
2111                Err(RuntimeError::Cancelled(message.request))
2112            }),
2113            RuntimeMessageKind::DuplicateResultUnavailable => {
2114                self.complete_pending(source, message, |message| {
2115                    Err(RuntimeError::DuplicateResultUnavailable(message.request))
2116                })
2117            }
2118            RuntimeMessageKind::Moved => self.complete_pending(source, message, |message| {
2119                if message.payload.len() != 2 || message.payload[0].len() != 32 {
2120                    return Err(RuntimeError::Protocol("invalid moved payload".into()));
2121                }
2122                let identity = &message.payload[0];
2123                let run = RunId::new(u128::from_le_bytes(identity[0..16].try_into().unwrap()))
2124                    .map_err(|_| RuntimeError::Protocol("invalid moved run".into()))?;
2125                let object = ObjectId::new(
2126                    run,
2127                    u128::from_le_bytes(identity[16..32].try_into().unwrap()),
2128                )
2129                .map_err(|_| RuntimeError::Protocol("invalid moved object".into()))?;
2130                let location = decode_location(message.payload[1].clone())?;
2131                Err(RuntimeError::Moved { object, location })
2132            }),
2133        }
2134    }
2135
2136    fn handle_request(
2137        &mut self,
2138        source: LocalityId,
2139        trace_id: Option<TraceId>,
2140        message: RuntimeMessage,
2141    ) -> Result<(), RuntimeError> {
2142        if self.state() != RuntimeState::Running {
2143            return self.send_failure(source, &message, "runtime is shutting down", trace_id);
2144        }
2145        if self
2146            .objects
2147            .request_targets_current(message.action, &message.payload)
2148        {
2149            self.dedup
2150                .release_redirect(message.request, message.action, message.domain)?;
2151        }
2152        match self.dedup.begin(
2153            message.request,
2154            message.action,
2155            message.domain,
2156            Instant::now(),
2157        ) {
2158            Ok(DedupDisposition::New(cancelled)) => {
2159                let Some(handler) = self.shared.registry.get(message.action).cloned() else {
2160                    return self.send_and_cache_failure(
2161                        source,
2162                        message,
2163                        "unknown action",
2164                        trace_id,
2165                    );
2166                };
2167                let job = ActionJob {
2168                    requester: source,
2169                    request: message.request,
2170                    action_id: message.action,
2171                    domain: message.domain,
2172                    trace_id,
2173                    input: ActionValue::Encoded(message.payload.clone()),
2174                    handler,
2175                    cancelled,
2176                    local: false,
2177                    submitted_at: Instant::now(),
2178                    object: None,
2179                };
2180                let admitted = self.objects.admit(
2181                    message.action,
2182                    job,
2183                    self.domains.can_submit(message.domain),
2184                );
2185                let Some(job) = (match admitted {
2186                    Ok(job) => job,
2187                    Err(RuntimeError::Moved { object, location }) => {
2188                        return self
2189                            .send_and_cache_moved(source, message, object, location, trace_id);
2190                    }
2191                    Err(error) => {
2192                        return self.send_and_cache_failure(
2193                            source,
2194                            message,
2195                            &error.to_string(),
2196                            trace_id,
2197                        );
2198                    }
2199                }) else {
2200                    return Ok(());
2201                };
2202                let admitted = job.object;
2203                if let Err(error) = self.domains.submit(job) {
2204                    if let Some(admitted) = admitted {
2205                        self.objects.rollback(admitted);
2206                    }
2207                    return self.send_and_cache_failure(
2208                        source,
2209                        message,
2210                        &error.to_string(),
2211                        trace_id,
2212                    );
2213                }
2214                Ok(())
2215            }
2216            Ok(DedupDisposition::Running) => {
2217                self.shared
2218                    .counters
2219                    .duplicate_requests
2220                    .fetch_add(1, Ordering::Relaxed);
2221                Ok(())
2222            }
2223            Ok(
2224                DedupDisposition::Replay(response)
2225                | DedupDisposition::Unavailable(response)
2226                | DedupDisposition::Cancelled(response),
2227            ) => {
2228                self.shared
2229                    .counters
2230                    .duplicate_requests
2231                    .fetch_add(1, Ordering::Relaxed);
2232                self.queue_response(source, response, trace_id, false)
2233            }
2234            Err(error) => self.send_failure(source, &message, &error.to_string(), trace_id),
2235        }
2236    }
2237
2238    fn handle_completion(&mut self, completion: ActionCompletion) -> Result<(), RuntimeError> {
2239        if let Some(object) = completion.object {
2240            for job in self.objects.complete(object) {
2241                let admitted = job.object;
2242                if let Err(error) = self.domains.submit(job) {
2243                    if let Some(admitted) = admitted {
2244                        self.objects.rollback(admitted);
2245                    }
2246                    return Err(error);
2247                }
2248            }
2249        }
2250        if completion.local {
2251            let entry = self.shared.pending.take_checked(
2252                completion.request,
2253                self.shared.local_id,
2254                completion.action_id,
2255            )?;
2256            if let Some(entry) = entry {
2257                let result = if completion.cancelled {
2258                    Err(RuntimeError::Cancelled(completion.request))
2259                } else {
2260                    completion
2261                        .result
2262                        .map_err(|error| RuntimeError::RemoteAction {
2263                            request: completion.request,
2264                            message: error.message(),
2265                        })
2266                };
2267                entry.promise.complete(result);
2268                self.shared
2269                    .counters
2270                    .completed
2271                    .fetch_add(1, Ordering::Relaxed);
2272            } else {
2273                self.shared
2274                    .counters
2275                    .late_results
2276                    .fetch_add(1, Ordering::Relaxed);
2277            }
2278            return Ok(());
2279        }
2280        let (kind, payload) = if completion.cancelled {
2281            (RuntimeMessageKind::Cancelled, Vec::new())
2282        } else {
2283            match completion.result {
2284                Ok(ActionValue::Encoded(payload)) => (RuntimeMessageKind::Success, payload),
2285                Ok(ActionValue::Typed(_)) => {
2286                    return Err(RuntimeError::Protocol(
2287                        "remote action produced typed output".into(),
2288                    ));
2289                }
2290                Err(error) => {
2291                    self.shared
2292                        .counters
2293                        .action_failures
2294                        .fetch_add(1, Ordering::Relaxed);
2295                    (
2296                        RuntimeMessageKind::Failure,
2297                        vec![error.message().into_bytes()],
2298                    )
2299                }
2300            }
2301        };
2302        let message = RuntimeMessage {
2303            kind,
2304            request: completion.request,
2305            action: completion.action_id,
2306            domain: completion.domain,
2307            deadline_ms: 0,
2308            payload,
2309        };
2310        self.queue_response(completion.requester, message, completion.trace_id, true)
2311    }
2312
2313    fn complete_pending<F>(
2314        &mut self,
2315        source: LocalityId,
2316        message: RuntimeMessage,
2317        map: F,
2318    ) -> Result<(), RuntimeError>
2319    where
2320        F: FnOnce(RuntimeMessage) -> Result<Segments, RuntimeError>,
2321    {
2322        match self
2323            .shared
2324            .pending
2325            .take_checked(message.request, source, message.action)?
2326        {
2327            Some(entry) => {
2328                entry
2329                    .promise
2330                    .complete(map(message).map(ActionValue::Encoded));
2331                self.shared
2332                    .counters
2333                    .completed
2334                    .fetch_add(1, Ordering::Relaxed);
2335            }
2336            None => {
2337                self.shared
2338                    .counters
2339                    .late_results
2340                    .fetch_add(1, Ordering::Relaxed);
2341            }
2342        }
2343        Ok(())
2344    }
2345
2346    fn send_failure(
2347        &mut self,
2348        destination: LocalityId,
2349        request: &RuntimeMessage,
2350        message: &str,
2351        trace_id: Option<TraceId>,
2352    ) -> Result<(), RuntimeError> {
2353        self.queue_response(
2354            destination,
2355            RuntimeMessage {
2356                kind: RuntimeMessageKind::Failure,
2357                request: request.request,
2358                action: request.action,
2359                domain: request.domain,
2360                deadline_ms: 0,
2361                payload: vec![cap(message.to_owned()).into_bytes()],
2362            },
2363            trace_id,
2364            false,
2365        )
2366    }
2367
2368    fn send_and_cache_moved(
2369        &mut self,
2370        destination: LocalityId,
2371        request: RuntimeMessage,
2372        object: ObjectId,
2373        location: hataori_runtime_foundation::protocol::ObjectLocation,
2374        trace_id: Option<TraceId>,
2375    ) -> Result<(), RuntimeError> {
2376        let mut identity = Vec::with_capacity(32);
2377        identity.extend_from_slice(&object.run().get().to_le_bytes());
2378        identity.extend_from_slice(&object.unique().to_le_bytes());
2379        let location = encode_location(location)
2380            .map_err(|error| RuntimeError::Protocol(error.message()))?
2381            .pop()
2382            .ok_or_else(|| RuntimeError::Protocol("missing moved location".into()))?;
2383        let response = RuntimeMessage {
2384            kind: RuntimeMessageKind::Moved,
2385            request: request.request,
2386            action: request.action,
2387            domain: request.domain,
2388            deadline_ms: 0,
2389            payload: vec![identity, location],
2390        };
2391        self.queue_response(destination, response, trace_id, true)
2392    }
2393
2394    fn send_and_cache_failure(
2395        &mut self,
2396        destination: LocalityId,
2397        request: RuntimeMessage,
2398        error: &str,
2399        trace_id: Option<TraceId>,
2400    ) -> Result<(), RuntimeError> {
2401        let response = RuntimeMessage {
2402            kind: RuntimeMessageKind::Failure,
2403            request: request.request,
2404            action: request.action,
2405            domain: request.domain,
2406            deadline_ms: 0,
2407            payload: vec![cap(error.to_owned()).into_bytes()],
2408        };
2409        self.queue_response(destination, response, trace_id, true)
2410    }
2411
2412    fn queue_response(
2413        &mut self,
2414        destination: LocalityId,
2415        message: RuntimeMessage,
2416        trace_id: Option<TraceId>,
2417        cache: bool,
2418    ) -> Result<(), RuntimeError> {
2419        let bytes = message
2420            .payload
2421            .iter()
2422            .try_fold(0_usize, |total, segment| total.checked_add(segment.len()));
2423        let bytes = bytes.ok_or(RuntimeError::ResourceExhausted {
2424            resource: ResourceKind::DedupBytes,
2425            limit: self.shared.limits.max_dedup_bytes,
2426        })?;
2427        let next_bytes =
2428            self.response_bytes
2429                .checked_add(bytes)
2430                .ok_or(RuntimeError::ResourceExhausted {
2431                    resource: ResourceKind::DedupBytes,
2432                    limit: self.shared.limits.max_dedup_bytes,
2433                })?;
2434        if self.responses.len() >= self.shared.limits.max_dedup_entries {
2435            return Err(RuntimeError::ResourceExhausted {
2436                resource: ResourceKind::DedupEntries,
2437                limit: self.shared.limits.max_dedup_entries,
2438            });
2439        }
2440        if next_bytes > self.shared.limits.max_dedup_bytes {
2441            return Err(RuntimeError::ResourceExhausted {
2442                resource: ResourceKind::DedupBytes,
2443                limit: self.shared.limits.max_dedup_bytes,
2444            });
2445        }
2446        self.response_bytes = next_bytes;
2447        self.responses.push_back(QueuedResponse {
2448            destination,
2449            message,
2450            trace_id,
2451            cache,
2452            bytes,
2453        });
2454        Ok(())
2455    }
2456
2457    fn drain_responses(&mut self, max_responses: usize) -> Result<usize, RuntimeError> {
2458        let mut sent = 0;
2459        while sent < max_responses {
2460            let Some(response) = self.responses.pop_front() else {
2461                break;
2462            };
2463            self.response_bytes = self.response_bytes.saturating_sub(response.bytes);
2464            match self.shared.send(
2465                response.destination,
2466                response.message.clone(),
2467                TicketPurpose::RequiredResponse,
2468                response.trace_id,
2469            ) {
2470                Ok(_) => {
2471                    if response.cache {
2472                        self.dedup.complete(response.message, Instant::now())?;
2473                    }
2474                    sent += 1;
2475                }
2476                Err(
2477                    RuntimeError::Transport(
2478                        hataori_runtime_foundation::transport::TransportError::QueueFull { .. }
2479                        | hataori_runtime_foundation::transport::TransportError::ByteLimit { .. },
2480                    )
2481                    | RuntimeError::ResourceExhausted {
2482                        resource: ResourceKind::SendTickets,
2483                        ..
2484                    },
2485                ) => {
2486                    self.response_bytes = self.response_bytes.saturating_add(response.bytes);
2487                    self.responses.push_front(response);
2488                    break;
2489                }
2490                Err(error) => return Err(error),
2491            }
2492        }
2493        Ok(sent)
2494    }
2495
2496    fn expire_pending(&self) {
2497        for (request, entry) in self.shared.pending.expired(Instant::now()) {
2498            entry.promise.complete(Err(RuntimeError::DeadlineExceeded {
2499                request,
2500                deadline: entry.deadline,
2501            }));
2502            self.shared
2503                .counters
2504                .timed_out
2505                .fetch_add(1, Ordering::Relaxed);
2506            if let Some(token) = entry.cancel_token {
2507                token.store(true, Ordering::Release);
2508            }
2509            if entry.destination != self.shared.local_id {
2510                let message = RuntimeMessage {
2511                    kind: RuntimeMessageKind::Cancel,
2512                    request,
2513                    action: entry.action,
2514                    domain: entry.domain,
2515                    deadline_ms: 0,
2516                    payload: Vec::new(),
2517                };
2518                let _ = self.shared.send(
2519                    entry.destination,
2520                    message,
2521                    TicketPurpose::BestEffort,
2522                    entry.trace_id,
2523                );
2524            }
2525        }
2526    }
2527
2528    fn fail(&self, error: RuntimeError) {
2529        self.shared
2530            .state
2531            .store(RuntimeState::Failed as u8, Ordering::Release);
2532        for (_, entry) in self.shared.pending.drain() {
2533            entry.promise.complete(Err(error.clone()));
2534            if let Some(token) = entry.cancel_token {
2535                token.store(true, Ordering::Release);
2536            }
2537        }
2538    }
2539}
2540
2541impl Drop for Runtime {
2542    fn drop(&mut self) {
2543        if !matches!(self.state(), RuntimeState::Stopped | RuntimeState::Failed) {
2544            let _work_gate = self.shared.work_gate.lock().unwrap();
2545            self.shared
2546                .state
2547                .store(RuntimeState::Draining as u8, Ordering::Release);
2548            self.shared.local_leases.clear();
2549            for (_, entry) in self.shared.pending.drain() {
2550                entry.promise.complete(Err(RuntimeError::Shutdown));
2551                if let Some(token) = entry.cancel_token {
2552                    token.store(true, Ordering::Release);
2553                }
2554            }
2555        }
2556    }
2557}
2558
2559fn decode_state(value: u8) -> RuntimeState {
2560    match value {
2561        value if value == RuntimeState::Created as u8 => RuntimeState::Created,
2562        value if value == RuntimeState::Bootstrapping as u8 => RuntimeState::Bootstrapping,
2563        value if value == RuntimeState::Running as u8 => RuntimeState::Running,
2564        value if value == RuntimeState::Draining as u8 => RuntimeState::Draining,
2565        value if value == RuntimeState::Stopped as u8 => RuntimeState::Stopped,
2566        _ => RuntimeState::Failed,
2567    }
2568}
2569
2570#[cfg(test)]
2571mod tests;