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