pingora_timeout/
fast_timeout.rs

1// Copyright 2025 Cloudflare, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The fast and more complicated version of pingora-timeout
16//!
17//! The following optimizations are applied:
18//! - The timeouts lazily initialize their timer when the Future is pending for the first time.
19//! - There is no global lock for creating and cancelling timeouts.
20//! - Timeout timers are rounded to the next 10ms tick and timers are shared across all timeouts with the same deadline.
21//!
22//! In order for this to work, a standalone thread is created to arm the timers, which has some
23//! overhead. As a general rule, the benefits of this don't outweigh the overhead unless
24//! there are more than about 100 timeout() calls/sec in the system. Use regular tokio timeout or
25//! [super::tokio_timeout] in the low usage case.
26
27use super::timer::*;
28use super::*;
29use once_cell::sync::Lazy;
30use std::sync::Arc;
31
32static TIMER_MANAGER: Lazy<Arc<TimerManager>> = Lazy::new(|| {
33    let tm = Arc::new(TimerManager::new());
34    check_clock_thread(&tm);
35    tm
36});
37
38fn check_clock_thread(tm: &Arc<TimerManager>) {
39    if tm.should_i_start_clock() {
40        std::thread::Builder::new()
41            .name("Timer thread".into())
42            .spawn(|| TIMER_MANAGER.clock_thread())
43            .unwrap();
44    }
45}
46
47/// The timeout generated by [fast_timeout()].
48///
49/// Users don't need to interact with this object.
50pub struct FastTimeout(Duration);
51
52impl ToTimeout for FastTimeout {
53    fn timeout(&self) -> Pin<Box<dyn Future<Output = ()> + Send + Sync>> {
54        Box::pin(TIMER_MANAGER.register_timer(self.0).poll())
55    }
56
57    fn create(d: Duration) -> Self {
58        FastTimeout(d)
59    }
60}
61
62/// Similar to [tokio::time::timeout] but more efficient.
63pub fn fast_timeout<T>(duration: Duration, future: T) -> Timeout<T, FastTimeout>
64where
65    T: Future,
66{
67    check_clock_thread(&TIMER_MANAGER);
68    Timeout::new_with_delay(future, duration)
69}
70
71/// Similar to [tokio::time::sleep] but more efficient.
72pub async fn fast_sleep(duration: Duration) {
73    check_clock_thread(&TIMER_MANAGER);
74    TIMER_MANAGER.register_timer(duration).poll().await
75}
76
77/// Pause the timer for fork()
78///
79/// Because RwLock across fork() is undefined behavior, this function makes sure that no one
80/// holds any locks.
81///
82/// This function should be called right before fork().
83pub fn pause_for_fork() {
84    TIMER_MANAGER.pause_for_fork();
85}
86
87/// Unpause the timer after fork()
88///
89/// This function should be called right after fork().
90pub fn unpause() {
91    TIMER_MANAGER.unpause();
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[tokio::test]
99    async fn test_timeout() {
100        let fut = tokio_sleep(Duration::from_secs(1000));
101        let to = fast_timeout(Duration::from_secs(1), fut);
102        assert!(to.await.is_err())
103    }
104
105    #[tokio::test]
106    async fn test_instantly_return() {
107        let fut = async { 1 };
108        let to = fast_timeout(Duration::from_secs(1), fut);
109        assert_eq!(to.await.unwrap(), 1)
110    }
111
112    #[tokio::test]
113    async fn test_delayed_return() {
114        let fut = async {
115            tokio_sleep(Duration::from_secs(1)).await;
116            1
117        };
118        let to = fast_timeout(Duration::from_secs(1000), fut);
119        assert_eq!(to.await.unwrap(), 1)
120    }
121
122    #[tokio::test]
123    async fn test_sleep() {
124        let fut = async {
125            fast_sleep(Duration::from_secs(1)).await;
126            1
127        };
128        let to = fast_timeout(Duration::from_secs(1000), fut);
129        assert_eq!(to.await.unwrap(), 1)
130    }
131}