1use std::{
7 collections::{BTreeMap, HashSet, VecDeque},
8 net::IpAddr,
9 sync::{Arc, RwLock, Weak},
10 task::Waker,
11 time::Duration,
12};
13use tracing::instrument;
14
15use crate::{
16 SimulationError, SimulationResult,
17 chaos::fault_events::SimFaultEvent,
18 network::{
19 NetworkConfiguration, PartitionStrategy,
20 sim::{ConnectionId, ListenerId, SimNetworkProvider},
21 },
22};
23
24use super::{
25 events::{ConnectionStateChange, Event, EventQueue, NetworkOperation, ScheduledEvent},
26 rng::{reset_sim_rng, set_sim_seed, sim_random, sim_random_range},
27 sleep::SleepFuture,
28 state::{
29 ClogState, CloseReason, ConnectionFlags, ConnectionState, NetworkState, PartitionState,
30 StorageState,
31 },
32 wakers::WakerRegistry,
33};
34
35#[derive(Debug)]
37pub(crate) struct SimInner {
38 pub(crate) current_time: Duration,
39 pub(crate) timer_time: Duration,
42 pub(crate) event_queue: EventQueue,
43 pub(crate) next_sequence: u64,
44
45 pub(crate) network: NetworkState,
47
48 pub(crate) storage: StorageState,
50
51 pub(crate) wakers: WakerRegistry,
53
54 pub(crate) next_task_id: u64,
56 pub(crate) awakened_tasks: HashSet<u64>,
57
58 pub(crate) events_processed: u64,
60
61 pub(crate) last_bit_flip_time: Duration,
63
64 pub(crate) last_processed_event: Option<Event>,
66
67 pub(crate) pending_faults: Vec<SimFaultRecord>,
70}
71
72impl SimInner {
73 pub(crate) fn new() -> Self {
74 Self {
75 current_time: Duration::ZERO,
76 timer_time: Duration::ZERO,
77 event_queue: EventQueue::new(),
78 next_sequence: 0,
79 network: NetworkState::new(NetworkConfiguration::default()),
80 storage: StorageState::default(),
81 wakers: WakerRegistry::default(),
82 next_task_id: 0,
83 awakened_tasks: HashSet::new(),
84 events_processed: 0,
85 last_bit_flip_time: Duration::ZERO,
86 last_processed_event: None,
87 pending_faults: Vec::new(),
88 }
89 }
90
91 pub(crate) fn new_with_config(network_config: NetworkConfiguration) -> Self {
92 Self {
93 current_time: Duration::ZERO,
94 timer_time: Duration::ZERO,
95 event_queue: EventQueue::new(),
96 next_sequence: 0,
97 network: NetworkState::new(network_config),
98 storage: StorageState::default(),
99 wakers: WakerRegistry::default(),
100 next_task_id: 0,
101 awakened_tasks: HashSet::new(),
102 events_processed: 0,
103 last_bit_flip_time: Duration::ZERO,
104 last_processed_event: None,
105 pending_faults: Vec::new(),
106 }
107 }
108
109 pub(crate) fn record_fault(&mut self, event: SimFaultEvent) {
115 let time_ms = u64::try_from(self.current_time.as_millis()).unwrap_or(u64::MAX);
116 self.pending_faults.push(SimFaultRecord { time_ms, event });
117 }
118
119 pub(crate) fn calculate_flip_bit_count(random_value: u32, min_bits: u32, max_bits: u32) -> u32 {
129 if random_value == 0 {
130 return max_bits.min(32);
132 }
133
134 let bit_count = 1 + random_value.leading_zeros();
136
137 bit_count.clamp(min_bits, max_bits)
139 }
140}
141
142#[derive(Debug, Clone)]
149pub struct SimFaultRecord {
150 pub time_ms: u64,
152 pub event: SimFaultEvent,
154}
155
156#[derive(Debug)]
162pub struct SimWorld {
163 pub(crate) inner: Arc<RwLock<SimInner>>,
164}
165
166impl SimWorld {
167 fn create(network_config: Option<NetworkConfiguration>, seed: u64) -> Self {
169 reset_sim_rng();
170 set_sim_seed(seed);
171 crate::chaos::assertions::reset_assertion_results();
172
173 let inner = match network_config {
174 Some(config) => SimInner::new_with_config(config),
175 None => SimInner::new(),
176 };
177
178 Self {
179 inner: Arc::new(RwLock::new(inner)),
180 }
181 }
182
183 #[must_use]
188 pub fn new() -> Self {
189 Self::create(None, 0)
190 }
191
192 #[must_use]
202 pub fn take_faults(&self) -> Vec<SimFaultRecord> {
203 let mut inner = self
204 .inner
205 .write()
206 .expect("RwLock poisoned: prior task panicked");
207 std::mem::take(&mut inner.pending_faults)
208 }
209
210 #[must_use]
219 pub fn new_with_seed(seed: u64) -> Self {
220 Self::create(None, seed)
221 }
222
223 #[must_use]
225 pub fn new_with_network_config(network_config: NetworkConfiguration) -> Self {
226 Self::create(Some(network_config), 0)
227 }
228
229 #[must_use]
236 pub fn new_with_network_config_and_seed(
237 network_config: NetworkConfiguration,
238 seed: u64,
239 ) -> Self {
240 Self::create(Some(network_config), seed)
241 }
242
243 #[instrument(skip(self))]
252 pub fn step(&mut self) -> bool {
253 let mut inner = self
254 .inner
255 .write()
256 .expect("RwLock poisoned: prior task panicked");
257
258 if let Some(scheduled_event) = inner.event_queue.pop_earliest() {
259 inner.current_time = scheduled_event.time();
261
262 Self::clear_expired_clogs_with_inner(&mut inner);
264
265 Self::randomly_trigger_partitions_with_inner(&mut inner);
267
268 let event = scheduled_event.into_event();
270 inner.last_processed_event = Some(event.clone());
271
272 Self::process_event_with_inner(&mut inner, event);
274
275 !inner.event_queue.is_empty()
277 } else {
278 inner.last_processed_event = None;
279 false
281 }
282 }
283
284 #[instrument(skip(self))]
294 pub fn run_until_empty(&mut self) {
295 while self.step() {
296 if self
298 .inner
299 .read()
300 .expect("RwLock poisoned: prior task panicked")
301 .events_processed
302 .is_multiple_of(50)
303 {
304 let has_workload_events = !self
305 .inner
306 .read()
307 .expect("RwLock poisoned: prior task panicked")
308 .event_queue
309 .has_only_infrastructure_events();
310 if !has_workload_events {
311 tracing::debug!(
312 "Early termination: only infrastructure events remain in queue"
313 );
314 break;
315 }
316 }
317 }
318 }
319
320 #[must_use]
326 pub fn current_time(&self) -> Duration {
327 self.inner
328 .read()
329 .expect("RwLock poisoned: prior task panicked")
330 .current_time
331 }
332
333 #[must_use]
342 pub fn now(&self) -> Duration {
343 self.inner
344 .read()
345 .expect("RwLock poisoned: prior task panicked")
346 .current_time
347 }
348
349 #[must_use]
368 pub fn timer(&self) -> Duration {
369 let mut inner = self
370 .inner
371 .write()
372 .expect("RwLock poisoned: prior task panicked");
373 let chaos = &inner.network.config.chaos;
374
375 if !chaos.clock_drift_enabled {
377 return inner.current_time;
378 }
379
380 let max_timer = inner.current_time + chaos.clock_drift_max;
384
385 if inner.timer_time < max_timer {
387 let random_factor = sim_random::<f64>(); let gap = max_timer
389 .checked_sub(inner.timer_time)
390 .expect("timer_time < max_timer was just checked")
391 .as_secs_f64();
392 let delta = random_factor * gap / 2.0;
393 inner.timer_time += Duration::from_secs_f64(delta);
394 }
395
396 inner.timer_time = inner.timer_time.max(inner.current_time);
398
399 inner.timer_time
400 }
401
402 #[instrument(skip(self))]
408 pub fn schedule_event(&self, event: Event, delay: Duration) {
409 let mut inner = self
410 .inner
411 .write()
412 .expect("RwLock poisoned: prior task panicked");
413 let scheduled_time = inner.current_time + delay;
414 let sequence = inner.next_sequence;
415 inner.next_sequence += 1;
416
417 let scheduled_event = ScheduledEvent::new(scheduled_time, event, sequence);
418 inner.event_queue.schedule(scheduled_event);
419 }
420
421 pub fn schedule_event_at(&self, event: Event, time: Duration) {
427 let mut inner = self
428 .inner
429 .write()
430 .expect("RwLock poisoned: prior task panicked");
431 let sequence = inner.next_sequence;
432 inner.next_sequence += 1;
433
434 let scheduled_event = ScheduledEvent::new(time, event, sequence);
435 inner.event_queue.schedule(scheduled_event);
436 }
437
438 #[must_use]
443 pub fn downgrade(&self) -> WeakSimWorld {
444 WeakSimWorld {
445 inner: Arc::downgrade(&self.inner),
446 }
447 }
448
449 #[must_use]
455 pub fn has_pending_events(&self) -> bool {
456 !self
457 .inner
458 .read()
459 .expect("RwLock poisoned: prior task panicked")
460 .event_queue
461 .is_empty()
462 }
463
464 #[must_use]
470 pub fn pending_event_count(&self) -> usize {
471 self.inner
472 .read()
473 .expect("RwLock poisoned: prior task panicked")
474 .event_queue
475 .len()
476 }
477
478 #[must_use]
480 pub fn network_provider(&self) -> SimNetworkProvider {
481 SimNetworkProvider::new(self.downgrade())
482 }
483
484 #[must_use]
486 pub fn time_provider(&self) -> crate::providers::SimTimeProvider {
487 crate::providers::SimTimeProvider::new(self.downgrade())
488 }
489
490 #[must_use]
492 pub fn task_provider(&self) -> crate::providers::SimTaskProvider {
493 crate::providers::SimTaskProvider
494 }
495
496 #[must_use]
498 pub fn storage_provider(&self, ip: std::net::IpAddr) -> crate::storage::SimStorageProvider {
499 crate::storage::SimStorageProvider::new(self.downgrade(), ip)
500 }
501
502 pub fn set_storage_config(&mut self, config: crate::storage::StorageConfiguration) {
510 self.inner
511 .write()
512 .expect("RwLock poisoned: prior task panicked")
513 .storage
514 .config = config;
515 }
516
517 pub fn set_storage_config_for(
527 &mut self,
528 ip: std::net::IpAddr,
529 config: crate::storage::StorageConfiguration,
530 ) {
531 self.inner
532 .write()
533 .expect("RwLock poisoned: prior task panicked")
534 .storage
535 .per_process_configs
536 .insert(ip, config);
537 }
538
539 #[must_use]
548 pub fn disk_episode_for(
549 &self,
550 ip: std::net::IpAddr,
551 ) -> Option<super::state::DiskDegradationState> {
552 self.inner
553 .read()
554 .expect("RwLock poisoned: prior task panicked")
555 .storage
556 .disk_episodes
557 .get(&ip)
558 .copied()
559 }
560
561 pub fn with_network_config<F, R>(&self, f: F) -> R
573 where
574 F: FnOnce(&NetworkConfiguration) -> R,
575 {
576 let inner = self
577 .inner
578 .read()
579 .expect("RwLock poisoned: prior task panicked");
580 f(&inner.network.config)
581 }
582
583 pub(crate) fn create_listener(&self) -> ListenerId {
585 let mut inner = self
586 .inner
587 .write()
588 .expect("RwLock poisoned: prior task panicked");
589 let listener_id = ListenerId(inner.network.next_listener_id);
590 inner.network.next_listener_id += 1;
591
592 inner.network.listeners.insert(listener_id);
593
594 listener_id
595 }
596
597 pub(crate) fn read_from_connection(
599 &self,
600 connection_id: ConnectionId,
601 buf: &mut [u8],
602 ) -> SimulationResult<usize> {
603 let mut inner = self
604 .inner
605 .write()
606 .expect("RwLock poisoned: prior task panicked");
607
608 let partial_read_max_bytes = inner.network.config.chaos.partial_read_max_bytes;
611
612 if let Some(connection) = inner.network.connections.get_mut(&connection_id) {
613 let available = std::cmp::min(buf.len(), connection.receive_buffer.len());
614
615 let limit = if available > 0 && !connection.flags.is_stable() && crate::buggify!() {
621 let max_read = std::cmp::min(available, partial_read_max_bytes);
622 if max_read >= 1 {
623 let n = sim_random_range(1..max_read + 1);
624 if n < available {
625 tracing::debug!(
626 "BUGGIFY: Partial read on connection {} - delivering {} of {} bytes",
627 connection_id.0,
628 n,
629 available
630 );
631 }
632 n
633 } else {
634 available
635 }
636 } else {
637 available
638 };
639
640 let mut bytes_read = 0;
641 while bytes_read < limit {
642 match connection.receive_buffer.pop_front() {
643 Some(byte) => {
644 buf[bytes_read] = byte;
645 bytes_read += 1;
646 }
647 None => break,
648 }
649 }
650 Ok(bytes_read)
651 } else {
652 Err(SimulationError::InvalidState(
653 "connection not found".to_string(),
654 ))
655 }
656 }
657
658 pub(crate) fn write_to_connection(
660 &self,
661 connection_id: ConnectionId,
662 data: &[u8],
663 ) -> SimulationResult<()> {
664 let mut inner = self
665 .inner
666 .write()
667 .expect("RwLock poisoned: prior task panicked");
668
669 if let Some(connection) = inner.network.connections.get_mut(&connection_id) {
670 for &byte in data {
671 connection.receive_buffer.push_back(byte);
672 }
673 Ok(())
674 } else {
675 Err(SimulationError::InvalidState(
676 "connection not found".to_string(),
677 ))
678 }
679 }
680
681 pub(crate) fn buffer_send(
686 &self,
687 connection_id: ConnectionId,
688 data: Vec<u8>,
689 ) -> SimulationResult<()> {
690 tracing::debug!(
691 "buffer_send called for connection_id={} with {} bytes",
692 connection_id.0,
693 data.len()
694 );
695 let mut inner = self
696 .inner
697 .write()
698 .expect("RwLock poisoned: prior task panicked");
699
700 if let Some(conn) = inner.network.connections.get_mut(&connection_id) {
701 conn.send_buffer.push_back(data);
703 tracing::debug!(
704 "buffer_send: added data to send_buffer, new length: {}",
705 conn.send_buffer.len()
706 );
707
708 if conn.flags.send_in_progress() {
710 tracing::debug!(
711 "buffer_send: sender already in progress, not scheduling new event"
712 );
713 } else {
714 tracing::debug!(
715 "buffer_send: sender not in progress, scheduling ProcessSendBuffer event"
716 );
717 conn.flags.set_send_in_progress(true);
718
719 let scheduled_time = inner.current_time + std::time::Duration::ZERO;
721 let sequence = inner.next_sequence;
722 inner.next_sequence += 1;
723 let scheduled_event = ScheduledEvent::new(
724 scheduled_time,
725 Event::Network {
726 connection_id: connection_id.0,
727 operation: NetworkOperation::ProcessSendBuffer,
728 },
729 sequence,
730 );
731 inner.event_queue.schedule(scheduled_event);
732 tracing::debug!(
733 "buffer_send: scheduled ProcessSendBuffer event with sequence {}",
734 sequence
735 );
736 }
737
738 Ok(())
739 } else {
740 tracing::debug!(
741 "buffer_send: connection_id={} not found in connections table",
742 connection_id.0
743 );
744 Err(SimulationError::InvalidState(
745 "connection not found".to_string(),
746 ))
747 }
748 }
749
750 pub(crate) fn create_connection_pair(
758 &self,
759 client_addr: &str,
760 server_addr: &str,
761 ) -> (ConnectionId, ConnectionId) {
762 const DEFAULT_SEND_BUFFER_CAPACITY: usize = 64 * 1024;
766
767 let mut inner = self
768 .inner
769 .write()
770 .expect("RwLock poisoned: prior task panicked");
771
772 let client_conn = ConnectionId(inner.network.next_connection_id);
773 inner.network.next_connection_id += 1;
774
775 let server_conn = ConnectionId(inner.network.next_connection_id);
776 inner.network.next_connection_id += 1;
777
778 let current_time = inner.current_time;
780
781 let client_endpoint = NetworkState::parse_ip_from_addr(client_addr);
783 let server_endpoint = NetworkState::parse_ip_from_addr(server_addr);
784
785 let ephemeral_peer_addr = match client_endpoint {
789 Some(std::net::IpAddr::V4(ipv4)) => {
790 let octets = ipv4.octets();
791 let ip_offset =
792 u8::try_from(sim_random_range(0u32..256)).expect("range bounded to u8");
793 let new_last_octet = octets[3].wrapping_add(ip_offset);
794 let ephemeral_ip =
795 std::net::Ipv4Addr::new(octets[0], octets[1], octets[2], new_last_octet);
796 let ephemeral_port = sim_random_range(40000u16..60000);
797 format!("{ephemeral_ip}:{ephemeral_port}")
798 }
799 Some(std::net::IpAddr::V6(ipv6)) => {
800 let segments = ipv6.segments();
802 let mut new_segments = segments;
803 let ip_offset = sim_random_range(0u16..256);
804 new_segments[7] = new_segments[7].wrapping_add(ip_offset);
805 let ephemeral_ip = std::net::Ipv6Addr::from(new_segments);
806 let ephemeral_port = sim_random_range(40000u16..60000);
807 format!("[{ephemeral_ip}]:{ephemeral_port}")
808 }
809 None => {
810 let ephemeral_port = sim_random_range(40000u16..60000);
812 format!("unknown:{ephemeral_port}")
813 }
814 };
815
816 inner.network.connections.insert(
819 client_conn,
820 ConnectionState {
821 local_ip: client_endpoint,
822 remote_ip: server_endpoint,
823 peer_address: server_addr.to_owned(),
824 receive_buffer: VecDeque::new(),
825 paired_connection: Some(server_conn),
826 send_buffer: VecDeque::new(),
827 next_send_time: current_time,
828 flags: ConnectionFlags::default(),
829 cut_expiry: None,
830 close_reason: CloseReason::None,
831 send_buffer_capacity: DEFAULT_SEND_BUFFER_CAPACITY,
832 send_delay: None,
833 recv_delay: None,
834 half_open_error_at: None,
835 last_data_delivery_scheduled_at: None,
836 },
837 );
838
839 inner.network.connections.insert(
841 server_conn,
842 ConnectionState {
843 local_ip: server_endpoint,
844 remote_ip: client_endpoint,
845 peer_address: ephemeral_peer_addr,
846 receive_buffer: VecDeque::new(),
847 paired_connection: Some(client_conn),
848 send_buffer: VecDeque::new(),
849 next_send_time: current_time,
850 flags: ConnectionFlags::default(),
851 cut_expiry: None,
852 close_reason: CloseReason::None,
853 send_buffer_capacity: DEFAULT_SEND_BUFFER_CAPACITY,
854 send_delay: None,
855 recv_delay: None,
856 half_open_error_at: None,
857 last_data_delivery_scheduled_at: None,
858 },
859 );
860
861 (client_conn, server_conn)
862 }
863
864 pub(crate) fn register_read_waker(&self, connection_id: ConnectionId, waker: Waker) {
866 let mut inner = self
867 .inner
868 .write()
869 .expect("RwLock poisoned: prior task panicked");
870 let is_replacement = inner.wakers.reads.contains_key(&connection_id);
871 inner.wakers.reads.insert(connection_id, waker);
872 tracing::debug!(
873 "register_read_waker: connection_id={}, replacement={}, total_wakers={}",
874 connection_id.0,
875 is_replacement,
876 inner.wakers.reads.len()
877 );
878 }
879
880 pub(crate) fn register_accept_waker(&self, addr: &str, waker: Waker) {
882 use std::collections::hash_map::DefaultHasher;
883 use std::hash::{Hash, Hasher};
884
885 let mut inner = self
886 .inner
887 .write()
888 .expect("RwLock poisoned: prior task panicked");
889 let mut hasher = DefaultHasher::new();
891 addr.hash(&mut hasher);
892 let listener_key = ListenerId(hasher.finish());
893
894 inner.wakers.listeners.insert(listener_key, waker);
895 }
896
897 pub(crate) fn store_pending_connection(&self, addr: &str, connection_id: ConnectionId) {
899 use std::collections::hash_map::DefaultHasher;
900 use std::hash::{Hash, Hasher};
901
902 let mut inner = self
903 .inner
904 .write()
905 .expect("RwLock poisoned: prior task panicked");
906 inner
907 .network
908 .pending_connections
909 .insert(addr.to_string(), connection_id);
910
911 let mut hasher = DefaultHasher::new();
913 addr.hash(&mut hasher);
914 let listener_key = ListenerId(hasher.finish());
915
916 if let Some(waker) = inner.wakers.listeners.remove(&listener_key) {
917 waker.wake();
918 }
919 }
920
921 pub(crate) fn pending_connection(&self, addr: &str) -> Option<ConnectionId> {
923 let mut inner = self
924 .inner
925 .write()
926 .expect("RwLock poisoned: prior task panicked");
927 inner.network.pending_connections.remove(addr)
928 }
929
930 pub(crate) fn connection_peer_address(&self, connection_id: ConnectionId) -> Option<String> {
939 let inner = self
940 .inner
941 .read()
942 .expect("RwLock poisoned: prior task panicked");
943 inner
944 .network
945 .connections
946 .get(&connection_id)
947 .map(|conn| conn.peer_address.clone())
948 }
949
950 #[instrument(skip(self))]
955 pub fn sleep(&self, duration: Duration) -> SleepFuture {
956 let task_id = self.generate_task_id();
957
958 let actual_duration = self.apply_buggified_delay(duration);
960
961 self.schedule_event(Event::Timer { task_id }, actual_duration);
963
964 SleepFuture::new(self.downgrade(), task_id)
966 }
967
968 fn apply_buggified_delay(&self, duration: Duration) -> Duration {
970 let inner = self
971 .inner
972 .read()
973 .expect("RwLock poisoned: prior task panicked");
974 let chaos = &inner.network.config.chaos;
975
976 if !chaos.buggified_delay_enabled || chaos.buggified_delay_max == Duration::ZERO {
977 return duration;
978 }
979
980 if sim_random::<f64>() < chaos.buggified_delay_probability {
982 let random_factor = sim_random::<f64>().powf(1000.0);
984 let extra_delay = chaos.buggified_delay_max.mul_f64(random_factor);
985 tracing::trace!(
986 extra_delay_ms = extra_delay.as_millis(),
987 "Buggified delay applied"
988 );
989 duration + extra_delay
990 } else {
991 duration
992 }
993 }
994
995 fn generate_task_id(&self) -> u64 {
997 let mut inner = self
998 .inner
999 .write()
1000 .expect("RwLock poisoned: prior task panicked");
1001 let task_id = inner.next_task_id;
1002 inner.next_task_id += 1;
1003 task_id
1004 }
1005
1006 fn wake_all(wakers: &mut BTreeMap<ConnectionId, Vec<Waker>>, connection_id: ConnectionId) {
1008 if let Some(waker_list) = wakers.remove(&connection_id) {
1009 for waker in waker_list {
1010 waker.wake();
1011 }
1012 }
1013 }
1014
1015 pub(crate) fn is_task_awake(&self, task_id: u64) -> bool {
1017 let inner = self
1018 .inner
1019 .read()
1020 .expect("RwLock poisoned: prior task panicked");
1021 inner.awakened_tasks.contains(&task_id)
1022 }
1023
1024 pub(crate) fn register_task_waker(&self, task_id: u64, waker: Waker) {
1026 let mut inner = self
1027 .inner
1028 .write()
1029 .expect("RwLock poisoned: prior task panicked");
1030 inner.wakers.tasks.insert(task_id, waker);
1031 }
1032
1033 fn clear_expired_clogs_with_inner(inner: &mut SimInner) {
1035 let now = inner.current_time;
1036 let expired: Vec<ConnectionId> = inner
1037 .network
1038 .connection_clogs
1039 .iter()
1040 .filter_map(|(id, state)| (now >= state.expires_at).then_some(*id))
1041 .collect();
1042
1043 for id in expired {
1044 inner.network.connection_clogs.remove(&id);
1045 Self::wake_all(&mut inner.wakers.write_clogs, id);
1046 }
1047 }
1048
1049 #[instrument(skip(inner))]
1051 fn process_event_with_inner(inner: &mut SimInner, event: Event) {
1052 inner.events_processed += 1;
1053
1054 match event {
1055 Event::Timer { task_id } => Self::handle_timer_event(inner, task_id),
1056 Event::Connection { id, state } => Self::handle_connection_event(inner, id, state),
1057 Event::Network {
1058 connection_id,
1059 operation,
1060 } => Self::handle_network_event(inner, connection_id, operation),
1061 Event::Storage { file_id, operation } => {
1062 super::storage_ops::handle_storage_event(inner, file_id, operation);
1063 }
1064 Event::Shutdown => Self::handle_shutdown_event(inner),
1065 Event::ProcessRestart { ip }
1066 | Event::ProcessGracefulShutdown { ip, .. }
1067 | Event::ProcessForceKill { ip, .. } => {
1068 tracing::debug!("Process lifecycle event for IP {}", ip);
1071 }
1072 }
1073 }
1074
1075 fn handle_timer_event(inner: &mut SimInner, task_id: u64) {
1077 inner.awakened_tasks.insert(task_id);
1078 if let Some(waker) = inner.wakers.tasks.remove(&task_id) {
1079 waker.wake();
1080 }
1081 }
1082
1083 fn handle_connection_event(inner: &mut SimInner, id: u64, state: ConnectionStateChange) {
1085 let connection_id = ConnectionId(id);
1086
1087 match state {
1088 ConnectionStateChange::BindComplete | ConnectionStateChange::ConnectionReady => {
1089 }
1091 ConnectionStateChange::ClogClear => {
1092 inner.network.connection_clogs.remove(&connection_id);
1093 Self::wake_all(&mut inner.wakers.write_clogs, connection_id);
1094 }
1095 ConnectionStateChange::ReadClogClear => {
1096 inner.network.read_clogs.remove(&connection_id);
1097 Self::wake_all(&mut inner.wakers.read_clogs, connection_id);
1098 }
1099 ConnectionStateChange::PartitionRestore => {
1100 Self::clear_expired_partitions(inner);
1101 }
1102 ConnectionStateChange::SendPartitionClear => {
1103 Self::clear_expired_send_partitions(inner);
1104 }
1105 ConnectionStateChange::RecvPartitionClear => {
1106 Self::clear_expired_recv_partitions(inner);
1107 }
1108 ConnectionStateChange::CutRestore => {
1109 if let Some(conn) = inner.network.connections.get_mut(&connection_id)
1110 && conn.flags.is_cut()
1111 {
1112 conn.flags.set_is_cut(false);
1113 conn.cut_expiry = None;
1114 inner.record_fault(SimFaultEvent::CutRestored { connection_id: id });
1115 tracing::debug!("Connection {} restored via scheduled event", id);
1116 Self::wake_all(&mut inner.wakers.cuts, connection_id);
1117 }
1118 }
1119 ConnectionStateChange::HalfOpenError => {
1120 inner.record_fault(SimFaultEvent::HalfOpenError { connection_id: id });
1121 tracing::debug!("Connection {} half-open error time reached", id);
1122 if let Some(waker) = inner.wakers.reads.remove(&connection_id) {
1123 waker.wake();
1124 }
1125 Self::wake_all(&mut inner.wakers.send_buffers, connection_id);
1126 }
1127 }
1128 }
1129
1130 fn clear_expired_partitions(inner: &mut SimInner) {
1132 let now = inner.current_time;
1133 let expired: Vec<_> = inner
1134 .network
1135 .ip_partitions
1136 .iter()
1137 .filter_map(|(pair, state)| (now >= state.expires_at).then_some(*pair))
1138 .collect();
1139
1140 for pair in expired {
1141 inner.network.ip_partitions.remove(&pair);
1142 tracing::debug!("Restored IP partition {} -> {}", pair.0, pair.1);
1143 }
1144 }
1145
1146 fn clear_expired_send_partitions(inner: &mut SimInner) {
1148 let now = inner.current_time;
1149 let expired: Vec<_> = inner
1150 .network
1151 .send_partitions
1152 .iter()
1153 .filter_map(|(ip, &expires_at)| (now >= expires_at).then_some(*ip))
1154 .collect();
1155
1156 for ip in expired {
1157 inner.network.send_partitions.remove(&ip);
1158 tracing::debug!("Cleared send partition for {}", ip);
1159 }
1160 }
1161
1162 fn clear_expired_recv_partitions(inner: &mut SimInner) {
1164 let now = inner.current_time;
1165 let expired: Vec<_> = inner
1166 .network
1167 .recv_partitions
1168 .iter()
1169 .filter_map(|(ip, &expires_at)| (now >= expires_at).then_some(*ip))
1170 .collect();
1171
1172 for ip in expired {
1173 inner.network.recv_partitions.remove(&ip);
1174 tracing::debug!("Cleared receive partition for {}", ip);
1175 }
1176 }
1177
1178 fn handle_network_event(inner: &mut SimInner, conn_id: u64, operation: NetworkOperation) {
1180 let connection_id = ConnectionId(conn_id);
1181
1182 match operation {
1183 NetworkOperation::DataDelivery { data } => {
1184 Self::handle_data_delivery(inner, connection_id, data);
1185 }
1186 NetworkOperation::ProcessSendBuffer => {
1187 Self::handle_process_send_buffer(inner, connection_id);
1188 }
1189 NetworkOperation::FinDelivery => {
1190 Self::handle_fin_delivery(inner, connection_id);
1191 }
1192 }
1193 }
1194
1195 fn handle_data_delivery(inner: &mut SimInner, connection_id: ConnectionId, data: Vec<u8>) {
1197 tracing::trace!(
1198 "DataDelivery: {} bytes to connection {}",
1199 data.len(),
1200 connection_id.0
1201 );
1202
1203 let is_stable = inner
1205 .network
1206 .connections
1207 .get(&connection_id)
1208 .is_some_and(|conn| conn.flags.is_stable());
1209
1210 if !inner.network.connections.contains_key(&connection_id) {
1211 tracing::warn!("DataDelivery: connection {} not found", connection_id.0);
1212 return;
1213 }
1214
1215 let data_to_deliver = if is_stable {
1217 data
1218 } else {
1219 Self::maybe_corrupt_data(inner, connection_id, &data)
1220 };
1221
1222 let Some(conn) = inner.network.connections.get_mut(&connection_id) else {
1224 return;
1225 };
1226
1227 for &byte in &data_to_deliver {
1228 conn.receive_buffer.push_back(byte);
1229 }
1230
1231 if let Some(waker) = inner.wakers.reads.remove(&connection_id) {
1232 waker.wake();
1233 }
1234 }
1235
1236 fn handle_fin_delivery(inner: &mut SimInner, connection_id: ConnectionId) {
1242 tracing::debug!(
1243 "FinDelivery: FIN received on connection {}",
1244 connection_id.0
1245 );
1246
1247 let is_closed = inner
1249 .network
1250 .connections
1251 .get(&connection_id)
1252 .is_some_and(|conn| conn.flags.is_closed());
1253
1254 if is_closed {
1255 tracing::debug!(
1256 "FinDelivery: connection {} already closed, ignoring stale FIN",
1257 connection_id.0
1258 );
1259 return;
1260 }
1261
1262 if let Some(conn) = inner.network.connections.get_mut(&connection_id) {
1263 conn.flags.set_remote_fin_received(true);
1264 }
1265
1266 if let Some(waker) = inner.wakers.reads.remove(&connection_id) {
1267 waker.wake();
1268 }
1269 }
1270
1271 fn schedule_fin_delivery(
1276 inner: &mut SimInner,
1277 paired_id: Option<ConnectionId>,
1278 last_delivery_time: Option<Duration>,
1279 ) {
1280 let Some(peer_id) = paired_id else {
1281 return;
1282 };
1283
1284 let fin_time = match last_delivery_time {
1286 Some(t) if t >= inner.current_time => t + Duration::from_nanos(1),
1287 _ => inner.current_time + Duration::from_nanos(1),
1288 };
1289
1290 let sequence = inner.next_sequence;
1291 inner.next_sequence += 1;
1292
1293 tracing::debug!(
1294 "Scheduling FinDelivery to connection {} at {:?}",
1295 peer_id.0,
1296 fin_time
1297 );
1298
1299 inner.event_queue.schedule(ScheduledEvent::new(
1300 fin_time,
1301 Event::Network {
1302 connection_id: peer_id.0,
1303 operation: NetworkOperation::FinDelivery,
1304 },
1305 sequence,
1306 ));
1307 }
1308
1309 fn maybe_corrupt_data(
1311 inner: &mut SimInner,
1312 connection_id: ConnectionId,
1313 data: &[u8],
1314 ) -> Vec<u8> {
1315 if data.is_empty() {
1316 return data.to_vec();
1317 }
1318
1319 let chaos = &inner.network.config.chaos;
1320 let now = inner.current_time;
1321 let cooldown_elapsed =
1322 now.saturating_sub(inner.last_bit_flip_time) >= chaos.bit_flip_cooldown;
1323
1324 if !cooldown_elapsed || !crate::buggify_with_prob!(chaos.bit_flip_probability) {
1325 return data.to_vec();
1326 }
1327
1328 let random_value = sim_random::<u32>();
1329 let flip_count = SimInner::calculate_flip_bit_count(
1330 random_value,
1331 chaos.bit_flip_min_bits,
1332 chaos.bit_flip_max_bits,
1333 );
1334
1335 let mut corrupted_data = data.to_vec();
1336 let mut flipped_positions = std::collections::HashSet::new();
1337
1338 for _ in 0..flip_count {
1339 let raw_byte = sim_random::<u64>();
1341 let raw_bit = sim_random::<u64>();
1342 let len_u64 =
1343 u64::try_from(corrupted_data.len()).expect("corrupted_data length fits in u64");
1344 let byte_idx =
1345 usize::try_from(raw_byte % len_u64).expect("modulus bounded by usize length");
1346 let bit_idx = usize::try_from(raw_bit % 8).expect("modulus 8 fits in usize");
1347 let position = (byte_idx, bit_idx);
1348
1349 if !flipped_positions.contains(&position) {
1350 flipped_positions.insert(position);
1351 corrupted_data[byte_idx] ^= 1 << bit_idx;
1352 }
1353 }
1354
1355 inner.last_bit_flip_time = now;
1356 tracing::info!(
1357 "BitFlipInjected: bytes={} bits_flipped={} unique_positions={}",
1358 data.len(),
1359 flip_count,
1360 flipped_positions.len()
1361 );
1362
1363 inner.record_fault(SimFaultEvent::BitFlip {
1364 connection_id: connection_id.0,
1365 flip_count: flipped_positions.len(),
1366 });
1367
1368 corrupted_data
1369 }
1370
1371 fn handle_process_send_buffer(inner: &mut SimInner, connection_id: ConnectionId) {
1373 let is_partitioned = inner
1374 .network
1375 .is_connection_partitioned(connection_id, inner.current_time);
1376
1377 if is_partitioned {
1378 Self::handle_partitioned_send(inner, connection_id);
1379 } else {
1380 Self::handle_normal_send(inner, connection_id);
1381 }
1382 }
1383
1384 fn handle_partitioned_send(inner: &mut SimInner, connection_id: ConnectionId) {
1386 let Some(conn) = inner.network.connections.get_mut(&connection_id) else {
1387 return;
1388 };
1389
1390 if let Some(data) = conn.send_buffer.pop_front() {
1391 tracing::debug!(
1392 "Connection {} partitioned, failing send of {} bytes",
1393 connection_id.0,
1394 data.len()
1395 );
1396 Self::wake_all(&mut inner.wakers.send_buffers, connection_id);
1397
1398 if conn.send_buffer.is_empty() {
1399 conn.flags.set_send_in_progress(false);
1400 if conn.flags.graceful_close_pending() {
1402 conn.flags.set_graceful_close_pending(false);
1403 let peer_id = conn.paired_connection;
1404 let last_time = conn.last_data_delivery_scheduled_at;
1405 Self::schedule_fin_delivery(inner, peer_id, last_time);
1406 }
1407 } else {
1408 Self::schedule_process_send_buffer(inner, connection_id);
1409 }
1410 } else {
1411 conn.flags.set_send_in_progress(false);
1412 if conn.flags.graceful_close_pending() {
1414 conn.flags.set_graceful_close_pending(false);
1415 let peer_id = conn.paired_connection;
1416 let last_time = conn.last_data_delivery_scheduled_at;
1417 Self::schedule_fin_delivery(inner, peer_id, last_time);
1418 }
1419 }
1420 }
1421
1422 fn handle_normal_send(inner: &mut SimInner, connection_id: ConnectionId) {
1424 let Some(conn) = inner.network.connections.get(&connection_id) else {
1426 return;
1427 };
1428
1429 let paired_id = conn.paired_connection;
1430 let send_delay = conn.send_delay;
1431 let next_send_time = conn.next_send_time;
1432 let has_data = !conn.send_buffer.is_empty();
1433 let is_stable = conn.flags.is_stable(); let local_ip = conn.local_ip;
1435 let remote_ip = conn.remote_ip;
1436
1437 let recv_delay = paired_id.and_then(|pid| {
1438 inner
1439 .network
1440 .connections
1441 .get(&pid)
1442 .and_then(|c| c.recv_delay)
1443 });
1444
1445 let pair_extra = match (local_ip, remote_ip) {
1449 (Some(src), Some(dst)) => inner
1450 .network
1451 .pair_latencies
1452 .get(&(src, dst))
1453 .copied()
1454 .unwrap_or(Duration::ZERO),
1455 _ => Duration::ZERO,
1456 };
1457
1458 if !has_data {
1459 if let Some(conn) = inner.network.connections.get_mut(&connection_id) {
1460 conn.flags.set_send_in_progress(false);
1461 if conn.flags.graceful_close_pending() {
1463 conn.flags.set_graceful_close_pending(false);
1464 let peer_id = conn.paired_connection;
1465 let last_time = conn.last_data_delivery_scheduled_at;
1466 Self::schedule_fin_delivery(inner, peer_id, last_time);
1467 }
1468 }
1469 return;
1470 }
1471
1472 let Some(conn) = inner.network.connections.get_mut(&connection_id) else {
1473 return;
1474 };
1475
1476 let Some(mut data) = conn.send_buffer.pop_front() else {
1477 conn.flags.set_send_in_progress(false);
1478 return;
1479 };
1480
1481 Self::wake_all(&mut inner.wakers.send_buffers, connection_id);
1482
1483 if !is_stable && crate::buggify!() && !data.is_empty() {
1485 let max_send = std::cmp::min(
1486 data.len(),
1487 inner.network.config.chaos.partial_write_max_bytes,
1488 );
1489 let truncate_to = sim_random_range(0..max_send + 1);
1490
1491 if truncate_to < data.len() {
1492 let remainder = data.split_off(truncate_to);
1493 conn.send_buffer.push_front(remainder);
1494 tracing::debug!(
1495 "BUGGIFY: Partial write on connection {} - sending {} bytes",
1496 connection_id.0,
1497 data.len()
1498 );
1499 }
1500 }
1501
1502 let has_more = !conn.send_buffer.is_empty();
1503 let base_delay = if has_more {
1504 Duration::from_nanos(1)
1505 } else {
1506 send_delay.unwrap_or_else(|| {
1507 crate::network::sample_latency(&inner.network.config.write_latency)
1508 })
1509 };
1510
1511 let earliest_time = std::cmp::max(inner.current_time + base_delay, next_send_time);
1512 conn.next_send_time = earliest_time + Duration::from_nanos(1);
1513
1514 if let Some(paired_id) = paired_id {
1516 let scheduled_time = earliest_time + recv_delay.unwrap_or(Duration::ZERO) + pair_extra;
1517 let sequence = inner.next_sequence;
1518 inner.next_sequence += 1;
1519
1520 inner.event_queue.schedule(ScheduledEvent::new(
1521 scheduled_time,
1522 Event::Network {
1523 connection_id: paired_id.0,
1524 operation: NetworkOperation::DataDelivery { data },
1525 },
1526 sequence,
1527 ));
1528
1529 conn.last_data_delivery_scheduled_at = Some(scheduled_time);
1531 }
1532
1533 if conn.send_buffer.is_empty() {
1535 conn.flags.set_send_in_progress(false);
1536 if conn.flags.graceful_close_pending() {
1538 conn.flags.set_graceful_close_pending(false);
1539 let peer_id = conn.paired_connection;
1540 let last_time = conn.last_data_delivery_scheduled_at;
1541 Self::schedule_fin_delivery(inner, peer_id, last_time);
1542 }
1543 } else {
1544 Self::schedule_process_send_buffer(inner, connection_id);
1545 }
1546 }
1547
1548 fn schedule_process_send_buffer(inner: &mut SimInner, connection_id: ConnectionId) {
1550 let sequence = inner.next_sequence;
1551 inner.next_sequence += 1;
1552
1553 inner.event_queue.schedule(ScheduledEvent::new(
1554 inner.current_time,
1555 Event::Network {
1556 connection_id: connection_id.0,
1557 operation: NetworkOperation::ProcessSendBuffer,
1558 },
1559 sequence,
1560 ));
1561 }
1562
1563 fn handle_shutdown_event(inner: &mut SimInner) {
1565 tracing::debug!("Processing Shutdown event - waking all pending tasks");
1566
1567 for (task_id, waker) in std::mem::take(&mut inner.wakers.tasks) {
1568 tracing::trace!("Waking task {}", task_id);
1569 waker.wake();
1570 }
1571
1572 for (_conn_id, waker) in std::mem::take(&mut inner.wakers.reads) {
1573 waker.wake();
1574 }
1575
1576 tracing::debug!("Shutdown event processed");
1577 }
1578
1579 #[must_use]
1581 pub fn assertion_results(
1582 &self,
1583 ) -> std::collections::HashMap<String, crate::chaos::AssertionStats> {
1584 crate::chaos::assertion_results()
1585 }
1586
1587 pub fn reset_assertion_results(&self) {
1589 crate::chaos::reset_assertion_results();
1590 }
1591
1592 pub fn abort_all_connections_for_ip(&self, ip: std::net::IpAddr) {
1602 let connection_ids: Vec<ConnectionId> = {
1603 let inner = self
1604 .inner
1605 .read()
1606 .expect("RwLock poisoned: prior task panicked");
1607 inner
1608 .network
1609 .connections
1610 .iter()
1611 .filter_map(|(id, conn)| {
1612 if conn.local_ip == Some(ip) || conn.remote_ip == Some(ip) {
1613 Some(*id)
1614 } else {
1615 None
1616 }
1617 })
1618 .collect()
1619 };
1620
1621 let count = connection_ids.len();
1622 for conn_id in connection_ids {
1623 self.close_connection_abort(conn_id);
1624 }
1625
1626 if count > 0 {
1627 tracing::debug!("Aborted {} connections for rebooted IP {}", count, ip);
1628 }
1629 }
1630
1631 pub fn schedule_process_restart(
1635 &self,
1636 ip: std::net::IpAddr,
1637 recovery_delay: std::time::Duration,
1638 ) {
1639 self.schedule_event(Event::ProcessRestart { ip }, recovery_delay);
1640 tracing::debug!(
1641 "Scheduled process restart for IP {} in {:?}",
1642 ip,
1643 recovery_delay
1644 );
1645 }
1646
1647 #[must_use]
1656 pub fn last_processed_event(&self) -> Option<Event> {
1657 self.inner
1658 .read()
1659 .expect("RwLock poisoned: prior task panicked")
1660 .last_processed_event
1661 .clone()
1662 }
1663
1664 #[must_use]
1670 pub fn extract_metrics(&self) -> crate::runner::SimulationMetrics {
1671 let inner = self
1672 .inner
1673 .read()
1674 .expect("RwLock poisoned: prior task panicked");
1675
1676 crate::runner::SimulationMetrics {
1677 wall_time: std::time::Duration::ZERO,
1678 simulated_time: inner.current_time,
1679 events_processed: inner.events_processed,
1680 }
1681 }
1682
1683 #[must_use]
1691 pub fn should_clog_write(&self, connection_id: ConnectionId) -> bool {
1692 let inner = self
1693 .inner
1694 .read()
1695 .expect("RwLock poisoned: prior task panicked");
1696 let config = &inner.network.config;
1697
1698 if inner
1700 .network
1701 .connections
1702 .get(&connection_id)
1703 .is_some_and(|conn| conn.flags.is_stable())
1704 {
1705 return false;
1706 }
1707
1708 if let Some(clog_state) = inner.network.connection_clogs.get(&connection_id) {
1710 return inner.current_time < clog_state.expires_at;
1711 }
1712
1713 config.chaos.clog_probability > 0.0 && sim_random::<f64>() < config.chaos.clog_probability
1715 }
1716
1717 pub fn clog_write(&self, connection_id: ConnectionId) {
1723 let mut inner = self
1724 .inner
1725 .write()
1726 .expect("RwLock poisoned: prior task panicked");
1727 let config = &inner.network.config;
1728
1729 let clog_duration = crate::network::sample_duration(&config.chaos.clog_duration);
1730 let expires_at = inner.current_time + clog_duration;
1731 inner
1732 .network
1733 .connection_clogs
1734 .insert(connection_id, ClogState { expires_at });
1735
1736 let clear_event = Event::Connection {
1738 id: connection_id.0,
1739 state: ConnectionStateChange::ClogClear,
1740 };
1741 let sequence = inner.next_sequence;
1742 inner.next_sequence += 1;
1743 inner
1744 .event_queue
1745 .schedule(ScheduledEvent::new(expires_at, clear_event, sequence));
1746 }
1747
1748 #[must_use]
1754 pub fn is_write_clogged(&self, connection_id: ConnectionId) -> bool {
1755 let inner = self
1756 .inner
1757 .read()
1758 .expect("RwLock poisoned: prior task panicked");
1759
1760 if let Some(clog_state) = inner.network.connection_clogs.get(&connection_id) {
1761 inner.current_time < clog_state.expires_at
1762 } else {
1763 false
1764 }
1765 }
1766
1767 pub fn register_clog_waker(&self, connection_id: ConnectionId, waker: Waker) {
1773 let mut inner = self
1774 .inner
1775 .write()
1776 .expect("RwLock poisoned: prior task panicked");
1777 inner
1778 .wakers
1779 .write_clogs
1780 .entry(connection_id)
1781 .or_default()
1782 .push(waker);
1783 }
1784
1785 #[must_use]
1793 pub fn should_clog_read(&self, connection_id: ConnectionId) -> bool {
1794 let inner = self
1795 .inner
1796 .read()
1797 .expect("RwLock poisoned: prior task panicked");
1798 let config = &inner.network.config;
1799
1800 if inner
1802 .network
1803 .connections
1804 .get(&connection_id)
1805 .is_some_and(|conn| conn.flags.is_stable())
1806 {
1807 return false;
1808 }
1809
1810 if let Some(clog_state) = inner.network.read_clogs.get(&connection_id) {
1812 return inner.current_time < clog_state.expires_at;
1813 }
1814
1815 config.chaos.clog_probability > 0.0 && sim_random::<f64>() < config.chaos.clog_probability
1817 }
1818
1819 pub fn clog_read(&self, connection_id: ConnectionId) {
1825 let mut inner = self
1826 .inner
1827 .write()
1828 .expect("RwLock poisoned: prior task panicked");
1829 let config = &inner.network.config;
1830
1831 let clog_duration = crate::network::sample_duration(&config.chaos.clog_duration);
1832 let expires_at = inner.current_time + clog_duration;
1833 inner
1834 .network
1835 .read_clogs
1836 .insert(connection_id, ClogState { expires_at });
1837
1838 let clear_event = Event::Connection {
1840 id: connection_id.0,
1841 state: ConnectionStateChange::ReadClogClear,
1842 };
1843 let sequence = inner.next_sequence;
1844 inner.next_sequence += 1;
1845 inner
1846 .event_queue
1847 .schedule(ScheduledEvent::new(expires_at, clear_event, sequence));
1848 }
1849
1850 #[must_use]
1856 pub fn is_read_clogged(&self, connection_id: ConnectionId) -> bool {
1857 let inner = self
1858 .inner
1859 .read()
1860 .expect("RwLock poisoned: prior task panicked");
1861
1862 if let Some(clog_state) = inner.network.read_clogs.get(&connection_id) {
1863 inner.current_time < clog_state.expires_at
1864 } else {
1865 false
1866 }
1867 }
1868
1869 pub fn register_read_clog_waker(&self, connection_id: ConnectionId, waker: Waker) {
1875 let mut inner = self
1876 .inner
1877 .write()
1878 .expect("RwLock poisoned: prior task panicked");
1879 inner
1880 .wakers
1881 .read_clogs
1882 .entry(connection_id)
1883 .or_default()
1884 .push(waker);
1885 }
1886
1887 pub fn clear_expired_clogs(&self) {
1893 let mut inner = self
1894 .inner
1895 .write()
1896 .expect("RwLock poisoned: prior task panicked");
1897 let now = inner.current_time;
1898 let expired: Vec<ConnectionId> = inner
1899 .network
1900 .connection_clogs
1901 .iter()
1902 .filter_map(|(id, state)| (now >= state.expires_at).then_some(*id))
1903 .collect();
1904
1905 for id in expired {
1906 inner.network.connection_clogs.remove(&id);
1907 Self::wake_all(&mut inner.wakers.write_clogs, id);
1908 }
1909 }
1910
1911 #[must_use]
1922 pub fn is_connection_cut(&self, connection_id: ConnectionId) -> bool {
1923 let inner = self
1924 .inner
1925 .read()
1926 .expect("RwLock poisoned: prior task panicked");
1927 inner
1928 .network
1929 .connections
1930 .get(&connection_id)
1931 .is_some_and(|conn| {
1932 conn.flags.is_cut()
1933 && conn
1934 .cut_expiry
1935 .is_some_and(|expiry| inner.current_time < expiry)
1936 })
1937 }
1938
1939 pub fn register_cut_waker(&self, connection_id: ConnectionId, waker: Waker) {
1945 let mut inner = self
1946 .inner
1947 .write()
1948 .expect("RwLock poisoned: prior task panicked");
1949 inner
1950 .wakers
1951 .cuts
1952 .entry(connection_id)
1953 .or_default()
1954 .push(waker);
1955 }
1956
1957 #[must_use]
1965 pub fn send_buffer_capacity(&self, connection_id: ConnectionId) -> usize {
1966 let inner = self
1967 .inner
1968 .read()
1969 .expect("RwLock poisoned: prior task panicked");
1970 inner
1971 .network
1972 .connections
1973 .get(&connection_id)
1974 .map_or(0, |conn| conn.send_buffer_capacity)
1975 }
1976
1977 #[must_use]
1983 pub fn send_buffer_used(&self, connection_id: ConnectionId) -> usize {
1984 let inner = self
1985 .inner
1986 .read()
1987 .expect("RwLock poisoned: prior task panicked");
1988 inner
1989 .network
1990 .connections
1991 .get(&connection_id)
1992 .map_or(0, |conn| {
1993 conn.send_buffer.iter().map(std::vec::Vec::len).sum()
1994 })
1995 }
1996
1997 #[must_use]
1999 pub fn available_send_buffer(&self, connection_id: ConnectionId) -> usize {
2000 let capacity = self.send_buffer_capacity(connection_id);
2001 let used = self.send_buffer_used(connection_id);
2002 capacity.saturating_sub(used)
2003 }
2004
2005 pub fn register_send_buffer_waker(&self, connection_id: ConnectionId, waker: Waker) {
2011 let mut inner = self
2012 .inner
2013 .write()
2014 .expect("RwLock poisoned: prior task panicked");
2015 inner
2016 .wakers
2017 .send_buffers
2018 .entry(connection_id)
2019 .or_default()
2020 .push(waker);
2021 }
2022
2023 #[must_use]
2032 pub fn pair_latency(&self, src: IpAddr, dst: IpAddr) -> Option<Duration> {
2033 let inner = self
2034 .inner
2035 .read()
2036 .expect("RwLock poisoned: prior task panicked");
2037 inner.network.pair_latencies.get(&(src, dst)).copied()
2038 }
2039
2040 #[must_use]
2047 pub fn set_pair_latency_if_not_set(
2048 &self,
2049 src: IpAddr,
2050 dst: IpAddr,
2051 latency: Duration,
2052 ) -> Duration {
2053 let mut inner = self
2054 .inner
2055 .write()
2056 .expect("RwLock poisoned: prior task panicked");
2057 *inner
2058 .network
2059 .pair_latencies
2060 .entry((src, dst))
2061 .or_insert_with(|| {
2062 tracing::debug!(
2063 "Setting base latency for IP pair {} -> {} to {:?}",
2064 src,
2065 dst,
2066 latency
2067 );
2068 latency
2069 })
2070 }
2071
2072 #[must_use]
2084 pub fn connection_base_latency(&self, connection_id: ConnectionId) -> Duration {
2085 let range = self.with_network_config(|config| config.chaos.max_pair_latency.clone());
2086 if range.end.is_zero() {
2087 return Duration::ZERO;
2088 }
2089
2090 let inner = self
2091 .inner
2092 .read()
2093 .expect("RwLock poisoned: prior task panicked");
2094 let endpoints = inner
2095 .network
2096 .connections
2097 .get(&connection_id)
2098 .and_then(|conn| Some((conn.local_ip?, conn.remote_ip?)));
2099 drop(inner);
2100
2101 let Some((local_ip, remote_ip)) = endpoints else {
2102 return Duration::ZERO;
2103 };
2104
2105 if let Some(latency) = self.pair_latency(local_ip, remote_ip) {
2107 return latency;
2108 }
2109
2110 let latency = crate::network::sample_duration(&range);
2112 self.set_pair_latency_if_not_set(local_ip, remote_ip, latency)
2113 }
2114
2115 #[must_use]
2124 pub fn send_delay(&self, connection_id: ConnectionId) -> Option<Duration> {
2125 let inner = self
2126 .inner
2127 .read()
2128 .expect("RwLock poisoned: prior task panicked");
2129 inner
2130 .network
2131 .connections
2132 .get(&connection_id)
2133 .and_then(|conn| conn.send_delay)
2134 }
2135
2136 #[must_use]
2143 pub fn recv_delay(&self, connection_id: ConnectionId) -> Option<Duration> {
2144 let inner = self
2145 .inner
2146 .read()
2147 .expect("RwLock poisoned: prior task panicked");
2148 inner
2149 .network
2150 .connections
2151 .get(&connection_id)
2152 .and_then(|conn| conn.recv_delay)
2153 }
2154
2155 #[must_use]
2161 pub fn is_connection_closed(&self, connection_id: ConnectionId) -> bool {
2162 let inner = self
2163 .inner
2164 .read()
2165 .expect("RwLock poisoned: prior task panicked");
2166 inner
2167 .network
2168 .connections
2169 .get(&connection_id)
2170 .is_some_and(|conn| conn.flags.is_closed())
2171 }
2172
2173 pub fn close_connection(&self, connection_id: ConnectionId) {
2177 self.close_connection_with_reason(connection_id, CloseReason::Graceful);
2178 }
2179
2180 pub fn close_connection_abort(&self, connection_id: ConnectionId) {
2184 self.close_connection_with_reason(connection_id, CloseReason::Aborted);
2185 }
2186
2187 #[must_use]
2193 pub fn close_reason(&self, connection_id: ConnectionId) -> CloseReason {
2194 let inner = self
2195 .inner
2196 .read()
2197 .expect("RwLock poisoned: prior task panicked");
2198 inner
2199 .network
2200 .connections
2201 .get(&connection_id)
2202 .map_or(CloseReason::None, |conn| conn.close_reason)
2203 }
2204
2205 fn close_connection_with_reason(&self, connection_id: ConnectionId, reason: CloseReason) {
2207 match reason {
2208 CloseReason::Graceful => self.close_connection_graceful(connection_id),
2209 CloseReason::Aborted => self.close_connection_aborted(connection_id),
2210 CloseReason::None => {}
2211 }
2212 }
2213
2214 fn close_connection_graceful(&self, connection_id: ConnectionId) {
2220 let mut inner = self
2221 .inner
2222 .write()
2223 .expect("RwLock poisoned: prior task panicked");
2224
2225 let conn_info = inner.network.connections.get(&connection_id).map(|conn| {
2227 (
2228 conn.paired_connection,
2229 conn.flags.send_closed(),
2230 conn.flags.is_closed(),
2231 conn.flags.send_in_progress(),
2232 conn.send_buffer.is_empty(),
2233 conn.last_data_delivery_scheduled_at,
2234 )
2235 });
2236
2237 let Some((
2238 paired_id,
2239 was_send_closed,
2240 was_closed,
2241 send_in_progress,
2242 send_buffer_empty,
2243 last_delivery_time,
2244 )) = conn_info
2245 else {
2246 return;
2247 };
2248
2249 if was_closed || was_send_closed {
2251 tracing::debug!(
2252 "Connection {} already closed/send_closed, skipping graceful close",
2253 connection_id.0
2254 );
2255 return;
2256 }
2257
2258 if let Some(conn) = inner.network.connections.get_mut(&connection_id) {
2260 conn.flags.set_is_closed(true);
2261 conn.close_reason = CloseReason::Graceful;
2262 conn.flags.set_send_closed(true);
2263 tracing::debug!(
2264 "Connection {} graceful close (FIN) - local write shut down",
2265 connection_id.0
2266 );
2267 }
2268
2269 if let Some(waker) = inner.wakers.reads.remove(&connection_id) {
2271 waker.wake();
2272 }
2273
2274 if send_in_progress || !send_buffer_empty {
2277 if let Some(conn) = inner.network.connections.get_mut(&connection_id) {
2279 conn.flags.set_graceful_close_pending(true);
2280 tracing::debug!(
2281 "Connection {} graceful close deferred (send pipeline active)",
2282 connection_id.0
2283 );
2284 }
2285 } else {
2286 Self::schedule_fin_delivery(&mut inner, paired_id, last_delivery_time);
2288 }
2289 }
2290
2291 fn close_connection_aborted(&self, connection_id: ConnectionId) {
2296 let mut inner = self
2297 .inner
2298 .write()
2299 .expect("RwLock poisoned: prior task panicked");
2300
2301 let paired_connection_id = inner
2302 .network
2303 .connections
2304 .get(&connection_id)
2305 .and_then(|conn| conn.paired_connection);
2306
2307 if let Some(conn) = inner.network.connections.get_mut(&connection_id)
2308 && !conn.flags.is_closed()
2309 {
2310 conn.flags.set_is_closed(true);
2311 conn.close_reason = CloseReason::Aborted;
2312 conn.flags.set_graceful_close_pending(false);
2314 tracing::debug!(
2315 "Connection {} closed permanently (reason: Aborted)",
2316 connection_id.0
2317 );
2318 }
2319
2320 if let Some(paired_id) = paired_connection_id
2321 && let Some(paired_conn) = inner.network.connections.get_mut(&paired_id)
2322 && !paired_conn.flags.is_closed()
2323 {
2324 paired_conn.flags.set_is_closed(true);
2325 paired_conn.close_reason = CloseReason::Aborted;
2326 tracing::debug!(
2327 "Paired connection {} also closed (reason: Aborted)",
2328 paired_id.0
2329 );
2330 }
2331
2332 if let Some(waker) = inner.wakers.reads.remove(&connection_id) {
2333 tracing::debug!(
2334 "Waking read waker for aborted connection {}",
2335 connection_id.0
2336 );
2337 waker.wake();
2338 }
2339
2340 if let Some(paired_id) = paired_connection_id
2341 && let Some(paired_waker) = inner.wakers.reads.remove(&paired_id)
2342 {
2343 tracing::debug!(
2344 "Waking read waker for paired aborted connection {}",
2345 paired_id.0
2346 );
2347 paired_waker.wake();
2348 }
2349 }
2350
2351 pub fn close_connection_asymmetric(
2357 &self,
2358 connection_id: ConnectionId,
2359 close_send: bool,
2360 close_recv: bool,
2361 ) {
2362 let mut inner = self
2363 .inner
2364 .write()
2365 .expect("RwLock poisoned: prior task panicked");
2366
2367 let paired_id = inner
2368 .network
2369 .connections
2370 .get(&connection_id)
2371 .and_then(|conn| conn.paired_connection);
2372
2373 if close_send && let Some(conn) = inner.network.connections.get_mut(&connection_id) {
2374 conn.flags.set_send_closed(true);
2375 conn.send_buffer.clear();
2376 tracing::debug!(
2377 "Connection {} send side closed (asymmetric)",
2378 connection_id.0
2379 );
2380 }
2381
2382 if close_recv
2383 && let Some(paired) = paired_id
2384 && let Some(paired_conn) = inner.network.connections.get_mut(&paired)
2385 {
2386 paired_conn.flags.set_recv_closed(true);
2387 tracing::debug!(
2388 "Connection {} recv side closed (asymmetric via peer)",
2389 paired.0
2390 );
2391 }
2392
2393 if close_send && let Some(waker) = inner.wakers.reads.remove(&connection_id) {
2394 waker.wake();
2395 }
2396 if close_recv
2397 && let Some(paired) = paired_id
2398 && let Some(waker) = inner.wakers.reads.remove(&paired)
2399 {
2400 waker.wake();
2401 }
2402 }
2403
2404 pub fn roll_random_close(&self, connection_id: ConnectionId) -> Option<bool> {
2410 let mut inner = self
2411 .inner
2412 .write()
2413 .expect("RwLock poisoned: prior task panicked");
2414 let config = &inner.network.config;
2415
2416 if inner
2418 .network
2419 .connections
2420 .get(&connection_id)
2421 .is_some_and(|conn| conn.flags.is_stable())
2422 {
2423 return None;
2424 }
2425
2426 if config.chaos.random_close_probability <= 0.0 {
2427 return None;
2428 }
2429
2430 let current_time = inner.current_time;
2431 let time_since_last = current_time.saturating_sub(inner.network.last_random_close_time);
2432 if time_since_last < config.chaos.random_close_cooldown {
2433 return None;
2434 }
2435
2436 if !crate::buggify_with_prob!(config.chaos.random_close_probability) {
2437 return None;
2438 }
2439
2440 inner.network.last_random_close_time = current_time;
2441
2442 inner.record_fault(SimFaultEvent::RandomClose {
2443 connection_id: connection_id.0,
2444 });
2445
2446 let paired_id = inner
2447 .network
2448 .connections
2449 .get(&connection_id)
2450 .and_then(|conn| conn.paired_connection);
2451
2452 let a = super::rng::sim_random_f64();
2453 let close_recv = a < 0.66;
2454 let close_send = a > 0.33;
2455
2456 tracing::info!(
2457 "Random connection failure triggered on connection {} (send={}, recv={}, a={:.3})",
2458 connection_id.0,
2459 close_send,
2460 close_recv,
2461 a
2462 );
2463
2464 if close_send && let Some(conn) = inner.network.connections.get_mut(&connection_id) {
2465 conn.flags.set_send_closed(true);
2466 conn.send_buffer.clear();
2467 }
2468
2469 if close_recv
2470 && let Some(paired) = paired_id
2471 && let Some(paired_conn) = inner.network.connections.get_mut(&paired)
2472 {
2473 paired_conn.flags.set_recv_closed(true);
2474 }
2475
2476 if close_send && let Some(waker) = inner.wakers.reads.remove(&connection_id) {
2477 waker.wake();
2478 }
2479 if close_recv
2480 && let Some(paired) = paired_id
2481 && let Some(waker) = inner.wakers.reads.remove(&paired)
2482 {
2483 waker.wake();
2484 }
2485
2486 let b = super::rng::sim_random_f64();
2487 let explicit = b < inner.network.config.chaos.random_close_explicit_ratio;
2488
2489 tracing::debug!(
2490 "Random close explicit={} (b={:.3}, ratio={:.2})",
2491 explicit,
2492 b,
2493 inner.network.config.chaos.random_close_explicit_ratio
2494 );
2495
2496 Some(explicit)
2497 }
2498
2499 #[must_use]
2505 pub fn is_send_closed(&self, connection_id: ConnectionId) -> bool {
2506 let inner = self
2507 .inner
2508 .read()
2509 .expect("RwLock poisoned: prior task panicked");
2510 inner
2511 .network
2512 .connections
2513 .get(&connection_id)
2514 .is_some_and(|conn| conn.flags.send_closed() || conn.flags.is_closed())
2515 }
2516
2517 #[must_use]
2523 pub fn is_recv_closed(&self, connection_id: ConnectionId) -> bool {
2524 let inner = self
2525 .inner
2526 .read()
2527 .expect("RwLock poisoned: prior task panicked");
2528 inner
2529 .network
2530 .connections
2531 .get(&connection_id)
2532 .is_some_and(|conn| conn.flags.recv_closed() || conn.flags.is_closed())
2533 }
2534
2535 #[must_use]
2544 pub fn is_remote_fin_received(&self, connection_id: ConnectionId) -> bool {
2545 let inner = self
2546 .inner
2547 .read()
2548 .expect("RwLock poisoned: prior task panicked");
2549 inner
2550 .network
2551 .connections
2552 .get(&connection_id)
2553 .is_some_and(|conn| conn.flags.remote_fin_received())
2554 }
2555
2556 #[must_use]
2564 pub fn is_half_open(&self, connection_id: ConnectionId) -> bool {
2565 let inner = self
2566 .inner
2567 .read()
2568 .expect("RwLock poisoned: prior task panicked");
2569 inner
2570 .network
2571 .connections
2572 .get(&connection_id)
2573 .is_some_and(|conn| conn.flags.is_half_open())
2574 }
2575
2576 #[must_use]
2582 pub fn should_half_open_error(&self, connection_id: ConnectionId) -> bool {
2583 let inner = self
2584 .inner
2585 .read()
2586 .expect("RwLock poisoned: prior task panicked");
2587 let current_time = inner.current_time;
2588 inner
2589 .network
2590 .connections
2591 .get(&connection_id)
2592 .is_some_and(|conn| {
2593 conn.flags.is_half_open()
2594 && conn
2595 .half_open_error_at
2596 .is_some_and(|error_at| current_time >= error_at)
2597 })
2598 }
2599
2600 pub fn mark_connection_stable(&self, connection_id: ConnectionId) {
2621 let mut inner = self
2622 .inner
2623 .write()
2624 .expect("RwLock poisoned: prior task panicked");
2625 if let Some(conn) = inner.network.connections.get_mut(&connection_id) {
2626 conn.flags.set_is_stable(true);
2627 tracing::debug!("Connection {} marked as stable", connection_id.0);
2628
2629 if let Some(paired_id) = conn.paired_connection
2631 && let Some(paired_conn) = inner.network.connections.get_mut(&paired_id)
2632 {
2633 paired_conn.flags.set_is_stable(true);
2634 tracing::debug!("Paired connection {} also marked as stable", paired_id.0);
2635 }
2636 }
2637 }
2638
2639 pub fn partition_pair(
2651 &self,
2652 from_ip: std::net::IpAddr,
2653 to_ip: std::net::IpAddr,
2654 duration: Duration,
2655 ) -> SimulationResult<()> {
2656 let mut inner = self
2657 .inner
2658 .write()
2659 .expect("RwLock poisoned: prior task panicked");
2660 let expires_at = inner.current_time + duration;
2661
2662 inner
2663 .network
2664 .ip_partitions
2665 .insert((from_ip, to_ip), PartitionState { expires_at });
2666
2667 let restore_event = Event::Connection {
2668 id: 0,
2669 state: ConnectionStateChange::PartitionRestore,
2670 };
2671 let sequence = inner.next_sequence;
2672 inner.next_sequence += 1;
2673 let scheduled_event = ScheduledEvent::new(expires_at, restore_event, sequence);
2674 inner.event_queue.schedule(scheduled_event);
2675
2676 inner.record_fault(SimFaultEvent::PartitionCreated {
2677 from: from_ip.to_string(),
2678 to: to_ip.to_string(),
2679 });
2680
2681 tracing::debug!(
2682 "Partitioned {} -> {} until {:?}",
2683 from_ip,
2684 to_ip,
2685 expires_at
2686 );
2687 Ok(())
2688 }
2689
2690 pub fn partition_send_from(
2700 &self,
2701 ip: std::net::IpAddr,
2702 duration: Duration,
2703 ) -> SimulationResult<()> {
2704 let mut inner = self
2705 .inner
2706 .write()
2707 .expect("RwLock poisoned: prior task panicked");
2708 let expires_at = inner.current_time + duration;
2709
2710 inner.network.send_partitions.insert(ip, expires_at);
2711
2712 let clear_event = Event::Connection {
2713 id: 0,
2714 state: ConnectionStateChange::SendPartitionClear,
2715 };
2716 let sequence = inner.next_sequence;
2717 inner.next_sequence += 1;
2718 let scheduled_event = ScheduledEvent::new(expires_at, clear_event, sequence);
2719 inner.event_queue.schedule(scheduled_event);
2720
2721 inner.record_fault(SimFaultEvent::SendPartitionCreated { ip: ip.to_string() });
2722 tracing::debug!("Partitioned sends from {} until {:?}", ip, expires_at);
2723 Ok(())
2724 }
2725
2726 pub fn partition_recv_to(
2736 &self,
2737 ip: std::net::IpAddr,
2738 duration: Duration,
2739 ) -> SimulationResult<()> {
2740 let mut inner = self
2741 .inner
2742 .write()
2743 .expect("RwLock poisoned: prior task panicked");
2744 let expires_at = inner.current_time + duration;
2745
2746 inner.network.recv_partitions.insert(ip, expires_at);
2747
2748 let clear_event = Event::Connection {
2749 id: 0,
2750 state: ConnectionStateChange::RecvPartitionClear,
2751 };
2752 let sequence = inner.next_sequence;
2753 inner.next_sequence += 1;
2754 let scheduled_event = ScheduledEvent::new(expires_at, clear_event, sequence);
2755 inner.event_queue.schedule(scheduled_event);
2756
2757 inner.record_fault(SimFaultEvent::RecvPartitionCreated { ip: ip.to_string() });
2758 tracing::debug!("Partitioned receives to {} until {:?}", ip, expires_at);
2759 Ok(())
2760 }
2761
2762 pub fn restore_partition(
2772 &self,
2773 from_ip: std::net::IpAddr,
2774 to_ip: std::net::IpAddr,
2775 ) -> SimulationResult<()> {
2776 let mut inner = self
2777 .inner
2778 .write()
2779 .expect("RwLock poisoned: prior task panicked");
2780 inner.network.ip_partitions.remove(&(from_ip, to_ip));
2781 inner.record_fault(SimFaultEvent::PartitionHealed {
2782 from: from_ip.to_string(),
2783 to: to_ip.to_string(),
2784 });
2785 tracing::debug!("Restored partition {} -> {}", from_ip, to_ip);
2786 Ok(())
2787 }
2788
2789 pub fn is_partitioned(
2799 &self,
2800 from_ip: std::net::IpAddr,
2801 to_ip: std::net::IpAddr,
2802 ) -> SimulationResult<bool> {
2803 let inner = self
2804 .inner
2805 .read()
2806 .expect("RwLock poisoned: prior task panicked");
2807 Ok(inner
2808 .network
2809 .is_partitioned(from_ip, to_ip, inner.current_time))
2810 }
2811
2812 fn randomly_trigger_partitions_with_inner(inner: &mut SimInner) {
2819 let partition_config = &inner.network.config;
2820
2821 if partition_config.chaos.partition_probability == 0.0 {
2822 return;
2823 }
2824
2825 if sim_random::<f64>() >= partition_config.chaos.partition_probability {
2827 return;
2828 }
2829
2830 let unique_ips: HashSet<IpAddr> = inner
2832 .network
2833 .connections
2834 .values()
2835 .filter_map(|conn| conn.local_ip)
2836 .collect();
2837
2838 if unique_ips.len() < 2 {
2839 return; }
2841
2842 let ip_list: Vec<IpAddr> = unique_ips.into_iter().collect();
2843 let partition_duration =
2844 crate::network::sample_duration(&partition_config.chaos.partition_duration);
2845 let expires_at = inner.current_time + partition_duration;
2846
2847 let partitioned_ips: Vec<IpAddr> = match partition_config.chaos.partition_strategy {
2849 PartitionStrategy::Random => {
2850 ip_list
2852 .iter()
2853 .filter(|_| sim_random::<f64>() < 0.5)
2854 .copied()
2855 .collect()
2856 }
2857 PartitionStrategy::UniformSize => {
2858 let partition_size = sim_random_range(1..ip_list.len());
2860 let mut shuffled = ip_list.clone();
2862 for i in (1..shuffled.len()).rev() {
2864 let j = sim_random_range(0..i + 1);
2865 shuffled.swap(i, j);
2866 }
2867 shuffled.into_iter().take(partition_size).collect()
2868 }
2869 PartitionStrategy::IsolateSingle => {
2870 let idx = sim_random_range(0..ip_list.len());
2872 vec![ip_list[idx]]
2873 }
2874 };
2875
2876 if partitioned_ips.is_empty() || partitioned_ips.len() == ip_list.len() {
2878 return;
2879 }
2880
2881 let non_partitioned: Vec<IpAddr> = ip_list
2883 .iter()
2884 .filter(|ip| !partitioned_ips.contains(ip))
2885 .copied()
2886 .collect();
2887
2888 for &from_ip in &partitioned_ips {
2889 for &to_ip in &non_partitioned {
2890 if inner
2892 .network
2893 .is_partitioned(from_ip, to_ip, inner.current_time)
2894 {
2895 continue;
2896 }
2897
2898 inner
2900 .network
2901 .ip_partitions
2902 .insert((from_ip, to_ip), PartitionState { expires_at });
2903 inner
2904 .network
2905 .ip_partitions
2906 .insert((to_ip, from_ip), PartitionState { expires_at });
2907
2908 let time_ms = u64::try_from(inner.current_time.as_millis()).unwrap_or(u64::MAX);
2911 inner.pending_faults.push(SimFaultRecord {
2912 time_ms,
2913 event: SimFaultEvent::PartitionCreated {
2914 from: from_ip.to_string(),
2915 to: to_ip.to_string(),
2916 },
2917 });
2918
2919 tracing::debug!(
2920 "Partition triggered: {} <-> {} until {:?} (strategy: {:?})",
2921 from_ip,
2922 to_ip,
2923 expires_at,
2924 partition_config.chaos.partition_strategy
2925 );
2926 }
2927 }
2928
2929 let restore_event = Event::Connection {
2931 id: 0,
2932 state: ConnectionStateChange::PartitionRestore,
2933 };
2934 let sequence = inner.next_sequence;
2935 inner.next_sequence += 1;
2936 let scheduled_event = ScheduledEvent::new(expires_at, restore_event, sequence);
2937 inner.event_queue.schedule(scheduled_event);
2938 }
2939}
2940
2941impl Default for SimWorld {
2942 fn default() -> Self {
2943 Self::new()
2944 }
2945}
2946
2947#[derive(Debug)]
2952pub struct WeakSimWorld {
2953 pub(crate) inner: Weak<RwLock<SimInner>>,
2954}
2955
2956macro_rules! weak_forward {
2958 (wrap $(#[$meta:meta])* $method:ident(&self $(, $arg:ident : $arg_ty:ty)*) -> $ret:ty) => {
2960 $(#[$meta])*
2961 pub fn $method(&self $(, $arg: $arg_ty)*) -> SimulationResult<$ret> {
2966 Ok(self.upgrade()?.$method($($arg),*))
2967 }
2968 };
2969 (pass $(#[$meta:meta])* $method:ident(&self $(, $arg:ident : $arg_ty:ty)*) -> $ret:ty) => {
2971 $(#[$meta])*
2972 pub fn $method(&self $(, $arg: $arg_ty)*) -> SimulationResult<$ret> {
2978 self.upgrade()?.$method($($arg),*)
2979 }
2980 };
2981 (unit $(#[$meta:meta])* $method:ident(&self $(, $arg:ident : $arg_ty:ty)*)) => {
2983 $(#[$meta])*
2984 pub fn $method(&self $(, $arg: $arg_ty)*) -> SimulationResult<()> {
2989 self.upgrade()?.$method($($arg),*);
2990 Ok(())
2991 }
2992 };
2993}
2994
2995impl WeakSimWorld {
2996 pub fn upgrade(&self) -> SimulationResult<SimWorld> {
3002 self.inner
3003 .upgrade()
3004 .map(|inner| SimWorld { inner })
3005 .ok_or(SimulationError::SimulationShutdown)
3006 }
3007
3008 weak_forward!(wrap #[doc = "Returns the current simulation time."] current_time(&self) -> Duration);
3009 weak_forward!(wrap #[doc = "Returns the exact simulation time (equivalent to FDB's `now()`)."] now(&self) -> Duration);
3010 weak_forward!(wrap #[doc = "Returns the drifted timer time (equivalent to FDB's `timer()`)."] timer(&self) -> Duration);
3011 weak_forward!(unit #[doc = "Schedules an event to execute after the specified delay."] schedule_event(&self, event: Event, delay: Duration));
3012 weak_forward!(unit #[doc = "Schedules an event to execute at the specified absolute time."] schedule_event_at(&self, event: Event, time: Duration));
3013 weak_forward!(pass #[doc = "Read data from connection's receive buffer."] read_from_connection(&self, connection_id: ConnectionId, buf: &mut [u8]) -> usize);
3014 weak_forward!(pass #[doc = "Write data to connection's receive buffer."] write_to_connection(&self, connection_id: ConnectionId, data: &[u8]) -> ());
3015 weak_forward!(pass #[doc = "Buffer data for ordered sending on a connection."] buffer_send(&self, connection_id: ConnectionId, data: Vec<u8>) -> ());
3016 weak_forward!(wrap #[doc = "Get a network provider for the simulation."] network_provider(&self) -> SimNetworkProvider);
3017 weak_forward!(wrap #[doc = "Get a time provider for the simulation."] time_provider(&self) -> crate::providers::SimTimeProvider);
3018 weak_forward!(wrap #[doc = "Sleep for the specified duration in simulation time."] sleep(&self, duration: Duration) -> SleepFuture);
3019
3020 pub fn with_network_config<F, R>(&self, f: F) -> SimulationResult<R>
3026 where
3027 F: FnOnce(&NetworkConfiguration) -> R,
3028 {
3029 Ok(self.upgrade()?.with_network_config(f))
3030 }
3031}
3032
3033impl Clone for WeakSimWorld {
3034 fn clone(&self) -> Self {
3035 Self {
3036 inner: self.inner.clone(),
3037 }
3038 }
3039}
3040
3041#[cfg(test)]
3042mod tests {
3043 use super::*;
3044
3045 #[test]
3046 fn sim_world_basic_lifecycle() {
3047 let mut sim = SimWorld::new();
3048
3049 assert_eq!(sim.current_time(), Duration::ZERO);
3051 assert!(!sim.has_pending_events());
3052 assert_eq!(sim.pending_event_count(), 0);
3053
3054 sim.schedule_event(Event::Timer { task_id: 1 }, Duration::from_millis(100));
3056
3057 assert!(sim.has_pending_events());
3058 assert_eq!(sim.pending_event_count(), 1);
3059 assert_eq!(sim.current_time(), Duration::ZERO);
3060
3061 let has_more = sim.step();
3063 assert!(!has_more);
3064 assert_eq!(sim.current_time(), Duration::from_millis(100));
3065 assert!(!sim.has_pending_events());
3066 assert_eq!(sim.pending_event_count(), 0);
3067 }
3068
3069 #[test]
3070 fn sim_world_multiple_events() {
3071 let mut sim = SimWorld::new();
3072
3073 sim.schedule_event(Event::Timer { task_id: 3 }, Duration::from_millis(300));
3075 sim.schedule_event(Event::Timer { task_id: 1 }, Duration::from_millis(100));
3076 sim.schedule_event(Event::Timer { task_id: 2 }, Duration::from_millis(200));
3077
3078 assert_eq!(sim.pending_event_count(), 3);
3079
3080 assert!(sim.step());
3082 assert_eq!(sim.current_time(), Duration::from_millis(100));
3083 assert_eq!(sim.pending_event_count(), 2);
3084
3085 assert!(sim.step());
3086 assert_eq!(sim.current_time(), Duration::from_millis(200));
3087 assert_eq!(sim.pending_event_count(), 1);
3088
3089 assert!(!sim.step());
3090 assert_eq!(sim.current_time(), Duration::from_millis(300));
3091 assert_eq!(sim.pending_event_count(), 0);
3092 }
3093
3094 #[test]
3095 fn sim_world_run_until_empty() {
3096 let mut sim = SimWorld::new();
3097
3098 sim.schedule_event(Event::Timer { task_id: 1 }, Duration::from_millis(100));
3100 sim.schedule_event(Event::Timer { task_id: 2 }, Duration::from_millis(200));
3101 sim.schedule_event(Event::Timer { task_id: 3 }, Duration::from_millis(300));
3102
3103 sim.run_until_empty();
3105
3106 assert_eq!(sim.current_time(), Duration::from_millis(300));
3107 assert!(!sim.has_pending_events());
3108 }
3109
3110 #[test]
3111 fn pair_latency_deterministic_per_seed() {
3112 use crate::network::{ChaosConfiguration, NetworkConfiguration};
3113
3114 let client_ip = IpAddr::V4(std::net::Ipv4Addr::new(10, 0, 1, 1));
3115 let server_ip = IpAddr::V4(std::net::Ipv4Addr::new(10, 0, 1, 2));
3116 let range = Duration::from_millis(10)..Duration::from_millis(50);
3117
3118 let on_config = || NetworkConfiguration {
3120 chaos: ChaosConfiguration {
3121 max_pair_latency: range.clone(),
3122 ..ChaosConfiguration::disabled()
3123 },
3124 ..NetworkConfiguration::fast_local()
3125 };
3126
3127 let assign = || {
3129 let sim = SimWorld::new_with_network_config_and_seed(on_config(), 42);
3130 let (client, server) = sim.create_connection_pair("10.0.1.1:0", "10.0.1.2:0");
3131 let _ = sim.connection_base_latency(client);
3132 let _ = sim.connection_base_latency(server);
3133 (
3134 sim.pair_latency(client_ip, server_ip),
3135 sim.pair_latency(server_ip, client_ip),
3136 )
3137 };
3138
3139 let first = assign();
3140 let second = assign();
3141
3142 assert_eq!(first, second, "pair latency must be deterministic per seed");
3144 let c2s = first.0.expect("client->server pair latency assigned");
3145 let s2c = first.1.expect("server->client pair latency assigned");
3146 for latency in [c2s, s2c] {
3147 assert!(
3148 range.contains(&latency),
3149 "pair latency {latency:?} must fall in the configured range {range:?}"
3150 );
3151 }
3152
3153 let off =
3155 SimWorld::new_with_network_config_and_seed(NetworkConfiguration::fast_local(), 42);
3156 let (client, server) = off.create_connection_pair("10.0.1.1:0", "10.0.1.2:0");
3157 let _ = off.connection_base_latency(client);
3158 let _ = off.connection_base_latency(server);
3159 assert_eq!(off.pair_latency(client_ip, server_ip), None);
3160 assert_eq!(off.pair_latency(server_ip, client_ip), None);
3161 }
3162
3163 #[test]
3164 fn sim_world_schedule_at_specific_time() {
3165 let mut sim = SimWorld::new();
3166
3167 sim.schedule_event_at(Event::Timer { task_id: 1 }, Duration::from_millis(500));
3169
3170 assert_eq!(sim.current_time(), Duration::ZERO);
3171
3172 sim.step();
3173
3174 assert_eq!(sim.current_time(), Duration::from_millis(500));
3175 }
3176
3177 #[test]
3178 fn weak_sim_world_lifecycle() {
3179 let sim = SimWorld::new();
3180 let weak = sim.downgrade();
3181
3182 assert_eq!(
3184 weak.current_time().expect("should get time"),
3185 Duration::ZERO
3186 );
3187
3188 weak.schedule_event(Event::Timer { task_id: 1 }, Duration::from_millis(100))
3190 .expect("should schedule event");
3191
3192 assert!(sim.has_pending_events());
3194
3195 drop(sim);
3197
3198 assert_eq!(
3200 weak.current_time(),
3201 Err(SimulationError::SimulationShutdown)
3202 );
3203 assert_eq!(
3204 weak.schedule_event(Event::Timer { task_id: 2 }, Duration::from_millis(200)),
3205 Err(SimulationError::SimulationShutdown)
3206 );
3207 }
3208
3209 #[test]
3210 fn deterministic_event_ordering() {
3211 let mut sim = SimWorld::new();
3212
3213 sim.schedule_event(Event::Timer { task_id: 2 }, Duration::from_millis(100));
3215 sim.schedule_event(Event::Timer { task_id: 1 }, Duration::from_millis(100));
3216 sim.schedule_event(Event::Timer { task_id: 3 }, Duration::from_millis(100));
3217
3218 assert!(sim.step());
3220 assert_eq!(sim.current_time(), Duration::from_millis(100));
3221 assert!(sim.step());
3222 assert_eq!(sim.current_time(), Duration::from_millis(100));
3223 assert!(!sim.step());
3224 assert_eq!(sim.current_time(), Duration::from_millis(100));
3225 }
3226
3227 #[test]
3228 fn record_fault_stamps_current_time() {
3229 let mut inner = SimInner::new();
3230 inner.current_time = Duration::from_millis(500);
3231 inner.record_fault(SimFaultEvent::StorageCrash {
3232 ip: "10.0.1.1".to_string(),
3233 });
3234
3235 assert_eq!(inner.pending_faults.len(), 1);
3236 let record = &inner.pending_faults[0];
3237 assert_eq!(record.time_ms, 500);
3238 assert!(matches!(record.event, SimFaultEvent::StorageCrash { .. }));
3239 }
3240
3241 #[test]
3242 fn partition_pair_records_fault() {
3243 let sim = SimWorld::new();
3244 let from: std::net::IpAddr = "10.0.1.1".parse().expect("valid ip");
3245 let to: std::net::IpAddr = "10.0.1.2".parse().expect("valid ip");
3246 sim.partition_pair(from, to, Duration::from_secs(10))
3247 .expect("partition should succeed");
3248
3249 let faults = sim.take_faults();
3250 assert_eq!(faults.len(), 1);
3251 assert!(matches!(
3252 &faults[0].event,
3253 SimFaultEvent::PartitionCreated { from, to }
3254 if from == "10.0.1.1" && to == "10.0.1.2"
3255 ));
3256 assert!(sim.take_faults().is_empty(), "drained on take");
3257 }
3258
3259 #[test]
3260 fn restore_partition_records_fault() {
3261 let sim = SimWorld::new();
3262 let from: std::net::IpAddr = "10.0.1.1".parse().expect("valid ip");
3263 let to: std::net::IpAddr = "10.0.1.2".parse().expect("valid ip");
3264 sim.partition_pair(from, to, Duration::from_secs(10))
3265 .expect("partition");
3266 sim.restore_partition(from, to).expect("restore");
3267
3268 let faults = sim.take_faults();
3269 assert_eq!(faults.len(), 2);
3270 assert!(matches!(
3271 &faults[0].event,
3272 SimFaultEvent::PartitionCreated { .. }
3273 ));
3274 assert!(matches!(
3275 &faults[1].event,
3276 SimFaultEvent::PartitionHealed { .. }
3277 ));
3278 }
3279}