1use std::{cell::Cell, convert::Infallible, fmt, rc::Rc, rc::Weak, time};
2
3use ntex_service::{BindCtx, Ctx, ReadyNotifier, Service, ServiceFactory};
4
5use crate::time::{CallbackTimer, Millis, now};
6
7pub struct KeepAlive<F, E>
16where
17 F: Fn() -> E + Clone,
18{
19 f: F,
20 ka: Millis,
21}
22
23impl<F, E> KeepAlive<F, E>
24where
25 F: Fn() -> E + Clone,
26{
27 pub fn new(ka: Millis, f: F) -> Self {
32 KeepAlive { f, ka }
33 }
34}
35
36impl<F, E> Clone for KeepAlive<F, E>
37where
38 F: Fn() -> E + Clone,
39{
40 fn clone(&self) -> Self {
41 KeepAlive {
42 f: self.f.clone(),
43 ka: self.ka,
44 }
45 }
46}
47
48impl<F, E> fmt::Debug for KeepAlive<F, E>
49where
50 F: Fn() -> E + Clone,
51{
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 f.debug_struct("KeepAlive")
54 .field("ka", &self.ka)
55 .field("f", &std::any::type_name::<F>())
56 .finish()
57 }
58}
59
60impl<F, E, St, Req> ServiceFactory<St, Req> for KeepAlive<F, E>
61where
62 F: Fn() -> E + Clone,
63{
64 type Res = Req;
65 type Error = E;
66
67 type Service = KeepAliveService<F, E>;
68 type InitError = Infallible;
69
70 #[inline]
71 async fn create(&self, _: &St) -> Result<Self::Service, Self::InitError> {
72 Ok(KeepAliveService::new(self.ka, self.f.clone()))
73 }
74}
75
76pub struct KeepAliveService<F, E>
80where
81 F: Fn() -> E,
82{
83 f: F,
84 shared: Rc<Shared>,
85}
86
87struct Shared {
88 dur: time::Duration,
89 timer: CallbackTimer,
90 expire: Cell<time::Instant>,
91 expired: Cell<bool>,
92 stopped: Cell<bool>,
93 changed: ReadyNotifier,
94}
95
96impl Shared {
97 fn on_timeout(&self) {
98 let now = now();
99 let expire = self.expire.get() + self.dur;
100 if expire <= now {
101 self.expired.set(true);
102 self.changed.notify();
103 } else {
104 let millis: u32 = (expire - now).as_millis().try_into().unwrap_or(u32::MAX);
105 self.timer.reset(Millis(millis.max(1)));
106 }
107 }
108}
109
110impl<F, E> KeepAliveService<F, E>
111where
112 F: Fn() -> E,
113{
114 pub fn new(dur: Millis, f: F) -> Self {
119 KeepAliveService {
120 f,
121 shared: Rc::new_cyclic(|weak: &Weak<Shared>| {
122 let weak = weak.clone();
123 Shared {
124 dur: time::Duration::from(dur),
125 expire: Cell::new(now()),
126 expired: Cell::new(false),
127 stopped: Cell::new(false),
128 changed: ReadyNotifier::new(),
129 timer: CallbackTimer::new(dur, move || {
130 if let Some(shared) = weak.upgrade() {
131 shared.on_timeout();
132 }
133 }),
134 }
135 }),
136 }
137 }
138}
139
140impl<F, E> fmt::Debug for KeepAliveService<F, E>
141where
142 F: Fn() -> E,
143{
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 f.debug_struct("KeepAliveService")
146 .field("dur", &self.shared.dur)
147 .field("expire", &self.shared.expire)
148 .field("f", &std::any::type_name::<F>())
149 .finish()
150 }
151}
152
153impl<F, E, St, Req> Service<St, Req> for KeepAliveService<F, E>
154where
155 F: Fn() -> E,
156{
157 type Res = Req;
158 type Error = E;
159
160 fn bind(&self, ctx: BindCtx<'_>) {
161 ctx.subscribe(&self.shared.changed);
162 }
163
164 async fn ready(&self, _: Ctx<'_, Self, St>) -> Result<(), Self::Error> {
165 if self.shared.expire.get() + self.shared.dur <= now() {
166 Err((self.f)())
167 } else {
168 Ok(())
169 }
170 }
171
172 #[inline]
173 async fn call(&self, req: Req, _: Ctx<'_, Self, St>) -> Result<Req, E> {
174 let shared = &self.shared;
175 let now = now();
176 let previous = shared.expire.replace(now);
177 let expired = shared.expired.replace(false);
178 if expired || previous + shared.dur <= now {
179 if !shared.stopped.get() {
180 shared.timer.reset(shared.dur.into());
181 }
182 shared.changed.notify();
183 }
184 Ok(req)
185 }
186
187 async fn shutdown(&self, _: Ctx<'_, Self, St>) {
188 self.shared.stopped.set(true);
189 self.shared.timer.cancel();
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use std::{pin::Pin, task::Context, task::Poll, task::ready};
196
197 use ntex_service::{Pipeline, ReadyCallback, boxed, factory};
198
199 use super::*;
200 use crate::{channel::oneshot, spawn, time::sleep};
201
202 #[derive(Debug, PartialEq)]
203 struct TestErr;
204
205 struct Dispatcher {
206 p: Pipeline<usize, usize, TestErr>,
207 tx: Option<oneshot::Sender<()>>,
208 }
209
210 impl Future for Dispatcher {
211 type Output = ();
212
213 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
214 let mut this = self.as_mut();
215 if ready!(this.p.poll_ready(cx)).is_err() {
216 if let Some(tx) = this.tx.take() {
217 let _ = tx.send(());
218 }
219 Poll::Ready(())
220 } else {
221 Poll::Pending
222 }
223 }
224 }
225
226 #[ntex::test]
232 async fn test_ka() {
233 let factory = factory::<_, (), usize>(KeepAlive::new(Millis(500), || TestErr));
236 assert!(format!("{factory:?}").contains("KeepAlive"));
237 let _ = factory.clone();
238
239 let svc = factory.create(&()).await.unwrap();
240 assert!(format!("{svc:?}").contains("KeepAliveService"));
241
242 let p = Pipeline::new((), boxed::service(svc));
243 assert_eq!(p.call(1usize).await, Ok(1usize));
244 let svc = p.bind();
245
246 let (tx, rx) = oneshot::channel();
247 drop(spawn(Dispatcher { p, tx: Some(tx) }));
248
249 sleep(Millis(25)).await;
250 assert_eq!(svc.call(1usize).await, Ok(1usize));
251 sleep(Millis(100)).await;
252
253 let res = rx.await;
254 assert_eq!(res, Ok(()));
255 assert_eq!(svc.ready().await, Err(TestErr));
256 }
257
258 #[ntex::test]
264 async fn test_ka_sub_millis() {
265 let svc = Rc::new(KeepAliveService::new(Millis(100), || TestErr));
266 let p = Pipeline::<usize, usize, TestErr>::new((), svc.clone()).bind();
267 svc.shared.timer.cancel();
268
269 svc.shared
271 .expire
272 .set(now().checked_sub(svc.shared.dur).unwrap() + time::Duration::from_micros(500));
273 svc.shared.on_timeout();
274 assert_eq!(p.ready().await, Ok(()));
275 assert!(!svc.shared.expired.get());
276 assert!(!svc.shared.timer.is_elapsed());
277 }
278
279 fn notification(p: &Pipeline<usize, usize, TestErr>) -> oneshot::Receiver<()> {
280 let (tx, rx) = oneshot::channel();
281 let tx = Cell::new(Some(tx));
282 p.on_notify(&ReadyCallback::new(move |_| {
283 if let Some(tx) = tx.take() {
284 let _ = tx.send(());
285 }
286 }));
287 rx
288 }
289
290 #[ntex::test]
296 async fn idle_timer_invalidates_completed_readiness_for_all_roots() {
297 let svc = Rc::new(KeepAliveService::new(Millis(25), || TestErr));
298 let first = Pipeline::new((), svc.clone());
299 let second = Pipeline::<usize, usize, TestErr>::new((), svc);
300 assert_eq!(first.ready().await, Ok(()));
301 assert_eq!(second.ready().await, Ok(()));
302 let expired = notification(&first);
303
304 crate::time::timeout(Millis(1_000), expired)
305 .await
306 .unwrap()
307 .unwrap();
308 assert!(!first.is_ready());
309 assert!(!second.is_ready());
310 assert_eq!(first.ready().await, Err(TestErr));
311 assert_eq!(second.ready().await, Err(TestErr));
312 assert_eq!(first.call(1).await, Err(TestErr));
313 }
314
315 #[ntex::test]
321 async fn call_extends_deadline_before_old_timer_is_processed() {
322 let svc = Rc::new(KeepAliveService::new(Millis(250), || TestErr));
323 let p = Pipeline::new((), svc.clone());
324 let notifications = Rc::new(Cell::new(0));
325 let count = notifications.clone();
326 p.on_notify(&ReadyCallback::new(move |_| count.set(count.get() + 1)));
327 assert_eq!(p.ready().await, Ok(()));
328 svc.shared.timer.reset(Millis::ZERO);
329 assert_eq!(p.call(1).await, Ok(1));
330 sleep(Millis(50)).await;
331 assert!(p.is_ready());
332 assert_eq!(notifications.get(), 0);
333 assert!(!svc.shared.expired.get());
334 assert!(!svc.shared.timer.is_elapsed());
335
336 crate::time::timeout(Millis(1_000), notification(&p))
337 .await
338 .unwrap()
339 .unwrap();
340 assert_eq!(notifications.get(), 1);
341 assert_eq!(p.ready().await, Err(TestErr));
342 assert_eq!(p.call(2).await, Err(TestErr));
343 }
344
345 struct Bypass<S>(S);
346
347 impl<S: Service<(), usize, Res = usize, Error = TestErr>> Service<(), usize> for Bypass<S> {
348 type Res = usize;
349 type Error = TestErr;
350
351 #[inline]
352 fn bind(&self, ctx: BindCtx<'_>) {
353 ctx.bind(&self.0);
354 }
355
356 async fn call(&self, req: usize, ctx: Ctx<'_, Self>) -> Result<usize, TestErr> {
358 ctx.call(&self.0, req).await
359 }
360 }
361
362 #[ntex::test]
368 async fn bypassed_call_rearms_an_expired_timer() {
369 let svc = Rc::new(KeepAliveService::new(Millis(25), || TestErr));
370 let p = Pipeline::new((), svc.clone());
371 let bypass = Pipeline::new((), Bypass(svc.clone()));
372 p.ready().await.unwrap();
373 crate::time::timeout(Millis(1_000), notification(&p))
374 .await
375 .unwrap()
376 .unwrap();
377 assert_eq!(p.ready().await, Err(TestErr));
378
379 assert_eq!(bypass.call(1).await, Ok(1));
380 assert!(!svc.shared.timer.is_elapsed());
381 assert_eq!(p.ready().await, Ok(()));
382 crate::time::timeout(Millis(1_000), notification(&p))
383 .await
384 .unwrap()
385 .unwrap();
386 assert_eq!(p.ready().await, Err(TestErr));
387 }
388
389 #[ntex::test]
395 async fn timer_callback_is_canceled_on_shutdown_and_drop() {
396 for shutdown in [false, true] {
397 let svc = Rc::new(KeepAliveService::new(Millis(25), || TestErr));
398 let state = Rc::downgrade(&svc.shared);
399 let p = Pipeline::<usize, usize, TestErr>::new((), svc.clone());
400 let notifications = Rc::new(Cell::new(0));
401 let count = notifications.clone();
402 p.on_notify(&ReadyCallback::new(move |_| count.set(count.get() + 1)));
403 p.ready().await.unwrap();
404 if shutdown {
405 p.shutdown().await;
406 assert!(svc.shared.timer.is_elapsed());
407 sleep(Millis(75)).await;
408 assert_eq!(notifications.get(), 0);
409
410 let bypass = Pipeline::new((), Bypass(svc.clone()));
411 assert_eq!(bypass.call(1).await, Ok(1));
412 assert!(svc.shared.timer.is_elapsed());
413 let before = notifications.get();
414 sleep(Millis(75)).await;
415 assert_eq!(notifications.get(), before);
416 }
417 let before = notifications.get();
418 drop(p);
419 drop(svc);
420 assert!(state.upgrade().is_none());
421 sleep(Millis(75)).await;
422 assert_eq!(notifications.get(), before);
423 }
424 }
425
426 #[ntex::test]
431 async fn test_ka_zero() {
432 let p = Pipeline::<usize, usize, TestErr>::new(
433 (),
434 KeepAliveService::new(Millis::ZERO, || TestErr),
435 );
436 assert_eq!(p.ready().await, Err(TestErr));
437 assert_eq!(p.call(1).await, Err(TestErr));
438 }
439}