Skip to main content

consortium/engine/
mod.rs

1//! I/O engine abstraction.
2//!
3//! Rust implementation of `ClusterShell.Engine`.
4//!
5//! The Python version has EPoll, Poll, and Select backends.
6//! In Rust we will likely use a single backend (mio or polling crate).
7
8use slotmap::{new_key_type, SlotMap};
9use std::cmp::Reverse;
10use std::collections::{BinaryHeap, HashMap};
11use std::io;
12use std::os::unix::io::RawFd;
13use thiserror::Error;
14
15// ============================================================================
16// Constants
17// ============================================================================
18
19/// Read event flag
20pub const E_READ: u32 = 0x1;
21
22/// Write event flag
23pub const E_WRITE: u32 = 0x2;
24
25/// Time epsilon for floating point comparisons
26pub const EPSILON: f64 = 1.0e-3;
27
28/// Unlimited fanout value
29pub const FANOUT_UNLIMITED: i32 = -1;
30
31// ============================================================================
32// Error Types
33// ============================================================================
34
35/// Error types for the Engine module
36#[derive(Error, Debug)]
37pub enum EngineError {
38    /// Operation aborted, optionally with kill flag
39    #[error("Operation aborted (kill={kill})")]
40    Abort { kill: bool },
41
42    /// Operation timed out
43    #[error("Operation timed out")]
44    Timeout,
45
46    /// Illegal operation was attempted
47    #[error("Illegal operation: {0}")]
48    IllegalOperation(String),
49
50    /// Engine is already running
51    #[error("Engine is already running")]
52    AlreadyRunning,
53
54    /// Operation not supported
55    #[error("Not supported: {0}")]
56    NotSupported(String),
57
58    /// I/O error
59    #[error("I/O error: {0}")]
60    Io(#[from] io::Error),
61}
62
63// ============================================================================
64// Timer ID
65// ============================================================================
66
67new_key_type! { pub struct TimerId; }
68
69// ============================================================================
70// EngineBaseTimer
71// ============================================================================
72
73/// Base timer structure with fire delay, interval, and autoclose settings.
74///
75/// This is the core timer struct that Engine uses. It can be armed/disarmed
76/// and supports repeating timers via interval.
77#[derive(Debug, Clone)]
78pub struct EngineBaseTimer {
79    fire_delay: f64,
80    interval: f64,
81    autoclose: bool,
82}
83
84impl EngineBaseTimer {
85    /// Create a new EngineBaseTimer
86    ///
87    /// # Arguments
88    /// * `fire_delay` - Seconds until timer fires (-1.0 means not armed)
89    /// * `interval` - Seconds between repeats (-1.0 means no repeat)
90    /// * `autoclose` - Whether to autoclose the client when timer fires
91    pub fn new(fire_delay: f64, interval: f64, autoclose: bool) -> Self {
92        Self {
93            fire_delay,
94            interval,
95            autoclose,
96        }
97    }
98
99    /// Invalidate this timer (disarm it)
100    pub fn invalidate(&mut self) {
101        self.fire_delay = -1.0;
102    }
103
104    /// Check if this timer is valid (armed)
105    pub fn is_valid(&self) -> bool {
106        self.fire_delay >= 0.0
107    }
108
109    /// Set the next fire delay and interval
110    ///
111    /// # Arguments
112    /// * `fire_delay` - Seconds until next fire (-1.0 means disarm)
113    /// * `interval` - Seconds between repeats (-1.0 means no repeat)
114    pub fn set_nextfire(&mut self, fire_delay: f64, interval: f64) {
115        self.fire_delay = fire_delay;
116        self.interval = interval;
117    }
118
119    /// Get the fire delay
120    pub fn fire_delay(&self) -> f64 {
121        self.fire_delay
122    }
123
124    /// Get the interval
125    pub fn interval(&self) -> f64 {
126        self.interval
127    }
128
129    /// Get the autoclose flag
130    pub fn autoclose(&self) -> bool {
131        self.autoclose
132    }
133}
134
135// ============================================================================
136// TimerQueue (Internal)
137// ============================================================================
138
139/// Internal timer entry used by TimerQueue
140#[derive(Debug, Clone, PartialEq)]
141struct TimerEntry {
142    fire_date: f64,
143    timer_id: TimerId,
144    armed: bool,
145}
146
147impl Eq for TimerEntry {}
148
149impl Ord for TimerEntry {
150    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
151        // Reverse ordering: lower fire_date comes first (min-heap behavior)
152        self.fire_date
153            .partial_cmp(&other.fire_date)
154            .unwrap_or(std::cmp::Ordering::Equal)
155            .then_with(|| self.timer_id.cmp(&other.timer_id))
156    }
157}
158
159impl PartialOrd for TimerEntry {
160    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
161        Some(self.cmp(other))
162    }
163}
164
165/// Timer queue managing a set of timers using a BinaryHeap.
166///
167/// Timers are ordered by their fire_date for efficient expiration checking.
168/// Repeating timers are automatically rearmed after firing.
169#[derive(Debug)]
170pub struct TimerQueue {
171    timers: BinaryHeap<Reverse<TimerEntry>>,
172    timer_data: SlotMap<TimerId, EngineBaseTimer>,
173    armed_count: usize,
174    current_time: f64,
175}
176
177impl TimerQueue {
178    /// Create a new empty TimerQueue
179    pub fn new() -> Self {
180        Self {
181            timers: BinaryHeap::new(),
182            timer_data: SlotMap::with_capacity_and_key(16),
183            armed_count: 0,
184            current_time: 0.0,
185        }
186    }
187
188    /// Schedule a timer for future firing
189    ///
190    /// # Arguments
191    /// * `timer_id` - The ID of the timer to schedule
192    pub fn schedule(&mut self, timer_id: TimerId) {
193        if let Some(timer) = self.timer_data.get(timer_id) {
194            if timer.fire_delay >= 0.0 {
195                let fire_date = self.current_time + timer.fire_delay;
196                let entry = TimerEntry {
197                    fire_date,
198                    timer_id,
199                    armed: true,
200                };
201                self.timers.push(Reverse(entry));
202                self.armed_count += 1;
203            }
204        }
205    }
206
207    /// Reschedule an existing timer
208    ///
209    /// # Arguments
210    /// * `timer_id` - The ID of the timer to reschedule
211    pub fn reschedule(&mut self, timer_id: TimerId) {
212        // Remove old entry if it exists
213        self.invalidate(timer_id);
214        self.schedule(timer_id);
215    }
216
217    /// Invalidate (disarm) a timer
218    ///
219    /// # Arguments
220    /// * `timer_id` - The ID of the timer to invalidate
221    pub fn invalidate(&mut self, timer_id: TimerId) {
222        // Find and remove the timer entry
223        let mut found = false;
224        let mut new_heap = BinaryHeap::new();
225        for Reverse(entry) in self.timers.drain() {
226            if entry.timer_id == timer_id {
227                found = true;
228                // Update the timer state
229                if let Some(timer) = self.timer_data.get_mut(timer_id) {
230                    timer.invalidate();
231                }
232            } else {
233                new_heap.push(Reverse(entry));
234            }
235        }
236        self.timers = new_heap;
237        if found {
238            self.armed_count = self.armed_count.saturating_sub(1);
239        }
240    }
241
242    /// Fire all expired timers
243    ///
244    /// Returns a list of (timer_id, autoclose) for timers that fired
245    pub fn fire_expired(&mut self) -> Vec<(TimerId, bool)> {
246        let mut fired = Vec::new();
247        let now = self.current_time;
248
249        // Process all timers that have expired
250        while let Some(Reverse(entry)) = self.timers.peek() {
251            if entry.fire_date > now + EPSILON {
252                break;
253            }
254
255            let entry = self.timers.pop().unwrap().0;
256            let timer_id = entry.timer_id;
257
258            if let Some(timer) = self.timer_data.get(timer_id) {
259                if entry.armed && timer.fire_delay >= 0.0 {
260                    fired.push((timer_id, timer.autoclose));
261
262                    // If this is a repeating timer, reschedule it
263                    if timer.interval >= 0.0 {
264                        let new_fire_date = now + timer.interval;
265                        let new_entry = TimerEntry {
266                            fire_date: new_fire_date,
267                            timer_id,
268                            armed: true,
269                        };
270                        self.timers.push(Reverse(new_entry));
271                    } else {
272                        // One-shot timer: disarm it
273                        if let Some(t) = self.timer_data.get_mut(timer_id) {
274                            t.invalidate();
275                        }
276                        self.armed_count = self.armed_count.saturating_sub(1);
277                    }
278                }
279            }
280        }
281
282        fired
283    }
284
285    /// Get the time until the next timer fires
286    ///
287    /// Returns None if no timers are armed
288    pub fn nextfire_delay(&self) -> Option<f64> {
289        if let Some(Reverse(entry)) = self.timers.peek() {
290            let delay = entry.fire_date - self.current_time;
291            if delay > EPSILON {
292                Some(delay)
293            } else {
294                Some(0.0)
295            }
296        } else {
297            None
298        }
299    }
300
301    /// Clear all timers
302    pub fn clear(&mut self) {
303        self.timers.clear();
304        self.timer_data.clear();
305        self.armed_count = 0;
306    }
307
308    /// Add a timer to the queue
309    ///
310    /// Returns the TimerId for the new timer
311    pub fn add_timer(&mut self, timer: EngineBaseTimer) -> TimerId {
312        let timer_id = self.timer_data.insert(timer);
313        timer_id
314    }
315
316    /// Remove a timer by ID
317    pub fn remove_timer(&mut self, timer_id: TimerId) {
318        self.invalidate(timer_id);
319        self.timer_data.remove(timer_id);
320    }
321
322    /// Get the number of armed timers
323    pub fn armed_count(&self) -> usize {
324        self.armed_count
325    }
326
327    /// Update the current time (should be called before fire_expired)
328    pub fn update_time(&mut self, time: f64) {
329        self.current_time = time;
330    }
331
332    /// Check if a timer is valid
333    pub fn is_valid(&self, timer_id: TimerId) -> bool {
334        self.timer_data
335            .get(timer_id)
336            .map(|t| t.is_valid())
337            .unwrap_or(false)
338    }
339}
340
341impl Default for TimerQueue {
342    fn default() -> Self {
343        Self::new()
344    }
345}
346
347// ============================================================================
348// EngineTimer (concrete timer with handler)
349// ============================================================================
350
351/// Concrete timer with a callback handler.
352///
353/// When the timer fires, the handler function is called. This mirrors
354/// Python's `EngineTimer` which has `_fire()` calling `handler.ev_timer()`.
355pub struct EngineTimer {
356    base: EngineBaseTimer,
357    handler: Box<dyn FnMut() + Send>,
358}
359
360impl EngineTimer {
361    /// Create a new EngineTimer.
362    ///
363    /// # Arguments
364    /// * `fire_delay` - Seconds until timer fires (-1.0 means not armed)
365    /// * `interval` - Seconds between repeats (-1.0 means no repeat)
366    /// * `autoclose` - Whether to autoclose the associated client
367    /// * `handler` - Callback invoked when the timer fires
368    pub fn new(
369        fire_delay: f64,
370        interval: f64,
371        autoclose: bool,
372        handler: impl FnMut() + Send + 'static,
373    ) -> Self {
374        Self {
375            base: EngineBaseTimer::new(fire_delay, interval, autoclose),
376            handler: Box::new(handler),
377        }
378    }
379
380    /// Fire the timer (invoke the handler).
381    pub fn fire(&mut self) {
382        (self.handler)();
383    }
384
385    /// Get a reference to the underlying EngineBaseTimer.
386    pub fn base(&self) -> &EngineBaseTimer {
387        &self.base
388    }
389
390    /// Get a mutable reference to the underlying EngineBaseTimer.
391    pub fn base_mut(&mut self) -> &mut EngineBaseTimer {
392        &mut self.base
393    }
394}
395
396impl std::fmt::Debug for EngineTimer {
397    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
398        f.debug_struct("EngineTimer")
399            .field("base", &self.base)
400            .field("handler", &"<fn>")
401            .finish()
402    }
403}
404
405// ============================================================================
406// EngineClient trait
407// ============================================================================
408
409/// Trait for objects that can be managed by the Engine.
410///
411/// Clients are I/O objects (files, sockets, pipes) that the engine
412/// monitors for readability/writability and manages through their lifecycle.
413pub trait EngineClient: Send {
414    /// Start the client
415    fn start(&mut self) -> io::Result<()>;
416
417    /// Close the client
418    ///
419    /// # Arguments
420    /// * `abort` - Whether this is an abort close
421    /// * `timeout` - Whether this is a timeout close
422    fn close(&mut self, abort: bool, timeout: bool);
423
424    /// Get the file descriptor, if any
425    fn fd(&self) -> Option<RawFd>;
426
427    /// Get the event interest bitmask (E_READ, E_WRITE, etc.)
428    fn events(&self) -> u32;
429
430    /// Check if this client can be delayed (batched)
431    fn is_delayable(&self) -> bool;
432
433    /// Check if this client is currently registered with the engine
434    fn is_registered(&self) -> bool;
435
436    /// Set the registered state
437    fn set_registered(&mut self, val: bool);
438
439    /// Check if this client should be autoclosed
440    fn autoclose(&self) -> bool;
441
442    /// Handle a read event on this client.
443    /// Returns the number of bytes read, or 0 on EOF.
444    fn handle_read(&mut self) -> io::Result<usize> {
445        Ok(0)
446    }
447
448    /// Handle a write event on this client.
449    /// Returns the number of bytes written.
450    fn handle_write(&mut self) -> io::Result<usize> {
451        Ok(0)
452    }
453}
454
455// ============================================================================
456// Client ID
457// ============================================================================
458
459new_key_type! { pub struct ClientId; }
460
461// ============================================================================
462// PollBackend trait
463// ============================================================================
464
465/// Trait for I/O polling backends.
466///
467/// This trait abstracts the underlying polling mechanism (epoll, kqueue, poll, etc.)
468/// and allows for different implementations without changing the core Engine logic.
469pub trait PollBackend: Send + Sync {
470    /// Register a file descriptor for events
471    ///
472    /// # Arguments
473    /// * `fd` - The file descriptor to register
474    /// * `events` - Event bitmask (E_READ, E_WRITE)
475    /// * `client_id` - The client ID associated with this fd
476    fn register(&mut self, fd: RawFd, events: u32, client_id: ClientId) -> io::Result<()>;
477
478    /// Modify event registration for a file descriptor
479    fn modify(&mut self, fd: RawFd, events: u32, client_id: ClientId) -> io::Result<()>;
480
481    /// Unregister a file descriptor
482    fn unregister(&mut self, fd: RawFd) -> io::Result<()>;
483
484    /// Wait for events with optional timeout (in seconds)
485    ///
486    /// Returns a list of (fd, events, client_id) tuples for ready events
487    fn wait(&mut self, timeout: Option<f64>) -> io::Result<Vec<(RawFd, u32, ClientId)>>;
488
489    /// Get the backend name
490    fn name(&self) -> &str;
491}
492
493// ============================================================================
494// Engine struct
495// ============================================================================
496
497/// The main I/O event loop engine.
498///
499/// The Engine manages:
500/// - Timer scheduling and expiration
501/// - Client registration and event monitoring
502/// - The main event loop
503/// - Fanout control (maximum concurrent operations)
504///
505/// # Example
506/// ```no_run
507/// use consortium::engine::{Engine, EngineBaseTimer, EngineClient, PollBackend, ClientId, FANOUT_UNLIMITED};
508/// use std::os::unix::io::RawFd;
509///
510/// // Engine requires a concrete PollBackend implementation.
511/// // See NullBackend in tests for a minimal example.
512/// ```
513pub struct Engine<B: PollBackend> {
514    clients: SlotMap<ClientId, Box<dyn EngineClient>>,
515    timerq: TimerQueue,
516    reg_clifds: HashMap<RawFd, ClientId>,
517    poll_backend: B,
518    fanout: usize,
519    running: bool,
520    exited: bool,
521    evlooprefcnt: i32,
522}
523
524impl<B: PollBackend> Engine<B> {
525    /// Create a new Engine instance with the given backend.
526    ///
527    /// # Arguments
528    /// * `backend` - The polling backend implementation
529    /// * `fanout` - Maximum concurrent operations (0 for no limit)
530    pub fn with_backend(backend: B, fanout: usize) -> Self {
531        Self {
532            clients: SlotMap::with_key(),
533            timerq: TimerQueue::new(),
534            reg_clifds: HashMap::new(),
535            poll_backend: backend,
536            fanout,
537            running: false,
538            exited: false,
539            evlooprefcnt: 0,
540        }
541    }
542
543    /// Add a timer and return its ID
544    ///
545    /// # Arguments
546    /// * `timer` - The timer to add
547    pub fn add_timer(&mut self, timer: EngineBaseTimer) -> TimerId {
548        let timer_id = self.timerq.add_timer(timer);
549        self.timerq.schedule(timer_id);
550        timer_id
551    }
552
553    /// Remove a timer by ID
554    ///
555    /// # Arguments
556    /// * `id` - The timer ID to remove
557    pub fn remove_timer(&mut self, id: TimerId) {
558        self.timerq.remove_timer(id);
559    }
560
561    /// Fire all expired timers
562    pub fn fire_timers(&mut self) {
563        let _fired = self.timerq.fire_expired();
564        // Timer firing is handled internally by the timer queue.
565        // Client-associated timers will be integrated when the
566        // full client lifecycle is implemented.
567    }
568
569    /// Run the engine event loop
570    ///
571    /// # Arguments
572    /// * `timeout` - Optional timeout in seconds (None for no limit)
573    pub fn run(&mut self, timeout: Option<f64>) -> Result<(), EngineError> {
574        if self.running {
575            return Err(EngineError::AlreadyRunning);
576        }
577
578        self.running = true;
579        self.exited = false;
580
581        let start_time = self.timerq.current_time;
582        let max_time = timeout.map(|t| start_time + t);
583
584        while self.running {
585            // Update current time
586            self.timerq.update_time(self.timerq.current_time + 0.001);
587
588            // Fire any expired timers
589            self.fire_timers();
590
591            // Check if we should exit
592            if !self.running {
593                break;
594            }
595
596            // Get next timer delay
597            let next_timer_delay = self.timerq.nextfire_delay();
598
599            // Calculate timeout for poll
600            let poll_timeout = match (max_time, next_timer_delay) {
601                (Some(max), Some(timer_delay)) => {
602                    let remaining = max - self.timerq.current_time;
603                    if remaining <= EPSILON {
604                        return Err(EngineError::Timeout);
605                    }
606                    // Use f64::min by comparing manually since f64 doesn't implement Ord
607                    Some(if remaining < timer_delay {
608                        remaining
609                    } else {
610                        timer_delay
611                    })
612                }
613                (Some(max), None) => {
614                    let remaining = max - self.timerq.current_time;
615                    if remaining <= EPSILON {
616                        return Err(EngineError::Timeout);
617                    }
618                    Some(remaining)
619                }
620                (None, Some(timer_delay)) => Some(timer_delay),
621                (None, None) => None,
622            };
623
624            // Wait for events
625            let events = self.poll_backend.wait(poll_timeout.map(|t| t.max(0.0)))?;
626
627            // Process events — dispatch read/write handlers
628            for (_fd, events_mask, client_id) in events {
629                if let Some(client) = self.clients.get_mut(client_id) {
630                    if events_mask & E_READ != 0 {
631                        let _ = client.handle_read();
632                    }
633                    if events_mask & E_WRITE != 0 {
634                        let _ = client.handle_write();
635                    }
636                }
637            }
638
639            // Check timeout
640            if let Some(max) = max_time {
641                if self.timerq.current_time >= max {
642                    return Err(EngineError::Timeout);
643                }
644            }
645        }
646
647        self.exited = true;
648        self.running = false;
649        Ok(())
650    }
651
652    /// Abort the engine
653    ///
654    /// # Arguments
655    /// * `kill` - If true, force kill all clients
656    pub fn abort(&mut self, kill: bool) {
657        if kill {
658            // Force kill all clients
659            for (_, client) in self.clients.iter_mut() {
660                client.close(true, false);
661            }
662        }
663        self.running = false;
664    }
665
666    /// Check if the engine is running
667    pub fn is_running(&self) -> bool {
668        self.running
669    }
670
671    /// Check if the engine has exited
672    pub fn exited(&self) -> bool {
673        self.exited
674    }
675
676    /// Add a client to the engine
677    ///
678    /// # Arguments
679    /// * `client` - The client to add
680    pub fn add_client(&mut self, client: Box<dyn EngineClient>) -> Result<ClientId, EngineError> {
681        let client_id = self.clients.insert(client);
682
683        if let Some(client) = self.clients.get(client_id) {
684            if let Some(fd) = client.fd() {
685                if fd >= 0 {
686                    let events = client.events();
687                    self.poll_backend.register(fd, events, client_id)?;
688                    self.reg_clifds.insert(fd, client_id);
689                }
690            }
691        }
692
693        Ok(client_id)
694    }
695
696    /// Remove a client by ID
697    ///
698    /// # Arguments
699    /// * `id` - The client ID to remove
700    pub fn remove_client(&mut self, id: ClientId) {
701        if let Some(client) = self.clients.get(id) {
702            if let Some(fd) = client.fd() {
703                if fd >= 0 {
704                    let _ = self.poll_backend.unregister(fd);
705                    self.reg_clifds.remove(&fd);
706                }
707            }
708        }
709        self.clients.remove(id);
710    }
711
712    /// Get the fanout value
713    pub fn fanout(&self) -> usize {
714        self.fanout
715    }
716
717    /// Update client event interests
718    pub fn update_client_events(&mut self, client_id: ClientId) -> Result<(), EngineError> {
719        if let Some(client) = self.clients.get(client_id) {
720            if let Some(fd) = client.fd() {
721                if fd >= 0 {
722                    let events = client.events();
723                    if self.reg_clifds.contains_key(&fd) {
724                        self.poll_backend.modify(fd, events, client_id)?;
725                    } else {
726                        self.poll_backend.register(fd, events, client_id)?;
727                        self.reg_clifds.insert(fd, client_id);
728                    }
729                }
730            }
731        }
732        Ok(())
733    }
734
735    /// Stop the engine event loop.
736    pub fn stop(&mut self) {
737        self.running = false;
738    }
739
740    /// Increment the event loop reference count.
741    /// While refcnt > 0, the engine loop stays alive.
742    pub fn evloop_acquire(&mut self) {
743        self.evlooprefcnt += 1;
744    }
745
746    /// Decrement the event loop reference count.
747    /// When refcnt reaches 0, the engine stops.
748    pub fn evloop_release(&mut self) {
749        self.evlooprefcnt -= 1;
750        if self.evlooprefcnt <= 0 {
751            self.running = false;
752        }
753    }
754
755    /// Get the number of registered clients.
756    pub fn client_count(&self) -> usize {
757        self.clients.len()
758    }
759
760    /// Get the timer queue (immutable).
761    pub fn timerq(&self) -> &TimerQueue {
762        &self.timerq
763    }
764
765    /// Get the timer queue (mutable).
766    pub fn timerq_mut(&mut self) -> &mut TimerQueue {
767        &mut self.timerq
768    }
769
770    /// Start all registered clients.
771    pub fn start_all_clients(&mut self) -> io::Result<()> {
772        let ids: Vec<ClientId> = self.clients.keys().collect();
773        for id in ids {
774            if let Some(client) = self.clients.get_mut(id) {
775                client.start()?;
776            }
777        }
778        Ok(())
779    }
780}
781
782// ============================================================================
783// NullBackend — no-op backend for testing
784// ============================================================================
785
786/// A no-op polling backend that never returns events.
787///
788/// Useful for testing timer logic and client management without actual I/O.
789#[derive(Debug, Default)]
790pub struct NullBackend;
791
792impl PollBackend for NullBackend {
793    fn register(&mut self, _fd: RawFd, _events: u32, _client_id: ClientId) -> io::Result<()> {
794        Ok(())
795    }
796
797    fn modify(&mut self, _fd: RawFd, _events: u32, _client_id: ClientId) -> io::Result<()> {
798        Ok(())
799    }
800
801    fn unregister(&mut self, _fd: RawFd) -> io::Result<()> {
802        Ok(())
803    }
804
805    fn wait(&mut self, _timeout: Option<f64>) -> io::Result<Vec<(RawFd, u32, ClientId)>> {
806        Ok(Vec::new())
807    }
808
809    fn name(&self) -> &str {
810        "null"
811    }
812}
813
814// ============================================================================
815// Tests
816// ============================================================================
817
818#[cfg(test)]
819mod tests {
820    use super::*;
821
822    #[test]
823    fn test_engine_constants() {
824        assert_eq!(E_READ, 0x1);
825        assert_eq!(E_WRITE, 0x2);
826        assert!((EPSILON - 1.0e-3).abs() < EPSILON);
827        assert_eq!(FANOUT_UNLIMITED, -1);
828    }
829
830    #[test]
831    fn test_engine_base_timer() {
832        let mut timer = EngineBaseTimer::new(5.0, -1.0, false);
833        assert!(timer.is_valid());
834        assert_eq!(timer.fire_delay(), 5.0);
835        assert_eq!(timer.interval(), -1.0);
836
837        timer.invalidate();
838        assert!(!timer.is_valid());
839        assert_eq!(timer.fire_delay(), -1.0);
840
841        timer.set_nextfire(10.0, 2.0);
842        assert!(timer.is_valid());
843        assert_eq!(timer.fire_delay(), 10.0);
844        assert_eq!(timer.interval(), 2.0);
845    }
846
847    #[test]
848    fn test_timer_queue_scheduling() {
849        let mut timerq = TimerQueue::new();
850
851        // Add some timers
852        let timer1 = timerq.add_timer(EngineBaseTimer::new(1.0, -1.0, false));
853        let timer2 = timerq.add_timer(EngineBaseTimer::new(2.0, -1.0, false));
854        let timer3 = timerq.add_timer(EngineBaseTimer::new(0.5, -1.0, false));
855
856        // Schedule them
857        timerq.schedule(timer1);
858        timerq.schedule(timer2);
859        timerq.schedule(timer3);
860
861        // Check armed count
862        assert_eq!(timerq.armed_count(), 3);
863
864        // Check next fire delay (should be timer3 at 0.5s)
865        let delay = timerq.nextfire_delay();
866        assert!(delay.is_some());
867        assert!((delay.unwrap() - 0.5).abs() < EPSILON);
868    }
869
870    #[test]
871    fn test_timer_queue_firing() {
872        let mut timerq = TimerQueue::new();
873
874        // Add timers with different delays
875        let timer1 = timerq.add_timer(EngineBaseTimer::new(0.1, -1.0, false));
876        let timer2 = timerq.add_timer(EngineBaseTimer::new(0.2, -1.0, false));
877
878        timerq.schedule(timer1);
879        timerq.schedule(timer2);
880
881        // Advance time past both timers
882        timerq.update_time(0.3);
883
884        // Fire expired timers
885        let fired = timerq.fire_expired();
886
887        // Should have fired both timers
888        assert_eq!(fired.len(), 2);
889
890        // Both should be invalid now (one-shot timers)
891        assert!(!timerq.is_valid(timer1));
892        assert!(!timerq.is_valid(timer2));
893    }
894
895    #[test]
896    fn test_timer_queue_invalidation() {
897        let mut timerq = TimerQueue::new();
898
899        let timer1 = timerq.add_timer(EngineBaseTimer::new(1.0, -1.0, false));
900        let timer2 = timerq.add_timer(EngineBaseTimer::new(2.0, -1.0, false));
901
902        timerq.schedule(timer1);
903        timerq.schedule(timer2);
904
905        // Invalidate timer1
906        timerq.invalidate(timer1);
907
908        // Should only have one armed timer now
909        assert_eq!(timerq.armed_count(), 1);
910
911        // Check next fire delay (should be timer2 at 2.0s)
912        let delay = timerq.nextfire_delay();
913        assert!(delay.is_some());
914        assert!((delay.unwrap() - 2.0).abs() < EPSILON);
915    }
916
917    #[test]
918    fn test_timer_queue_repeat() {
919        let mut timerq = TimerQueue::new();
920
921        // Add a repeating timer
922        let timer = timerq.add_timer(EngineBaseTimer::new(0.1, 0.1, false));
923        timerq.schedule(timer);
924
925        // Advance time past first fire
926        timerq.update_time(0.15);
927        let fired = timerq.fire_expired();
928        assert_eq!(fired.len(), 1);
929
930        // Timer should still be valid (repeating)
931        assert!(timerq.is_valid(timer));
932
933        // Advance time past second fire
934        timerq.update_time(0.25);
935        let fired = timerq.fire_expired();
936        assert_eq!(fired.len(), 1);
937
938        // Timer should still be valid
939        assert!(timerq.is_valid(timer));
940    }
941
942    #[test]
943    fn test_engine_error_types() {
944        let err = EngineError::Abort { kill: true };
945        assert!(matches!(err, EngineError::Abort { kill: true }));
946
947        let err = EngineError::Timeout;
948        assert!(matches!(err, EngineError::Timeout));
949
950        let err = EngineError::IllegalOperation("test".to_string());
951        assert!(matches!(err, EngineError::IllegalOperation(_)));
952
953        let err = EngineError::AlreadyRunning;
954        assert!(matches!(err, EngineError::AlreadyRunning));
955
956        let err = EngineError::NotSupported("test".to_string());
957        assert!(matches!(err, EngineError::NotSupported(_)));
958
959        let io_err = io::Error::new(io::ErrorKind::NotFound, "not found");
960        let err = EngineError::Io(io_err);
961        assert!(matches!(err, EngineError::Io(_)));
962    }
963
964    // -- NullBackend tests ---------------------------------------------------
965
966    #[test]
967    fn test_null_backend_name() {
968        let nb = NullBackend;
969        assert_eq!(nb.name(), "null");
970    }
971
972    #[test]
973    fn test_null_backend_wait_returns_empty() {
974        let mut nb = NullBackend;
975        let events = nb.wait(Some(0.0)).unwrap();
976        assert!(events.is_empty());
977    }
978
979    #[test]
980    fn test_null_backend_register_modify_unregister() {
981        let mut nb = NullBackend;
982        // SlotMap needs a ClientId but NullBackend ignores it — use a
983        // stand-in from an ephemeral slotmap.
984        let mut sm: SlotMap<ClientId, ()> = SlotMap::with_key();
985        let cid = sm.insert(());
986        nb.register(0, E_READ, cid).unwrap();
987        nb.modify(0, E_WRITE, cid).unwrap();
988        nb.unregister(0).unwrap();
989    }
990
991    // -- Engine with NullBackend tests --------------------------------------
992
993    struct DummyClient {
994        registered: bool,
995        started: bool,
996    }
997
998    impl DummyClient {
999        fn new() -> Self {
1000            Self {
1001                registered: false,
1002                started: false,
1003            }
1004        }
1005    }
1006
1007    impl EngineClient for DummyClient {
1008        fn start(&mut self) -> io::Result<()> {
1009            self.started = true;
1010            Ok(())
1011        }
1012
1013        fn close(&mut self, _abort: bool, _timeout: bool) {}
1014
1015        fn fd(&self) -> Option<RawFd> {
1016            None // no real fd
1017        }
1018
1019        fn events(&self) -> u32 {
1020            E_READ
1021        }
1022
1023        fn is_delayable(&self) -> bool {
1024            false
1025        }
1026
1027        fn is_registered(&self) -> bool {
1028            self.registered
1029        }
1030
1031        fn set_registered(&mut self, val: bool) {
1032            self.registered = val;
1033        }
1034
1035        fn autoclose(&self) -> bool {
1036            false
1037        }
1038    }
1039
1040    #[test]
1041    fn test_engine_creation() {
1042        let engine = Engine::with_backend(NullBackend, 0);
1043        assert!(!engine.is_running());
1044        assert!(!engine.exited());
1045        assert_eq!(engine.fanout(), 0);
1046        assert_eq!(engine.client_count(), 0);
1047    }
1048
1049    #[test]
1050    fn test_engine_add_remove_client() {
1051        let mut engine = Engine::with_backend(NullBackend, 0);
1052        let cid = engine.add_client(Box::new(DummyClient::new())).unwrap();
1053        assert_eq!(engine.client_count(), 1);
1054        engine.remove_client(cid);
1055        assert_eq!(engine.client_count(), 0);
1056    }
1057
1058    #[test]
1059    fn test_engine_add_timer() {
1060        let mut engine = Engine::with_backend(NullBackend, 0);
1061        let tid = engine.add_timer(EngineBaseTimer::new(1.0, -1.0, false));
1062        assert!(engine.timerq().is_valid(tid));
1063        engine.remove_timer(tid);
1064        assert!(!engine.timerq().is_valid(tid));
1065    }
1066
1067    #[test]
1068    fn test_engine_fire_timers() {
1069        let mut engine = Engine::with_backend(NullBackend, 0);
1070        let tid = engine.add_timer(EngineBaseTimer::new(0.1, -1.0, false));
1071        assert!(engine.timerq().is_valid(tid));
1072        engine.timerq_mut().update_time(1.0);
1073        engine.fire_timers();
1074        assert!(!engine.timerq().is_valid(tid));
1075    }
1076
1077    #[test]
1078    fn test_engine_abort() {
1079        let mut engine = Engine::with_backend(NullBackend, 0);
1080        engine.abort(false);
1081        assert!(!engine.is_running());
1082    }
1083
1084    #[test]
1085    fn test_engine_evloop_refcnt() {
1086        let mut engine = Engine::with_backend(NullBackend, 0);
1087        engine.evloop_acquire();
1088        engine.evloop_acquire();
1089        engine.evloop_release();
1090        // refcnt is 1, engine should still be "runnable"
1091        // evloop_release doesn't start the engine, just would stop it if it were running
1092        engine.evloop_release();
1093        // refcnt is 0, running should be false
1094        assert!(!engine.is_running());
1095    }
1096
1097    #[test]
1098    fn test_engine_stop() {
1099        let mut engine = Engine::with_backend(NullBackend, 0);
1100        // Simulate running state
1101        engine.stop();
1102        assert!(!engine.is_running());
1103    }
1104
1105    #[test]
1106    fn test_engine_start_all_clients() {
1107        let mut engine = Engine::with_backend(NullBackend, 0);
1108        engine.add_client(Box::new(DummyClient::new())).unwrap();
1109        engine.add_client(Box::new(DummyClient::new())).unwrap();
1110        engine.start_all_clients().unwrap();
1111        assert_eq!(engine.client_count(), 2);
1112    }
1113
1114    #[test]
1115    fn test_engine_update_client_events() {
1116        let mut engine = Engine::with_backend(NullBackend, 0);
1117        let cid = engine.add_client(Box::new(DummyClient::new())).unwrap();
1118        // DummyClient has no fd, so this is a no-op but shouldn't error
1119        engine.update_client_events(cid).unwrap();
1120    }
1121
1122    // -- EngineTimer tests --------------------------------------------------
1123
1124    #[test]
1125    fn test_engine_timer_creation() {
1126        let timer = EngineTimer::new(1.0, -1.0, false, || {});
1127        assert!(timer.base().is_valid());
1128        assert_eq!(timer.base().fire_delay(), 1.0);
1129        assert_eq!(timer.base().interval(), -1.0);
1130        assert!(!timer.base().autoclose());
1131    }
1132
1133    #[test]
1134    fn test_engine_timer_fire_calls_handler() {
1135        use std::sync::{
1136            atomic::{AtomicUsize, Ordering},
1137            Arc,
1138        };
1139        let counter = Arc::new(AtomicUsize::new(0));
1140        let counter_clone = counter.clone();
1141
1142        let mut timer = EngineTimer::new(0.5, -1.0, false, move || {
1143            counter_clone.fetch_add(1, Ordering::SeqCst);
1144        });
1145
1146        assert_eq!(counter.load(Ordering::SeqCst), 0);
1147        timer.fire();
1148        assert_eq!(counter.load(Ordering::SeqCst), 1);
1149        timer.fire();
1150        assert_eq!(counter.load(Ordering::SeqCst), 2);
1151    }
1152
1153    #[test]
1154    fn test_engine_timer_invalidate() {
1155        let mut timer = EngineTimer::new(1.0, -1.0, false, || {});
1156        assert!(timer.base().is_valid());
1157        timer.base_mut().invalidate();
1158        assert!(!timer.base().is_valid());
1159    }
1160
1161    #[test]
1162    fn test_engine_timer_debug() {
1163        let timer = EngineTimer::new(1.0, -1.0, false, || {});
1164        let debug_str = format!("{:?}", timer);
1165        assert!(debug_str.contains("EngineTimer"));
1166        assert!(debug_str.contains("<fn>"));
1167    }
1168
1169    #[test]
1170    fn test_engine_timer_with_autoclose() {
1171        let timer = EngineTimer::new(2.0, 0.5, true, || {});
1172        assert!(timer.base().autoclose());
1173        assert_eq!(timer.base().interval(), 0.5);
1174    }
1175}