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 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 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 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 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 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 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 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;