Skip to main content

libdd_common/
timeout.rs

1// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4use std::time::{Duration, Instant};
5
6pub struct TimeoutManager {
7    start_time: Instant,
8    timeout: Duration,
9}
10
11impl TimeoutManager {
12    // 4ms per sched slice, give ~4x10 slices for safety
13    const MINIMUM_REAP_TIME: Duration = Duration::from_millis(160);
14
15    pub fn new(timeout: Duration) -> Self {
16        Self {
17            start_time: Instant::now(),
18            timeout,
19        }
20    }
21
22    pub fn remaining(&self) -> Duration {
23        // If elapsed > timeout, remaining will be 0
24        let elapsed = self.start_time.elapsed();
25        if elapsed >= self.timeout {
26            Self::MINIMUM_REAP_TIME
27        } else {
28            (self.timeout - elapsed).max(Self::MINIMUM_REAP_TIME)
29        }
30    }
31
32    pub fn elapsed(&self) -> Duration {
33        self.start_time.elapsed()
34    }
35
36    pub fn timeout(&self) -> Duration {
37        self.timeout
38    }
39}
40
41impl std::fmt::Debug for TimeoutManager {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        f.debug_struct("TimeoutManager")
44            .field("start_time", &self.start_time)
45            .field("elapsed", &self.elapsed())
46            .field("timeout", &self.timeout)
47            .field("remaining", &self.remaining())
48            .finish()
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn test_timeout_manager_new() {
58        let timeout = Duration::from_secs(5);
59        let manager = TimeoutManager::new(timeout);
60
61        assert_eq!(manager.timeout(), timeout);
62        assert!(manager.elapsed() < Duration::from_millis(100)); // Should be very small
63        assert!(manager.remaining() >= TimeoutManager::MINIMUM_REAP_TIME);
64    }
65
66    #[test]
67    fn test_timeout_manager_remaining() {
68        let timeout = Duration::from_millis(100);
69        let manager = TimeoutManager::new(timeout);
70
71        // Initially, remaining should be close to timeout but at least MINIMUM_REAP_TIME
72        let remaining = manager.remaining();
73        assert!(remaining >= TimeoutManager::MINIMUM_REAP_TIME);
74        // Note: remaining might be greater than timeout due to MINIMUM_REAP_TIME
75
76        // After sleeping, remaining should decrease (but still respect MINIMUM_REAP_TIME)
77        std::thread::sleep(Duration::from_millis(10));
78        let remaining_after_sleep = manager.remaining();
79        assert!(remaining_after_sleep >= TimeoutManager::MINIMUM_REAP_TIME);
80    }
81
82    #[test]
83    fn test_timeout_manager_elapsed() {
84        let timeout = Duration::from_secs(1);
85        let manager = TimeoutManager::new(timeout);
86
87        // Initially elapsed should be very small
88        assert!(manager.elapsed() < Duration::from_millis(100));
89
90        // After sleeping, elapsed should increase
91        std::thread::sleep(Duration::from_millis(10));
92        let elapsed = manager.elapsed();
93        assert!(elapsed >= Duration::from_millis(10));
94
95        #[cfg(not(miri))] // miri allows the clock to go arbitrarily fast
96        assert!(elapsed < Duration::from_millis(100)); // Should be reasonable
97    }
98
99    #[test]
100    fn test_timeout_manager_minimum_reap_time() {
101        let timeout = Duration::from_millis(50); // Less than MINIMUM_REAP_TIME
102        let manager = TimeoutManager::new(timeout);
103
104        // Even with a small timeout, remaining should be at least MINIMUM_REAP_TIME
105        assert_eq!(manager.remaining(), TimeoutManager::MINIMUM_REAP_TIME);
106    }
107
108    #[test]
109    fn test_timeout_manager_debug() {
110        let timeout = Duration::from_secs(1);
111        let manager = TimeoutManager::new(timeout);
112
113        let debug_str = format!("{manager:?}");
114
115        // Debug output should contain the expected fields
116        assert!(debug_str.contains("TimeoutManager"));
117        assert!(debug_str.contains("start_time"));
118        assert!(debug_str.contains("elapsed"));
119        assert!(debug_str.contains("timeout"));
120        assert!(debug_str.contains("remaining"));
121    }
122
123    #[test]
124    fn test_timeout_manager_timeout_exceeded() {
125        let timeout = Duration::from_millis(10);
126        let manager = TimeoutManager::new(timeout);
127
128        // Sleep longer than the timeout
129        std::thread::sleep(Duration::from_millis(50));
130
131        // Elapsed should be greater than timeout
132        assert!(manager.elapsed() > timeout);
133
134        // Remaining should still be at least MINIMUM_REAP_TIME (not overflow)
135        let remaining = manager.remaining();
136        assert_eq!(remaining, TimeoutManager::MINIMUM_REAP_TIME);
137    }
138}