Skip to main content

hataori_runtime/
runtime.rs

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