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.remaining() >= TimeoutManager::MINIMUM_REAP_TIME);
63    }
64
65    #[test]
66    fn test_timeout_manager_remaining() {
67        let timeout = Duration::from_millis(100);
68        let manager = TimeoutManager::new(timeout);
69
70        // Initially, remaining should be close to timeout but at least MINIMUM_REAP_TIME
71        let remaining = manager.remaining();
72        assert!(remaining >= TimeoutManager::MINIMUM_REAP_TIME);
73        // Note: remaining might be greater than timeout due to MINIMUM_REAP_TIME
74
75        // After sleeping, remaining should decrease (but still respect MINIMUM_REAP_TIME)
76        std::thread::sleep(Duration::from_millis(10));
77        let remaining_after_sleep = manager.remaining();
78        assert!(remaining_after_sleep >= TimeoutManager::MINIMUM_REAP_TIME);
79    }
80
81    #[test]
82    fn test_timeout_manager_elapsed() {
83        let timeout = Duration::from_secs(1);
84        let manager = TimeoutManager::new(timeout);
85
86        // Initially elapsed should be very small
87        assert!(manager.elapsed() < Duration::from_millis(100));
88
89        // After sleeping, elapsed should increase
90        std::thread::sleep(Duration::from_millis(10));
91        let elapsed = manager.elapsed();
92        assert!(elapsed >= Duration::from_millis(10));
93    }
94
95    #[test]
96    fn test_timeout_manager_minimum_reap_time() {
97        let timeout = Duration::from_millis(50); // Less than MINIMUM_REAP_TIME
98        let manager = TimeoutManager::new(timeout);
99
100        // Even with a small timeout, remaining should be at least MINIMUM_REAP_TIME
101        assert_eq!(manager.remaining(), TimeoutManager::MINIMUM_REAP_TIME);
102    }
103
104    #[test]
105    fn test_timeout_manager_debug() {
106        let timeout = Duration::from_secs(1);
107        let manager = TimeoutManager::new(timeout);
108
109        let debug_str = format!("{manager:?}");
110
111        // Debug output should contain the expected fields
112        assert!(debug_str.contains("TimeoutManager"));
113        assert!(debug_str.contains("start_time"));
114        assert!(debug_str.contains("elapsed"));
115        assert!(debug_str.contains("timeout"));
116        assert!(debug_str.contains("remaining"));
117    }
118
119    #[test]
120    fn test_timeout_manager_timeout_exceeded() {
121        let timeout = Duration::from_millis(10);
122        let manager = TimeoutManager::new(timeout);
123
124        // Sleep longer than the timeout
125        std::thread::sleep(Duration::from_millis(50));
126
127        // Elapsed should be greater than timeout
128        assert!(manager.elapsed() > timeout);
129
130        // Remaining should still be at least MINIMUM_REAP_TIME (not overflow)
131        let remaining = manager.remaining();
132        assert_eq!(remaining, TimeoutManager::MINIMUM_REAP_TIME);
133    }
134}