moonpool_sim/sim/events.rs
1//! Event scheduling and processing for the simulation engine.
2//!
3//! This module provides the core event types and queue for scheduling
4//! events in chronological order with deterministic ordering.
5
6use std::{cmp::Ordering, collections::BinaryHeap, time::Duration};
7
8pub use crate::storage::StorageOperation;
9
10/// Events that can be scheduled in the simulation.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Event {
13 /// Timer event for waking sleeping tasks
14 Timer {
15 /// The unique identifier for the task to wake.
16 task_id: u64,
17 },
18
19 /// Network data operations
20 Network {
21 /// The connection involved
22 connection_id: u64,
23 /// The operation type
24 operation: NetworkOperation,
25 },
26
27 /// Connection state changes
28 Connection {
29 /// The connection or listener ID
30 id: u64,
31 /// The state change type
32 state: ConnectionStateChange,
33 },
34
35 /// Storage I/O operations
36 Storage {
37 /// The file involved
38 file_id: u64,
39 /// The operation type
40 operation: StorageOperation,
41 },
42
43 /// Shutdown event to wake all tasks for graceful termination
44 Shutdown,
45
46 /// Process restart event: a rebooted process is ready to boot again.
47 ///
48 /// Scheduled after a process is killed, at `now + recovery_delay`.
49 /// The orchestrator handles this by calling the process factory
50 /// and spawning a new `run()` task.
51 ProcessRestart {
52 /// The IP address of the process to restart.
53 ip: std::net::IpAddr,
54 },
55
56 /// Graceful shutdown initiated for a process.
57 ///
58 /// Cancels the per-process shutdown token so the process can observe
59 /// `ctx.shutdown().is_cancelled()` and perform cleanup. A
60 /// [`ProcessForceKill`](Event::ProcessForceKill) is scheduled after the
61 /// grace period expires.
62 ProcessGracefulShutdown {
63 /// The IP address of the process being gracefully shut down.
64 ip: std::net::IpAddr,
65 /// Grace period in milliseconds before force-kill.
66 grace_period_ms: u64,
67 /// Recovery delay in milliseconds after force-kill before restart.
68 recovery_delay_ms: u64,
69 },
70
71 /// Force-kill a process after a graceful shutdown grace period.
72 ///
73 /// Aborts the process task and all its connections, then schedules a
74 /// [`ProcessRestart`](Event::ProcessRestart) after a recovery delay.
75 ProcessForceKill {
76 /// The IP address of the process to force-kill.
77 ip: std::net::IpAddr,
78 /// Recovery delay in milliseconds before restart.
79 recovery_delay_ms: u64,
80 },
81}
82
83impl Event {
84 /// Determines if this event is purely infrastructural (not workload-related).
85 ///
86 /// Infrastructure events maintain simulation state but don't represent actual
87 /// application work. These events can be safely ignored when determining if
88 /// a simulation should terminate after workloads complete.
89 #[must_use]
90 pub fn is_infrastructure_event(&self) -> bool {
91 matches!(
92 self,
93 Event::Connection {
94 state: ConnectionStateChange::PartitionRestore
95 | ConnectionStateChange::SendPartitionClear
96 | ConnectionStateChange::RecvPartitionClear
97 | ConnectionStateChange::CutRestore,
98 ..
99 } | Event::ProcessRestart { .. }
100 )
101 }
102}
103
104/// Network data operations
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub enum NetworkOperation {
107 /// Deliver data to connection's receive buffer
108 DataDelivery {
109 /// The data bytes to deliver
110 data: Vec<u8>,
111 },
112 /// Process next message from connection's send buffer
113 ProcessSendBuffer,
114 /// Deliver FIN (graceful close) to a connection's receive side.
115 /// Scheduled after the last `DataDelivery` to ensure all data arrives first.
116 FinDelivery,
117}
118
119/// Connection state changes
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum ConnectionStateChange {
122 /// Listener bind operation completed
123 BindComplete,
124 /// Connection establishment completed
125 ConnectionReady,
126 /// Clear write clog for a connection
127 ClogClear,
128 /// Clear read clog for a connection
129 ReadClogClear,
130 /// Restore a temporarily cut connection
131 CutRestore,
132 /// Restore network partition between IPs
133 PartitionRestore,
134 /// Clear send partition for an IP
135 SendPartitionClear,
136 /// Clear receive partition for an IP
137 RecvPartitionClear,
138 /// Half-open connection starts returning errors
139 HalfOpenError,
140}
141
142/// An event scheduled for execution at a specific simulation time.
143#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct ScheduledEvent {
145 time: Duration,
146 event: Event,
147 /// Sequence number for deterministic ordering
148 pub sequence: u64,
149}
150
151impl ScheduledEvent {
152 /// Creates a new scheduled event.
153 #[must_use]
154 pub fn new(time: Duration, event: Event, sequence: u64) -> Self {
155 Self {
156 time,
157 event,
158 sequence,
159 }
160 }
161
162 /// Returns the scheduled execution time.
163 #[must_use]
164 pub fn time(&self) -> Duration {
165 self.time
166 }
167
168 /// Returns a reference to the event.
169 #[must_use]
170 pub fn event(&self) -> &Event {
171 &self.event
172 }
173
174 /// Consumes the scheduled event and returns the event.
175 #[must_use]
176 pub fn into_event(self) -> Event {
177 self.event
178 }
179}
180
181impl PartialOrd for ScheduledEvent {
182 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
183 Some(self.cmp(other))
184 }
185}
186
187impl Ord for ScheduledEvent {
188 fn cmp(&self, other: &Self) -> Ordering {
189 // BinaryHeap is a max heap, but we want earliest time first
190 // So we reverse the time comparison
191 match other.time.cmp(&self.time) {
192 Ordering::Equal => {
193 // For events at the same time, use sequence number for deterministic ordering
194 // Earlier sequence numbers should be processed first (also reversed for max heap)
195 other.sequence.cmp(&self.sequence)
196 }
197 other => other,
198 }
199 }
200}
201
202/// A priority queue for scheduling events in chronological order.
203///
204/// Events are processed in time order, with deterministic ordering for events
205/// scheduled at the same time using sequence numbers.
206#[derive(Debug)]
207pub struct EventQueue {
208 heap: BinaryHeap<ScheduledEvent>,
209}
210
211impl EventQueue {
212 /// Creates a new empty event queue.
213 #[must_use]
214 pub fn new() -> Self {
215 Self {
216 heap: BinaryHeap::new(),
217 }
218 }
219
220 /// Schedules an event for execution.
221 pub fn schedule(&mut self, event: ScheduledEvent) {
222 self.heap.push(event);
223 }
224
225 /// Removes and returns the earliest scheduled event.
226 pub fn pop_earliest(&mut self) -> Option<ScheduledEvent> {
227 self.heap.pop()
228 }
229
230 /// Returns `true` if the queue is empty.
231 #[must_use]
232 pub fn is_empty(&self) -> bool {
233 self.heap.is_empty()
234 }
235
236 /// Returns the number of events in the queue.
237 #[must_use]
238 pub fn len(&self) -> usize {
239 self.heap.len()
240 }
241
242 /// Checks if the queue contains only infrastructure events (no workload events).
243 ///
244 /// Infrastructure events are those that maintain simulation state but don't
245 /// represent actual application work (like connection restoration).
246 /// Returns true if empty or contains only infrastructure events.
247 #[must_use]
248 pub fn has_only_infrastructure_events(&self) -> bool {
249 self.heap
250 .iter()
251 .all(|scheduled_event| scheduled_event.event().is_infrastructure_event())
252 }
253}
254
255impl Default for EventQueue {
256 fn default() -> Self {
257 Self::new()
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_infrastructure_event_detection() {
267 // Test Event::is_infrastructure_event() method
268 let restore_event = Event::Connection {
269 id: 1,
270 state: ConnectionStateChange::PartitionRestore,
271 };
272 assert!(restore_event.is_infrastructure_event());
273
274 let timer_event = Event::Timer { task_id: 1 };
275 assert!(!timer_event.is_infrastructure_event());
276
277 let network_event = Event::Network {
278 connection_id: 1,
279 operation: NetworkOperation::DataDelivery {
280 data: vec![1, 2, 3],
281 },
282 };
283 assert!(!network_event.is_infrastructure_event());
284
285 let shutdown_event = Event::Shutdown;
286 assert!(!shutdown_event.is_infrastructure_event());
287
288 // Test EventQueue::has_only_infrastructure_events() method
289 let mut queue = EventQueue::new();
290
291 // Empty queue should be considered "only infrastructure"
292 assert!(queue.has_only_infrastructure_events());
293
294 // Queue with only ConnectionRestore events
295 queue.schedule(ScheduledEvent::new(
296 Duration::from_secs(1),
297 restore_event,
298 1,
299 ));
300 assert!(queue.has_only_infrastructure_events());
301
302 // Queue with workload events should return false
303 queue.schedule(ScheduledEvent::new(Duration::from_secs(2), timer_event, 2));
304 assert!(!queue.has_only_infrastructure_events());
305
306 // Queue with network events should return false
307 let mut queue2 = EventQueue::new();
308 queue2.schedule(ScheduledEvent::new(
309 Duration::from_secs(1),
310 network_event,
311 1,
312 ));
313 assert!(!queue2.has_only_infrastructure_events());
314 }
315
316 #[test]
317 fn event_queue_ordering() {
318 let mut queue = EventQueue::new();
319
320 // Schedule events in random order
321 queue.schedule(ScheduledEvent::new(
322 Duration::from_millis(300),
323 Event::Timer { task_id: 3 },
324 2,
325 ));
326 queue.schedule(ScheduledEvent::new(
327 Duration::from_millis(100),
328 Event::Timer { task_id: 1 },
329 0,
330 ));
331 queue.schedule(ScheduledEvent::new(
332 Duration::from_millis(200),
333 Event::Timer { task_id: 2 },
334 1,
335 ));
336
337 // Should pop in time order
338 let event1 = queue.pop_earliest().expect("should have event");
339 assert_eq!(event1.time(), Duration::from_millis(100));
340 assert_eq!(event1.event(), &Event::Timer { task_id: 1 });
341
342 let event2 = queue.pop_earliest().expect("should have event");
343 assert_eq!(event2.time(), Duration::from_millis(200));
344 assert_eq!(event2.event(), &Event::Timer { task_id: 2 });
345
346 let event3 = queue.pop_earliest().expect("should have event");
347 assert_eq!(event3.time(), Duration::from_millis(300));
348 assert_eq!(event3.event(), &Event::Timer { task_id: 3 });
349
350 assert!(queue.is_empty());
351 }
352
353 #[test]
354 fn same_time_deterministic_ordering() {
355 let mut queue = EventQueue::new();
356 let same_time = Duration::from_millis(100);
357
358 // Schedule multiple events at the same time with different sequence numbers
359 queue.schedule(ScheduledEvent::new(
360 same_time,
361 Event::Timer { task_id: 3 },
362 2, // Later sequence
363 ));
364 queue.schedule(ScheduledEvent::new(
365 same_time,
366 Event::Timer { task_id: 1 },
367 0, // Earlier sequence
368 ));
369 queue.schedule(ScheduledEvent::new(
370 same_time,
371 Event::Timer { task_id: 2 },
372 1, // Middle sequence
373 ));
374
375 // Should pop in sequence order when times are equal
376 let event1 = queue.pop_earliest().expect("should have event");
377 assert_eq!(event1.event(), &Event::Timer { task_id: 1 });
378 assert_eq!(event1.sequence, 0);
379
380 let event2 = queue.pop_earliest().expect("should have event");
381 assert_eq!(event2.event(), &Event::Timer { task_id: 2 });
382 assert_eq!(event2.sequence, 1);
383
384 let event3 = queue.pop_earliest().expect("should have event");
385 assert_eq!(event3.event(), &Event::Timer { task_id: 3 });
386 assert_eq!(event3.sequence, 2);
387
388 assert!(queue.is_empty());
389 }
390}