Skip to main content

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}