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 core::time::Duration;
5use std::time::Instant;
6
7pub struct TimeoutManager {
8    start_time: Instant,
9    timeout: Duration,
10}
11
12impl TimeoutManager {
13    // 4ms per sched slice, give ~4x10 slices for safety
14    const MINIMUM_REAP_TIME: Duration = Duration::from_millis(160);
15
16    pub fn new(timeout: Duration) -> Self {
17        Self {
18            start_time: Instant::now(),
19            timeout,
20        }
21    }
22
23    pub fn remaining(&self) -> Duration {
24        // If elapsed > timeout, remaining will be 0
25        let elapsed = self.start_time.elapsed();
26        if elapsed >= self.timeout {
27            Self::MINIMUM_REAP_TIME
28        } else {
29            (self.timeout - elapsed).max(Self::MINIMUM_REAP_TIME)
30        }
31    }
32
33    pub fn elapsed(&self) -> Duration {
34        self.start_time.elapsed()
35    }
36
37    pub fn timeout(&self) -> Duration {
38        self.timeout
39    }
40}
41
42impl core::fmt::Debug for TimeoutManager {
43    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
44        f.debug_struct("TimeoutManager")
45            .field("start_time", &self.start_time)
46            .field("elapsed", &self.elapsed())
47            .field("timeout", &self.timeout)
48            .field("remaining", &self.remaining())
49            .finish()
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn test_timeout_manager_new() {
59        let timeout = Duration::from_secs(5);
60        let manager = TimeoutManager::new(timeout);
61
62        assert_eq!(manager.timeout(), timeout);
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
96    #[test]
97    fn test_timeout_manager_minimum_reap_time() {
98        let timeout = Duration::from_millis(50); // Less than MINIMUM_REAP_TIME
99        let manager = TimeoutManager::new(timeout);
100
101        // Even with a small timeout, remaining should be at least MINIMUM_REAP_TIME
102        assert_eq!(manager.remaining(), TimeoutManager::MINIMUM_REAP_TIME);
103    }
104
105    #[test]
106    fn test_timeout_manager_debug() {
107        let timeout = Duration::from_secs(1);
108        let manager = TimeoutManager::new(timeout);
109
110        let debug_str = format!("{manager:?}");
111
112        // Debug output should contain the expected fields
113        assert!(debug_str.contains("TimeoutManager"));
114        assert!(debug_str.contains("start_time"));
115        assert!(debug_str.contains("elapsed"));
116        assert!(debug_str.contains("timeout"));
117        assert!(debug_str.contains("remaining"));
118    }
119
120    #[test]
121    fn test_timeout_manager_timeout_exceeded() {
122        let timeout = Duration::from_millis(10);
123        let manager = TimeoutManager::new(timeout);
124
125        // Sleep longer than the timeout
126        std::thread::sleep(Duration::from_millis(50));
127
128        // Elapsed should be greater than timeout
129        assert!(manager.elapsed() > timeout);
130
131        // Remaining should still be at least MINIMUM_REAP_TIME (not overflow)
132        let remaining = manager.remaining();
133        assert_eq!(remaining, TimeoutManager::MINIMUM_REAP_TIME);
134    }
135}