1#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
3pub struct VmMemoryUsage {
4 pub session_store: SessionStoreMemoryUsage,
6 pub coroutine_records: usize,
8 pub terminal_coroutines: usize,
10 pub program_count: usize,
12 pub program_instruction_count: usize,
14 pub obs_events: usize,
16 pub effect_trace_entries: usize,
18 pub communication_artifacts: usize,
20 pub output_condition_checks: usize,
22 pub retained_bytes: VmRetainedBytes,
24}
25
26#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
28pub struct VmRetainedBytes {
29 pub session_store: usize,
31 pub coroutines: usize,
33 pub programs: usize,
35 pub resource_states: usize,
37 pub traces: usize,
39 pub replay: usize,
41 pub output_condition_checks: usize,
43 pub scheduler_and_control: usize,
45 pub symbols: usize,
47 pub guard_layer: usize,
49 pub monitor: usize,
51 pub arena: usize,
53 pub total: usize,
55}
56
57fn vm_serialized_bytes<T: Serialize>(value: &T) -> usize {
58 bincode::serialized_size(value)
59 .ok()
60 .and_then(|bytes| usize::try_from(bytes).ok())
61 .unwrap_or(0)
62}
63
64impl VM {
65 fn communication_replay_enabled(&self) -> bool {
66 !matches!(
67 self.config.communication_replay_mode,
68 CommunicationReplayMode::Off
69 )
70 }
71
72 fn intern_load_plan_symbols(&mut self, plan: &crate::session::SessionOpenPlan, sid: SessionId) {
73 for role in plan.roles() {
74 let _: StringId = self.role_symbols.intern(role);
75 }
76 let _: StringId = self.handler_symbols.intern(crate::session::DEFAULT_HANDLER_ID);
77 let edge_handlers: Vec<_> = self
78 .sessions
79 .get(sid)
80 .map(|session| session.edge_handlers.keys().cloned().collect())
81 .unwrap_or_default();
82 for edge in edge_handlers {
83 let _: EdgeId = self.intern_edge(&edge);
84 }
85 }
86
87 #[must_use]
89 pub fn new(config: VMConfig) -> Self {
90 Self::new_with_models(config)
91 }
92
93 fn bind_default_handlers_for_session(&mut self, sid: SessionId) {
94 self.sessions.set_default_handler_for_session(
95 sid,
96 crate::session::DEFAULT_HANDLER_ID.to_string(),
97 );
98 self.handler_symbols.intern(crate::session::DEFAULT_HANDLER_ID);
99 }
100
101 fn ensure_session_capacity(&self) -> Result<(), VMError> {
102 if self.sessions.active_count() >= self.config.max_sessions {
103 return Err(VMError::TooManySessions {
104 max: self.config.max_sessions,
105 });
106 }
107 Ok(())
108 }
109
110 fn coroutine_runtime_eligible(&self, coro_id: usize) -> bool {
111 let Some(idx) = self.coro_index(coro_id) else {
112 return false;
113 };
114 let role = &self.coroutines[idx].role;
115 !(self.paused_coro_ids.contains(&coro_id)
116 || self.paused_roles.contains(role)
117 || self.crashed_sites.contains(role)
118 || self.timed_out_coro_ids.contains(&coro_id)
119 || self.timed_out_sites.contains_key(role))
120 }
121
122 fn mark_eligibility_dirty(&mut self) {
123 self.eligibility_dirty = true;
124 }
125
126 fn sync_ready_eligibility_for(&mut self, coro_id: usize) {
127 let eligible = self.sched.is_ready(coro_id) && self.coroutine_runtime_eligible(coro_id);
128 let eligibility = if eligible {
129 crate::scheduler::ReadyEligibility::Eligible
130 } else {
131 crate::scheduler::ReadyEligibility::Ineligible
132 };
133 self.sched.set_ready_eligibility(coro_id, eligibility);
134 #[cfg(debug_assertions)]
135 {
136 if eligible {
137 self.eligible_ready.insert(coro_id);
138 } else {
139 self.eligible_ready.remove(&coro_id);
140 }
141 }
142 }
143
144 fn refresh_ready_eligibility(&mut self) {
145 self.sched.clear_ready_eligibility();
146 #[cfg(debug_assertions)]
147 self.eligible_ready.clear();
148 for coro_id in self.sched.ready_set_snapshot() {
149 let eligible = self.coroutine_runtime_eligible(coro_id);
150 let eligibility = if eligible {
151 crate::scheduler::ReadyEligibility::Eligible
152 } else {
153 crate::scheduler::ReadyEligibility::Ineligible
154 };
155 self.sched.set_ready_eligibility(coro_id, eligibility);
156 #[cfg(debug_assertions)]
157 if eligible {
158 self.eligible_ready.insert(coro_id);
159 }
160 }
161 self.eligibility_dirty = false;
162 }
163
164 fn ensure_ready_eligibility(&mut self) {
165 if self.eligibility_dirty {
166 self.refresh_ready_eligibility();
167 }
168 }
169
170 #[cfg(debug_assertions)]
171 fn debug_assert_ready_eligibility_consistent(&self) {
172 for coro_id in &self.eligible_ready {
173 debug_assert!(self.sched.is_ready(*coro_id));
174 debug_assert!(self.coroutine_runtime_eligible(*coro_id));
175 }
176 }
177
178 fn sync_communication_consumption_mode(&mut self) {
179 self.communication_consumption
180 .set_mode(self.config.communication_replay_mode);
181 }
182
183 fn allocate_send_sequence(&mut self, edge: &Edge) -> u64 {
184 if !self.communication_replay_enabled() {
185 return 0;
187 }
188 self.sync_communication_consumption_mode();
189 self.communication_consumption.allocate_send_sequence(edge)
190 }
191
192 fn consume_receive_identity(
193 &mut self,
194 identity: CommunicationIdentity,
195 ) -> Result<CommunicationConsumeResult, CommunicationReplayError> {
196 if !self.communication_replay_enabled() {
197 return Ok(CommunicationConsumeResult {
199 mode: CommunicationReplayMode::Off,
200 pre_root: self.communication_consumption.root(),
201 post_root: self.communication_consumption.root(),
202 consumed_nullifier: None,
203 });
204 }
205 self.sync_communication_consumption_mode();
206 let result = self.communication_consumption.consume_receive(&identity)?;
207 self.communication_consumption_artifacts.push(
208 CommunicationConsumptionArtifact {
209 tick: self.clock.tick,
210 identity,
211 mode: result.mode,
212 pre_root: result.pre_root,
213 post_root: result.post_root,
214 },
215 &self.config.observability_retention,
216 );
217 Ok(result)
218 }
219
220 fn session_open_plan(
221 &mut self,
222 image: &CodeImage,
223 ) -> &crate::session::SessionOpenPlan {
224 let key = format!("{image:p}");
225 self.session_open_plans
226 .entry(key)
227 .or_insert_with(|| crate::session::SessionOpenPlan::new(&image.roles(), &image.local_types))
228 }
229
230 fn open_choreography_session(
231 &mut self,
232 plan: &crate::session::SessionOpenPlan,
233 ) -> (SessionId, Vec<String>) {
234 let sid = self.sessions.next_session_id();
235 let roles = plan.roles().to_vec();
236 self.sessions
237 .open_with_sid_from_plan(sid, plan, &self.config.buffer_config);
238 (sid, roles)
239 }
240
241 fn finalize_open_choreography_session(
242 &mut self,
243 sid: SessionId,
244 roles: &[String],
245 plan: &crate::session::SessionOpenPlan,
246 ) -> Result<(), VMError> {
247 self.next_session_id = self.sessions.next_session_id();
248 self.bind_default_handlers_for_session(sid);
249 self.intern_load_plan_symbols(plan, sid);
250 self.monitor.set_kind(sid, SessionKind::Peer);
251 self.resource_states
252 .entry(sid)
253 .or_default();
254 self.apply_open_delta(sid)
255 .map_err(VMError::PersistenceError)?;
256 self.obs_trace.push(
257 ObsEvent::Opened {
258 tick: self.clock.tick,
259 session: sid,
260 roles: roles.to_vec(),
261 },
262 &self.config.observability_retention,
263 );
264 Ok(())
265 }
266
267 fn spawn_coroutine_for_role(
268 &mut self,
269 image: &CodeImage,
270 sid: SessionId,
271 role: &str,
272 ) -> Result<(), VMError> {
273 if self.coroutines.len() >= self.config.max_coroutines {
274 return Err(VMError::TooManyCoroutines {
275 max: self.config.max_coroutines,
276 });
277 }
278
279 let program_id = self
280 .programs
281 .intern(image.programs.get(role).cloned().unwrap_or_default());
282 if self.code.is_none() {
283 let program = self
284 .programs
285 .get(program_id)
286 .expect("interned program must exist")
287 .clone();
288 self.code = Some(program);
289 }
290
291 let coro_id = self.next_coro_id;
292 self.next_coro_id += 1;
293
294 let endpoint = Endpoint {
295 sid,
296 role: role.to_string(),
297 };
298 self.role_coroutines
299 .entry(role.to_string())
300 .or_default()
301 .push(coro_id);
302 if self.paused_roles.contains(role) {
303 self.paused_coro_ids.insert(coro_id);
304 }
305 if self.timed_out_sites.contains_key(role) {
306 self.timed_out_coro_ids.insert(coro_id);
307 }
308 let mut coro = Coroutine::new(
309 coro_id,
310 program_id,
311 sid,
312 role.to_string(),
313 self.config.num_registers,
314 self.config.initial_cost_budget,
315 );
316 coro.owned_endpoints.push(endpoint.clone());
317 if !coro.regs.is_empty() {
318 coro.regs[0] = Value::Endpoint(endpoint);
319 }
320 self.sched.add_ready(coro_id);
321 self.coroutines.push(coro);
322 self.coro_slots.insert(coro_id, self.coroutines.len() - 1);
323 self.sync_ready_eligibility_for(coro_id);
324 Ok(())
325 }
326
327 fn spawn_session_coroutines(
328 &mut self,
329 image: &CodeImage,
330 sid: SessionId,
331 roles: &[String],
332 ) -> Result<(), VMError> {
333 for role in roles {
334 self.spawn_coroutine_for_role(image, sid, role)?;
335 }
336 Ok(())
337 }
338
339 pub fn load_choreography(&mut self, image: &CodeImage) -> Result<SessionId, VMError> {
349 self.ensure_session_capacity()?;
350 image.validate_runtime_shape().map_err(|reason| VMError::InvalidCodeImage { reason })?;
351 let plan = self.session_open_plan(image).clone();
352 let (sid, roles) = self.open_choreography_session(&plan);
353 self.finalize_open_choreography_session(sid, &roles, &plan)?;
354 self.programs.reserve(image.programs.len());
355 self.coroutines.reserve(roles.len());
356 self.spawn_session_coroutines(image, sid, &roles)?;
357 Ok(sid)
358 }
359
360 #[allow(clippy::too_many_lines)]
366 pub(crate) fn kernel_step_round(
367 &mut self,
368 handler: &dyn EffectHandler,
369 n: usize,
370 ) -> Result<StepResult, VMError> {
371 #[cfg(debug_assertions)]
372 debug_assert!(self.wf_vm_state().is_ok());
373 if n == 0 {
374 return Err(VMError::InvalidConcurrency { n });
375 }
376 self.last_sched_step = None;
377 self.clock.advance();
378 if self.all_done() {
379 return Ok(StepResult::AllDone);
380 }
381
382 self.ingest_topology_events(handler)?;
385 self.prune_expired_timeouts();
386 self.try_unblock_receivers();
387 self.ensure_ready_eligibility();
388 #[cfg(debug_assertions)]
389 self.debug_assert_ready_eligibility_consistent();
390 if !self.sched.has_eligible_ready() {
391 return Ok(StepResult::Stuck);
392 }
393 let coroutines = &self.coroutines;
394 let coro_slots = &self.coro_slots;
395 let Some(coro_id) = self.sched.pick_eligible_runnable(|id| {
396 coro_slots
397 .get(&id)
398 .and_then(|idx| coroutines.get(*idx))
399 .or_else(|| coroutines.get(id).filter(|coro| coro.id == id))
400 .or_else(|| coroutines.iter().find(|coro| coro.id == id))
401 .is_some_and(|coro| !coro.progress_tokens.is_empty())
402 }) else {
403 return Ok(StepResult::Stuck);
404 };
405 #[cfg(debug_assertions)]
406 self.eligible_ready.remove(&coro_id);
407
408 let result = self.exec_instr(coro_id, handler);
409
410 match result {
411 Ok(ExecOutcome::Continue) => {
412 self.last_sched_step = Some(SchedStepDebug {
413 selected_coro: coro_id,
414 exec_status: SchedExecStatus::Continue,
415 });
416 self.sched.reschedule(coro_id);
417 self.sync_ready_eligibility_for(coro_id);
418 }
419 Ok(ExecOutcome::Blocked(reason)) => {
420 let yielded = matches!(reason, BlockReason::Spawn);
421 self.last_sched_step = Some(SchedStepDebug {
422 selected_coro: coro_id,
423 exec_status: if yielded {
424 SchedExecStatus::Yielded
425 } else {
426 SchedExecStatus::Blocked
427 },
428 });
429 if yielded {
430 self.sched.reschedule(coro_id);
431 self.sync_ready_eligibility_for(coro_id);
432 } else {
433 self.sched.mark_blocked(coro_id, reason);
434 #[cfg(debug_assertions)]
435 self.eligible_ready.remove(&coro_id);
436 }
437 }
438 Ok(ExecOutcome::Halted) => {
439 self.last_sched_step = Some(SchedStepDebug {
440 selected_coro: coro_id,
441 exec_status: SchedExecStatus::Halted,
442 });
443 self.sched.mark_done(coro_id);
444 #[cfg(debug_assertions)]
445 self.eligible_ready.remove(&coro_id);
446 self.obs_trace.push(
447 ObsEvent::Halted {
448 tick: self.clock.tick,
449 coro_id,
450 },
451 &self.config.observability_retention,
452 );
453 }
454 Err(fault) => {
455 self.last_sched_step = Some(SchedStepDebug {
456 selected_coro: coro_id,
457 exec_status: SchedExecStatus::Faulted,
458 });
459 self.obs_trace.push(
460 ObsEvent::Faulted {
461 tick: self.clock.tick,
462 coro_id,
463 fault: fault.clone(),
464 },
465 &self.config.observability_retention,
466 );
467 let Some(idx) = self.coro_index(coro_id) else {
468 return Err(VMError::Fault { coro_id, fault });
469 };
470 self.coroutines[idx].status = CoroStatus::Faulted(fault.clone());
471 self.sched.mark_done(coro_id);
472 #[cfg(debug_assertions)]
473 self.eligible_ready.remove(&coro_id);
474 return Err(VMError::Fault { coro_id, fault });
475 }
476 }
477
478 if self.all_done() {
479 #[cfg(debug_assertions)]
480 self.debug_assert_ready_eligibility_consistent();
481 #[cfg(debug_assertions)]
482 debug_assert!(self.wf_vm_state().is_ok());
483 Ok(StepResult::AllDone)
484 } else {
485 #[cfg(debug_assertions)]
486 self.debug_assert_ready_eligibility_consistent();
487 #[cfg(debug_assertions)]
488 debug_assert!(self.wf_vm_state().is_ok());
489 Ok(StepResult::Continue)
490 }
491 }
492
493 pub fn step(&mut self, handler: &dyn EffectHandler) -> Result<StepResult, VMError> {
499 self.step_round(handler, 1)
500 }
501
502 pub fn step_round(
508 &mut self,
509 handler: &dyn EffectHandler,
510 n: usize,
511 ) -> Result<StepResult, VMError> {
512 VMKernel::step_round(self, handler, n)
513 }
514
515 pub fn run_concurrent(
523 &mut self,
524 handler: &dyn EffectHandler,
525 max_rounds: usize,
526 concurrency: usize,
527 ) -> Result<RunStatus, VMError> {
528 VMKernel::run_concurrent(self, handler, max_rounds, concurrency)
529 }
530
531 pub fn run(
539 &mut self,
540 handler: &dyn EffectHandler,
541 max_steps: usize,
542 ) -> Result<RunStatus, VMError> {
543 VMKernel::run(self, handler, max_steps)
544 }
545
546 pub fn run_replay(
555 &mut self,
556 fallback: &dyn EffectHandler,
557 replay_trace: &[EffectTraceEntry],
558 max_steps: usize,
559 ) -> Result<RunStatus, VMError> {
560 self.run_replay_shared(
561 fallback,
562 Arc::<[EffectTraceEntry]>::from(replay_trace),
563 max_steps,
564 )
565 }
566
567 pub fn run_replay_shared(
576 &mut self,
577 fallback: &dyn EffectHandler,
578 replay_trace: Arc<[EffectTraceEntry]>,
579 max_steps: usize,
580 ) -> Result<RunStatus, VMError> {
581 let replay = ReplayEffectHandler::with_fallback(replay_trace, fallback);
582 self.run(&replay, max_steps)
583 }
584
585 pub fn run_concurrent_replay(
591 &mut self,
592 fallback: &dyn EffectHandler,
593 replay_trace: &[EffectTraceEntry],
594 max_rounds: usize,
595 concurrency: usize,
596 ) -> Result<RunStatus, VMError> {
597 self.run_concurrent_replay_shared(
598 fallback,
599 Arc::<[EffectTraceEntry]>::from(replay_trace),
600 max_rounds,
601 concurrency,
602 )
603 }
604
605 pub fn run_concurrent_replay_shared(
611 &mut self,
612 fallback: &dyn EffectHandler,
613 replay_trace: Arc<[EffectTraceEntry]>,
614 max_rounds: usize,
615 concurrency: usize,
616 ) -> Result<RunStatus, VMError> {
617 let replay = ReplayEffectHandler::with_fallback(replay_trace, fallback);
618 self.run_concurrent(&replay, max_rounds, concurrency)
619 }
620
621 #[must_use]
623 pub fn trace(&self) -> &[ObsEvent] {
624 self.obs_trace.as_slice()
625 }
626
627 pub fn reap_closed_sessions(&mut self) -> Vec<ClosedSessionSummary> {
629 let eligible: Vec<SessionId> = self
630 .sessions
631 .closed_session_ids()
632 .into_iter()
633 .filter(|sid| {
634 self.coroutines
635 .iter()
636 .filter(|coro| coro.session_id == *sid)
637 .all(Coroutine::is_terminal)
638 })
639 .collect();
640 if eligible.is_empty() {
641 return Vec::new();
642 }
643
644 for sid in &eligible {
645 self.monitor.remove_kind(*sid);
646 self.resource_states.remove(sid);
647 self.communication_consumption.prune_session(*sid);
648 }
649 self.coroutines.retain(|coro| {
650 !(eligible.contains(&coro.session_id) && coro.is_terminal())
651 });
652 self.rebuild_coroutine_indexes();
653 self.sessions.reap_sessions(&eligible)
654 }
655
656 #[must_use]
658 pub fn obs_trace(&self) -> &[ObsEvent] {
659 self.obs_trace.as_slice()
660 }
661
662 #[must_use]
664 pub fn role_symbol_count(&self) -> usize {
665 self.role_symbols.len()
666 }
667
668 #[must_use]
670 pub fn label_symbol_count(&self) -> usize {
671 self.label_symbols.len()
672 }
673
674 #[must_use]
676 pub fn handler_symbol_count(&self) -> usize {
677 self.handler_symbols.len()
678 }
679
680 #[must_use]
682 pub fn edge_symbol_count(&self) -> usize {
683 self.edge_symbols.len()
684 }
685
686 #[must_use]
688 pub fn config(&self) -> &VMConfig {
689 &self.config
690 }
691
692 #[must_use]
694 pub fn last_sched_step(&self) -> Option<&SchedStepDebug> {
695 self.last_sched_step.as_ref()
696 }
697
698 #[must_use]
700 pub fn scheduler_step_count(&self) -> usize {
701 self.sched.step_count()
702 }
703
704 #[must_use]
706 pub fn coroutine_count(&self) -> usize {
707 self.coroutines.len()
708 }
709
710 #[must_use]
712 pub fn next_session_id(&self) -> SessionId {
713 self.sessions.next_session_id()
714 }
715
716 #[must_use]
718 pub fn session_count(&self) -> usize {
719 self.sessions.active_count()
720 }
721
722 #[must_use]
724 pub fn live_session_count(&self) -> usize {
725 self.sessions.live_count()
726 }
727
728 #[must_use]
730 pub fn memory_usage(&self) -> VmMemoryUsage {
731 let session_store = self.sessions.memory_usage();
732 let retained_bytes = self.retained_bytes(session_store.retained_bytes.total);
733 VmMemoryUsage {
734 session_store,
735 coroutine_records: self.coroutines.len(),
736 terminal_coroutines: self.coroutines.iter().filter(|coro| coro.is_terminal()).count(),
737 program_count: self.programs.len(),
738 program_instruction_count: self.programs.instruction_count(),
739 obs_events: self.obs_trace.len(),
740 effect_trace_entries: self.effect_trace.len(),
741 communication_artifacts: self.communication_consumption_artifacts.len(),
742 output_condition_checks: self.output_condition_checks.len(),
743 retained_bytes,
744 }
745 }
746
747 fn retained_bytes(&self, session_store_bytes: usize) -> VmRetainedBytes {
748 let mut retained_bytes = VmRetainedBytes {
749 session_store: session_store_bytes,
750 coroutines: self.coroutines.iter().map(vm_serialized_bytes).sum(),
751 programs: vm_serialized_bytes(&self.programs)
752 .saturating_add(vm_serialized_bytes(&self.code)),
753 resource_states: vm_serialized_bytes(&self.resource_states),
754 traces: vm_serialized_bytes(&self.obs_trace)
755 .saturating_add(vm_serialized_bytes(&self.effect_trace)),
756 replay: vm_serialized_bytes(&self.communication_consumption)
757 .saturating_add(vm_serialized_bytes(&self.communication_consumption_artifacts)),
758 output_condition_checks: vm_serialized_bytes(&self.output_condition_checks),
759 scheduler_and_control: vm_serialized_bytes(&self.sched)
760 .saturating_add(vm_serialized_bytes(&self.eligible_ready))
761 .saturating_add(vm_serialized_bytes(&self.paused_roles))
762 .saturating_add(vm_serialized_bytes(&self.crashed_sites))
763 .saturating_add(vm_serialized_bytes(&self.partitioned_edges))
764 .saturating_add(vm_serialized_bytes(&self.corrupted_edges))
765 .saturating_add(vm_serialized_bytes(&self.timed_out_sites))
766 .saturating_add(vm_serialized_bytes(&self.clock))
767 .saturating_add(vm_serialized_bytes(&self.last_sched_step))
768 .saturating_add(vm_serialized_bytes(&self.handler_identity_anchor))
769 .saturating_add(vm_serialized_bytes(&self.next_coro_id))
770 .saturating_add(vm_serialized_bytes(&self.next_session_id)),
771 symbols: vm_serialized_bytes(&self.role_symbols)
772 .saturating_add(vm_serialized_bytes(&self.label_symbols))
773 .saturating_add(vm_serialized_bytes(&self.handler_symbols))
774 .saturating_add(vm_serialized_bytes(&self.edge_symbols)),
775 guard_layer: vm_serialized_bytes(&self.guard_layer),
776 monitor: vm_serialized_bytes(&self.monitor),
777 arena: vm_serialized_bytes(&self.arena),
778 total: 0,
779 };
780 retained_bytes.total = Self::retained_bytes_total(&retained_bytes);
781 retained_bytes
782 }
783
784 fn retained_bytes_total(retained_bytes: &VmRetainedBytes) -> usize {
785 retained_bytes
786 .session_store
787 .saturating_add(retained_bytes.coroutines)
788 .saturating_add(retained_bytes.programs)
789 .saturating_add(retained_bytes.resource_states)
790 .saturating_add(retained_bytes.traces)
791 .saturating_add(retained_bytes.replay)
792 .saturating_add(retained_bytes.output_condition_checks)
793 .saturating_add(retained_bytes.scheduler_and_control)
794 .saturating_add(retained_bytes.symbols)
795 .saturating_add(retained_bytes.guard_layer)
796 .saturating_add(retained_bytes.monitor)
797 .saturating_add(retained_bytes.arena)
798 }
799
800 #[must_use]
802 pub fn output_condition_checks(&self) -> &[OutputConditionCheck] {
803 self.output_condition_checks.as_slice()
804 }
805
806 #[must_use]
808 pub fn effect_trace(&self) -> &[EffectTraceEntry] {
809 self.effect_trace.as_slice()
810 }
811
812 #[must_use]
814 pub fn communication_replay_root(&self) -> crate::verification::Hash {
815 self.communication_consumption.root()
816 }
817
818 #[must_use]
820 pub fn communication_consumption_artifacts(&self) -> &[CommunicationConsumptionArtifact] {
821 self.communication_consumption_artifacts.as_slice()
822 }
823
824 pub fn drain_obs_trace(&mut self) -> Vec<ObsEvent> {
826 self.obs_trace.drain()
827 }
828
829 pub fn drain_effect_trace(&mut self) -> Vec<EffectTraceEntry> {
831 self.effect_trace.drain()
832 }
833
834 pub fn drain_output_condition_checks(&mut self) -> Vec<OutputConditionCheck> {
836 self.output_condition_checks.drain()
837 }
838
839 pub fn drain_communication_consumption_artifacts(
841 &mut self,
842 ) -> Vec<CommunicationConsumptionArtifact> {
843 self.communication_consumption_artifacts.drain()
844 }
845
846 #[must_use]
848 pub fn canonical_replay_fragment(&self) -> CanonicalReplayFragmentV1 {
849 let partitioned_edges = self.partitioned_edges.iter().cloned().collect();
850 let corrupted_edges = self
851 .corrupted_edges
852 .iter()
853 .map(|(edge, corruption)| (edge.clone(), *corruption))
854 .collect();
855 let timed_out_sites = self
856 .timed_out_sites
857 .iter()
858 .map(|(site, until_tick)| (site.clone(), *until_tick))
859 .collect();
860 canonical_replay_fragment_v1(
861 self.obs_trace.as_slice(),
862 self.effect_trace.as_slice(),
863 self.crashed_sites.iter().cloned().collect(),
864 partitioned_edges,
865 corrupted_edges,
866 timed_out_sites,
867 self.config.effect_determinism_tier,
868 self.config.communication_replay_mode,
869 Some(self.communication_consumption.root()),
870 self.communication_consumption_artifacts.as_slice().to_vec(),
871 )
872 }
873
874 #[must_use]
876 pub fn crashed_sites(&self) -> &BTreeSet<SiteId> {
877 &self.crashed_sites
878 }
879
880 #[must_use]
882 pub fn partitioned_edges(&self) -> &BTreeSet<(SiteId, SiteId)> {
883 &self.partitioned_edges
884 }
885
886 #[must_use]
888 pub fn corrupted_edges(&self) -> &BTreeMap<(SiteId, SiteId), CorruptionType> {
889 &self.corrupted_edges
890 }
891
892 #[must_use]
894 pub fn timed_out_sites(&self) -> &BTreeMap<SiteId, u64> {
895 &self.timed_out_sites
896 }
897}