1use crate::errors::PingerError;
2use std::{
3 future::Future,
4 pin::Pin,
5 task::{Context, Poll, Waker},
6 time::Duration,
7};
8use tokio::time::{Instant, Sleep};
9use tokio_stream::Stream;
10
11#[derive(Debug)]
14pub(crate) struct Pinger {
15 ping_timer: Pin<Box<Sleep>>,
17 ping_waker: Option<Waker>,
25 ping_interval: Duration,
27 timeout_timer: Pin<Box<Sleep>>,
29 timeout_waker: Option<Waker>,
33 timeout: Duration,
35 state: PingState,
37}
38
39impl Pinger {
42 pub(crate) fn new(ping_interval: Duration, timeout_duration: Duration) -> Self {
45 let now = Instant::now();
46 let ping_timer = tokio::time::sleep_until(now + ping_interval);
47 let timeout_timer = tokio::time::sleep(timeout_duration);
48 Self {
49 state: PingState::Ready,
50 ping_timer: Box::pin(ping_timer),
51 ping_waker: None,
52 ping_interval,
53 timeout_timer: Box::pin(timeout_timer),
54 timeout_waker: None,
55 timeout: timeout_duration,
56 }
57 }
58
59 pub(crate) fn on_pong(&mut self) -> Result<(), PingerError> {
62 match self.state {
63 PingState::Ready => Err(PingerError::UnexpectedPong),
64 PingState::WaitingForPong => {
65 self.state = PingState::Ready;
66 self.ping_timer.as_mut().reset(Instant::now() + self.ping_interval);
67 self.ping_waker = None;
68 self.timeout_waker = None;
69 Ok(())
70 }
71 PingState::TimedOut => {
72 self.state = PingState::Ready;
75 self.ping_timer.as_mut().reset(Instant::now() + self.ping_interval);
76 self.ping_waker = None;
77 self.timeout_waker = None;
78 Ok(())
79 }
80 }
81 }
82
83 pub(crate) const fn state(&self) -> PingState {
85 self.state
86 }
87
88 pub(crate) fn poll_ping(
91 &mut self,
92 cx: &mut Context<'_>,
93 ) -> Poll<Result<PingerEvent, PingerError>> {
94 match self.state() {
95 PingState::Ready => {
96 if self.ping_waker.as_ref().is_some_and(|waker| waker.will_wake(cx.waker())) &&
99 !self.ping_timer.is_elapsed()
100 {
101 return Poll::Pending
102 }
103
104 if self.ping_timer.as_mut().poll(cx).is_ready() {
105 self.timeout_timer.as_mut().reset(Instant::now() + self.timeout);
106 self.ping_waker = None;
107 self.timeout_waker = None;
108 self.state = PingState::WaitingForPong;
109 return Poll::Ready(Ok(PingerEvent::Ping))
110 }
111 self.ping_waker = Some(cx.waker().clone());
112 }
113 PingState::WaitingForPong => {
114 if self.timeout_waker.as_ref().is_some_and(|waker| waker.will_wake(cx.waker())) &&
116 !self.timeout_timer.is_elapsed()
117 {
118 return Poll::Pending
119 }
120
121 if self.timeout_timer.as_mut().poll(cx).is_ready() {
122 self.timeout_waker = None;
123 self.state = PingState::TimedOut;
124 return Poll::Ready(Ok(PingerEvent::Timeout))
125 }
126 self.timeout_waker = Some(cx.waker().clone());
127 }
128 PingState::TimedOut => {
129 return Poll::Pending
132 }
133 };
134 Poll::Pending
135 }
136}
137
138impl Stream for Pinger {
139 type Item = Result<PingerEvent, PingerError>;
140
141 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
142 self.get_mut().poll_ping(cx).map(Some)
143 }
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub(crate) enum PingState {
149 Ready,
152 WaitingForPong,
154 TimedOut,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
162pub(crate) enum PingerEvent {
163 Ping,
165
166 Timeout,
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use futures::StreamExt;
174 use std::{
175 sync::{
176 atomic::{AtomicUsize, Ordering},
177 Arc,
178 },
179 task::Wake,
180 };
181
182 #[tokio::test]
183 async fn test_poll_ping_with_cached_waker() {
184 struct CountingWaker(AtomicUsize);
185 impl Wake for CountingWaker {
186 fn wake(self: Arc<Self>) {
187 self.0.fetch_add(1, Ordering::Relaxed);
188 }
189 }
190
191 let ping_interval = Duration::from_millis(100);
192 let timeout = Duration::from_millis(100);
193 let slack = Duration::from_millis(50);
194 let mut pinger = Pinger::new(ping_interval, timeout);
195
196 let first = Arc::new(CountingWaker(AtomicUsize::new(0)));
197 let first_waker = Waker::from(Arc::clone(&first));
198 let mut cx = Context::from_waker(&first_waker);
199
200 for _ in 0..10 {
202 assert!(pinger.poll_ping(&mut cx).is_pending());
203 }
204
205 tokio::time::sleep(ping_interval + slack).await;
207 assert_eq!(first.0.load(Ordering::Relaxed), 1);
208 assert!(matches!(pinger.poll_ping(&mut cx), Poll::Ready(Ok(PingerEvent::Ping))));
209 pinger.on_pong().unwrap();
210
211 let second = Arc::new(CountingWaker(AtomicUsize::new(0)));
213 let second_waker = Waker::from(Arc::clone(&second));
214 let mut second_cx = Context::from_waker(&second_waker);
215 assert!(pinger.poll_ping(&mut cx).is_pending());
216 assert!(pinger.poll_ping(&mut second_cx).is_pending());
217
218 tokio::time::sleep(ping_interval + slack).await;
219 assert_eq!(first.0.load(Ordering::Relaxed), 1);
220 assert_eq!(second.0.load(Ordering::Relaxed), 1);
221 assert!(matches!(pinger.poll_ping(&mut second_cx), Poll::Ready(Ok(PingerEvent::Ping))));
222
223 assert!(pinger.poll_ping(&mut second_cx).is_pending());
225 tokio::time::sleep(timeout + slack).await;
226 assert_eq!(second.0.load(Ordering::Relaxed), 2);
227 assert!(matches!(pinger.poll_ping(&mut second_cx), Poll::Ready(Ok(PingerEvent::Timeout))));
228 }
229
230 #[tokio::test]
231 async fn test_ping_timeout() {
232 let interval = Duration::from_millis(300);
233 let mut pinger = Pinger::new(interval, Duration::from_millis(20));
235 assert_eq!(pinger.next().await.unwrap().unwrap(), PingerEvent::Ping);
236 pinger.on_pong().unwrap();
237 assert_eq!(pinger.next().await.unwrap().unwrap(), PingerEvent::Ping);
238
239 tokio::time::sleep(interval).await;
240 assert_eq!(pinger.next().await.unwrap().unwrap(), PingerEvent::Timeout);
241 pinger.on_pong().unwrap();
242
243 assert_eq!(pinger.next().await.unwrap().unwrap(), PingerEvent::Ping);
244 }
245}