1extern crate alloc;
4use alloc::boxed::Box;
5use alloc::rc::Rc;
6use alloc::vec::Vec;
7use core::cell::Cell;
8use core::fmt;
9
10use crate::backoff::Backoff;
11use crate::policy::RenewalPolicy;
12pub use grafos_core::RevokeState;
13
14#[derive(Clone, Debug)]
20pub struct LeaseHandle {
21 lease_id: u128,
22 revoke_state: Rc<Cell<RevokeState>>,
23}
24
25impl LeaseHandle {
26 fn new(lease_id: u128) -> Self {
27 Self {
28 lease_id,
29 revoke_state: Rc::new(Cell::new(RevokeState::Active)),
30 }
31 }
32
33 pub fn lease_id(&self) -> u128 {
35 self.lease_id
36 }
37
38 pub fn revoke_state(&self) -> RevokeState {
40 self.revoke_state.get()
41 }
42
43 pub fn is_revoke_terminal(&self) -> bool {
45 self.revoke_state().is_terminal()
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
51pub enum LeaseRevokeTransitionError {
52 LeaseNotRegistered { lease_id: u128 },
54 IllegalTransition {
56 lease_id: u128,
57 from: RevokeState,
58 to: RevokeState,
59 },
60}
61
62impl fmt::Display for LeaseRevokeTransitionError {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 Self::LeaseNotRegistered { lease_id } => {
66 write!(f, "lease {lease_id} is not registered")
67 }
68 Self::IllegalTransition { lease_id, from, to } => {
69 write!(
70 f,
71 "illegal revoke-state transition for lease {lease_id}: {from} -> {to}"
72 )
73 }
74 }
75 }
76}
77
78#[cfg(feature = "std")]
79impl std::error::Error for LeaseRevokeTransitionError {}
80
81struct Entry {
83 lease_id: u128,
84 created_at: u64,
85 expires_at: u64,
86 policy: RenewalPolicy,
87 backoff: Backoff,
88 next_retry_at: u64,
90 last_renewed_at: Option<u64>,
93 revoke_state: Rc<Cell<RevokeState>>,
95}
96
97impl Entry {
98 fn renewal_deadline(&self) -> u64 {
99 let created = self.last_renewed_at.unwrap_or(self.created_at);
100 let jitter_seed = self.lease_id as u64;
101 self.policy
102 .renewal_deadline(created, self.expires_at, jitter_seed)
103 }
104
105 fn is_near_expiry(&self, now: u64) -> bool {
106 if now >= self.expires_at {
107 return true;
108 }
109 let remaining = self.expires_at - now;
110 let total_ttl = self
111 .expires_at
112 .saturating_sub(self.last_renewed_at.unwrap_or(self.created_at));
113 if total_ttl == 0 {
114 return true;
115 }
116 remaining < total_ttl / 10
118 }
119
120 fn handle(&self) -> LeaseHandle {
121 LeaseHandle {
122 lease_id: self.lease_id,
123 revoke_state: Rc::clone(&self.revoke_state),
124 }
125 }
126
127 fn transition_revoke_state(
128 &self,
129 next: RevokeState,
130 ) -> Result<RevokeState, LeaseRevokeTransitionError> {
131 let current = self.revoke_state.get();
132 if current == next {
133 return Ok(current);
134 }
135 if !current.legal_transition_to(next) {
136 return Err(LeaseRevokeTransitionError::IllegalTransition {
137 lease_id: self.lease_id,
138 from: current,
139 to: next,
140 });
141 }
142 self.revoke_state.set(next);
143 Ok(next)
144 }
145}
146
147#[derive(Debug, Clone, Default)]
149pub struct RenewalSummary {
150 pub renewed: u32,
152 pub skipped: u32,
154 pub failed: u32,
156 pub near_expiry: Vec<u128>,
158}
159
160pub struct RenewalManager {
169 entries: Vec<Entry>,
170 revoke_callbacks: Vec<Box<dyn Fn(u128, u8)>>,
171}
172
173impl RenewalManager {
174 pub fn new() -> Self {
176 RenewalManager {
177 entries: Vec::new(),
178 revoke_callbacks: Vec::new(),
179 }
180 }
181
182 pub fn register(&mut self, lease_id: u128, expires_at: u64, policy: RenewalPolicy) {
187 let created_at = expires_at.saturating_sub(policy.min_renew_secs);
188 let _ = self.register_entry(lease_id, created_at, expires_at, policy);
189 }
190
191 pub fn register_handle(
193 &mut self,
194 lease_id: u128,
195 expires_at: u64,
196 policy: RenewalPolicy,
197 ) -> LeaseHandle {
198 let created_at = expires_at.saturating_sub(policy.min_renew_secs);
199 self.register_entry(lease_id, created_at, expires_at, policy)
200 }
201
202 pub fn register_with_created_at(
204 &mut self,
205 lease_id: u128,
206 created_at: u64,
207 expires_at: u64,
208 policy: RenewalPolicy,
209 ) {
210 let _ = self.register_entry(lease_id, created_at, expires_at, policy);
211 }
212
213 pub fn register_with_created_at_handle(
216 &mut self,
217 lease_id: u128,
218 created_at: u64,
219 expires_at: u64,
220 policy: RenewalPolicy,
221 ) -> LeaseHandle {
222 self.register_entry(lease_id, created_at, expires_at, policy)
223 }
224
225 fn register_entry(
226 &mut self,
227 lease_id: u128,
228 created_at: u64,
229 expires_at: u64,
230 policy: RenewalPolicy,
231 ) -> LeaseHandle {
232 self.entries.retain(|e| e.lease_id != lease_id);
234 let backoff = Backoff::new(1, policy.max_backoff_secs);
235 let handle = LeaseHandle::new(lease_id);
236 self.entries.push(Entry {
237 lease_id,
238 created_at,
239 expires_at,
240 policy,
241 backoff,
242 next_retry_at: 0,
243 last_renewed_at: None,
244 revoke_state: Rc::clone(&handle.revoke_state),
245 });
246 handle
247 }
248
249 pub fn unregister(&mut self, lease_id: u128) {
251 self.entries.retain(|e| e.lease_id != lease_id);
252 }
253
254 pub fn handle(&self, lease_id: u128) -> Option<LeaseHandle> {
256 self.entries
257 .iter()
258 .find(|e| e.lease_id == lease_id)
259 .map(Entry::handle)
260 }
261
262 pub fn revoke_state(&self, lease_id: u128) -> Option<RevokeState> {
264 self.handle(lease_id).map(|handle| handle.revoke_state())
265 }
266
267 pub fn len(&self) -> usize {
269 self.entries.len()
270 }
271
272 pub fn is_empty(&self) -> bool {
274 self.entries.is_empty()
275 }
276
277 pub fn tick(&mut self, now_unix_secs: u64) -> RenewalSummary {
281 self.tick_with(now_unix_secs, |_lease_id, duration| {
282 Ok(duration)
284 })
285 }
286
287 pub fn tick_with<F>(&mut self, now_unix_secs: u64, mut renew_fn: F) -> RenewalSummary
292 where
293 F: FnMut(u128, u64) -> Result<u64, ()>,
294 {
295 let mut summary = RenewalSummary::default();
296
297 for entry in self.entries.iter_mut() {
298 if entry.is_near_expiry(now_unix_secs) {
300 summary.near_expiry.push(entry.lease_id);
301 }
302
303 if now_unix_secs >= entry.expires_at {
305 if entry.revoke_state.get() == RevokeState::Active {
306 let _ = entry.transition_revoke_state(RevokeState::Expired);
307 }
308 summary.skipped += 1;
309 continue;
310 }
311
312 let deadline = entry.renewal_deadline();
314 if now_unix_secs < deadline {
315 summary.skipped += 1;
316 continue;
317 }
318
319 if now_unix_secs < entry.next_retry_at {
321 summary.skipped += 1;
322 continue;
323 }
324
325 let duration = entry.policy.min_renew_secs;
327 match renew_fn(entry.lease_id, duration) {
328 Ok(new_expires_at) => {
329 let new_exp = now_unix_secs.saturating_add(new_expires_at);
330 entry.last_renewed_at = Some(now_unix_secs);
331 entry.expires_at = new_exp;
332 entry.backoff.reset();
333 entry.next_retry_at = 0;
334 summary.renewed += 1;
335
336 #[cfg(feature = "observe")]
337 emit_renew_success(entry.lease_id);
338 }
339 Err(()) => {
340 let delay = entry.backoff.next_delay();
341 entry.next_retry_at = now_unix_secs.saturating_add(delay);
342 summary.failed += 1;
343
344 #[cfg(feature = "observe")]
345 emit_renew_failure(entry.lease_id);
346 }
347 }
348 }
349
350 #[cfg(feature = "observe")]
351 emit_tick_metrics(&summary, self.entries.len());
352
353 summary
354 }
355
356 pub fn is_near_expiry(&self, lease_id: u128, now: u64) -> bool {
358 self.entries
359 .iter()
360 .find(|e| e.lease_id == lease_id)
361 .map(|e| e.is_near_expiry(now))
362 .unwrap_or(false)
363 }
364
365 pub fn on_revoked<F: Fn(u128, u8) + 'static>(&mut self, callback: F) {
370 self.revoke_callbacks.push(Box::new(callback));
371 }
372
373 pub fn transition_revoke_state(
378 &self,
379 lease_id: u128,
380 next: RevokeState,
381 ) -> Result<RevokeState, LeaseRevokeTransitionError> {
382 let entry = self
383 .entries
384 .iter()
385 .find(|e| e.lease_id == lease_id)
386 .ok_or(LeaseRevokeTransitionError::LeaseNotRegistered { lease_id })?;
387 entry.transition_revoke_state(next)
388 }
389
390 pub fn notify_revoked(&self, lease_id: u128, reason: u8) {
394 if let Some(entry) = self.entries.iter().find(|e| e.lease_id == lease_id) {
395 if entry.revoke_state.get() == RevokeState::Active {
396 let _ = entry.transition_revoke_state(RevokeState::RevokeWarning);
397 }
398 }
399 for cb in &self.revoke_callbacks {
400 cb(lease_id, reason);
401 }
402 }
403
404 pub fn expires_at(&self, lease_id: u128) -> Option<u64> {
406 self.entries
407 .iter()
408 .find(|e| e.lease_id == lease_id)
409 .map(|e| e.expires_at)
410 }
411}
412
413impl Default for RenewalManager {
414 fn default() -> Self {
415 Self::new()
416 }
417}
418
419#[cfg(feature = "observe")]
420fn emit_renew_success(lease_id: u128) {
421 let _ = lease_id;
422 grafos_observe::FabricMetrics::global().ops_total.inc();
423}
424
425#[cfg(feature = "observe")]
426fn emit_renew_failure(lease_id: u128) {
427 let _ = lease_id;
428 grafos_observe::FabricMetrics::global().ops_total.inc();
429}
430
431#[cfg(feature = "observe")]
432fn emit_tick_metrics(summary: &RenewalSummary, managed_count: usize) {
433 let _ = (summary, managed_count);
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439
440 #[test]
441 fn register_and_tick_before_deadline() {
442 let mut mgr = RenewalManager::new();
443 let policy = RenewalPolicy {
444 renew_at_fraction: 0.75,
445 jitter_fraction: 0.0,
446 min_renew_secs: 100,
447 max_backoff_secs: 5,
448 };
449 mgr.register_with_created_at(1, 1000, 1100, policy);
451
452 let s = mgr.tick(1050);
454 assert_eq!(s.renewed, 0);
455 assert_eq!(s.skipped, 1);
456 assert_eq!(s.failed, 0);
457 }
458
459 #[test]
460 fn tick_at_deadline_triggers_renewal() {
461 let mut mgr = RenewalManager::new();
462 let policy = RenewalPolicy {
463 renew_at_fraction: 0.75,
464 jitter_fraction: 0.0,
465 min_renew_secs: 100,
466 max_backoff_secs: 5,
467 };
468 mgr.register_with_created_at(1, 1000, 1100, policy);
469
470 let s = mgr.tick(1075);
472 assert_eq!(s.renewed, 1);
473 assert_eq!(s.skipped, 0);
474 }
475
476 #[test]
477 fn tick_after_expiry_skips() {
478 let mut mgr = RenewalManager::new();
479 let policy = RenewalPolicy {
480 renew_at_fraction: 0.75,
481 jitter_fraction: 0.0,
482 min_renew_secs: 100,
483 max_backoff_secs: 5,
484 };
485 mgr.register_with_created_at(1, 1000, 1100, policy);
486
487 let s = mgr.tick(1200);
488 assert_eq!(s.renewed, 0);
489 assert_eq!(s.skipped, 1);
490 }
491
492 #[test]
493 fn register_handle_exposes_active_revoke_state() {
494 let mut mgr = RenewalManager::new();
495 let handle = mgr.register_handle(1, 1100, RenewalPolicy::default());
496
497 assert_eq!(handle.lease_id(), 1);
498 assert_eq!(handle.revoke_state(), RevokeState::Active);
499 assert_eq!(mgr.revoke_state(1), Some(RevokeState::Active));
500 assert!(!handle.is_revoke_terminal());
501 }
502
503 #[test]
504 fn handle_observes_cooperative_revoke_path() {
505 let mut mgr = RenewalManager::new();
506 let handle = mgr.register_with_created_at_handle(1, 1000, 1100, RenewalPolicy::default());
507
508 assert_eq!(
509 mgr.transition_revoke_state(1, RevokeState::RevokeWarning),
510 Ok(RevokeState::RevokeWarning)
511 );
512 assert_eq!(handle.revoke_state(), RevokeState::RevokeWarning);
513
514 assert_eq!(
515 mgr.transition_revoke_state(1, RevokeState::GraceRunning),
516 Ok(RevokeState::GraceRunning)
517 );
518 assert_eq!(handle.revoke_state(), RevokeState::GraceRunning);
519
520 assert_eq!(
521 mgr.transition_revoke_state(1, RevokeState::CheckpointReported),
522 Ok(RevokeState::CheckpointReported)
523 );
524 assert_eq!(handle.revoke_state(), RevokeState::CheckpointReported);
525
526 assert_eq!(
527 mgr.transition_revoke_state(1, RevokeState::Torndown),
528 Ok(RevokeState::Torndown)
529 );
530 assert_eq!(handle.revoke_state(), RevokeState::Torndown);
531 assert!(handle.is_revoke_terminal());
532 }
533
534 #[test]
535 fn handle_observes_forced_teardown_path() {
536 let mut mgr = RenewalManager::new();
537 let handle = mgr.register_with_created_at_handle(1, 1000, 1100, RenewalPolicy::default());
538
539 assert_eq!(
540 mgr.transition_revoke_state(1, RevokeState::ForcedTeardown),
541 Ok(RevokeState::ForcedTeardown)
542 );
543 assert_eq!(handle.revoke_state(), RevokeState::ForcedTeardown);
544
545 assert_eq!(
546 mgr.transition_revoke_state(1, RevokeState::Torndown),
547 Ok(RevokeState::Torndown)
548 );
549 assert_eq!(handle.revoke_state(), RevokeState::Torndown);
550 }
551
552 #[test]
553 fn handle_observes_expiry_when_active_lease_ticks_past_ttl() {
554 let mut mgr = RenewalManager::new();
555 let handle = mgr.register_with_created_at_handle(1, 1000, 1100, RenewalPolicy::default());
556
557 let s = mgr.tick(1100);
558
559 assert_eq!(s.skipped, 1);
560 assert_eq!(handle.revoke_state(), RevokeState::Expired);
561 assert!(handle.is_revoke_terminal());
562 }
563
564 #[test]
565 fn transition_rejects_illegal_direct_path_without_mutating() {
566 let mut mgr = RenewalManager::new();
567 let handle = mgr.register_with_created_at_handle(1, 1000, 1100, RenewalPolicy::default());
568
569 let err = mgr
570 .transition_revoke_state(1, RevokeState::GraceRunning)
571 .unwrap_err();
572
573 assert_eq!(
574 err,
575 LeaseRevokeTransitionError::IllegalTransition {
576 lease_id: 1,
577 from: RevokeState::Active,
578 to: RevokeState::GraceRunning,
579 }
580 );
581 assert_eq!(handle.revoke_state(), RevokeState::Active);
582 }
583
584 #[test]
585 fn transition_unknown_lease_returns_typed_error() {
586 let mgr = RenewalManager::new();
587
588 assert_eq!(
589 mgr.transition_revoke_state(404, RevokeState::RevokeWarning),
590 Err(LeaseRevokeTransitionError::LeaseNotRegistered { lease_id: 404 })
591 );
592 }
593
594 #[test]
595 fn tick_with_failure_triggers_backoff() {
596 let mut mgr = RenewalManager::new();
597 let policy = RenewalPolicy {
598 renew_at_fraction: 0.75,
599 jitter_fraction: 0.0,
600 min_renew_secs: 100,
601 max_backoff_secs: 5,
602 };
603 mgr.register_with_created_at(1, 1000, 1100, policy);
604
605 let s = mgr.tick_with(1075, |_, _| Err(()));
607 assert_eq!(s.failed, 1);
608 assert_eq!(s.renewed, 0);
609
610 let s = mgr.tick_with(1075, |_, _| Ok(100));
612 assert_eq!(s.skipped, 1);
613 assert_eq!(s.renewed, 0);
614
615 let s = mgr.tick_with(1076, |_, _| Ok(100));
617 assert_eq!(s.renewed, 1);
618 }
619
620 #[test]
621 fn multiple_leases() {
622 let mut mgr = RenewalManager::new();
623 let policy = RenewalPolicy {
624 renew_at_fraction: 0.50,
625 jitter_fraction: 0.0,
626 min_renew_secs: 100,
627 max_backoff_secs: 5,
628 };
629
630 mgr.register_with_created_at(1, 1000, 1100, policy);
631 mgr.register_with_created_at(2, 1000, 1200, policy);
632 mgr.register_with_created_at(3, 1000, 1300, policy);
633
634 let s = mgr.tick(1050);
636 assert_eq!(s.renewed, 1);
637 assert_eq!(s.skipped, 2);
638
639 let s = mgr.tick(1100);
642 assert!(s.renewed >= 1);
643 }
644
645 #[test]
646 fn unregister_removes_lease() {
647 let mut mgr = RenewalManager::new();
648 let policy = RenewalPolicy::default();
649 mgr.register(1, 1100, policy);
650 mgr.register(2, 1200, policy);
651 assert_eq!(mgr.len(), 2);
652
653 mgr.unregister(1);
654 assert_eq!(mgr.len(), 1);
655 assert!(mgr.expires_at(1).is_none());
656 assert!(mgr.expires_at(2).is_some());
657 }
658
659 #[test]
660 fn is_near_expiry_predicate() {
661 let mut mgr = RenewalManager::new();
662 let policy = RenewalPolicy {
663 renew_at_fraction: 0.75,
664 jitter_fraction: 0.0,
665 min_renew_secs: 100,
666 max_backoff_secs: 5,
667 };
668 mgr.register_with_created_at(1, 1000, 1100, policy);
669
670 assert!(!mgr.is_near_expiry(1, 1050));
672
673 assert!(mgr.is_near_expiry(1, 1091));
675
676 assert!(mgr.is_near_expiry(1, 1100));
678 }
679
680 #[test]
681 fn near_expiry_in_summary() {
682 let mut mgr = RenewalManager::new();
683 let policy = RenewalPolicy {
684 renew_at_fraction: 0.75,
685 jitter_fraction: 0.0,
686 min_renew_secs: 100,
687 max_backoff_secs: 5,
688 };
689 mgr.register_with_created_at(1, 1000, 1100, policy);
690
691 let s = mgr.tick(1091);
692 assert!(s.near_expiry.contains(&1));
693 }
694
695 #[test]
696 fn re_register_replaces_entry() {
697 let mut mgr = RenewalManager::new();
698 let policy = RenewalPolicy::default();
699 mgr.register(1, 1100, policy);
700 assert_eq!(mgr.expires_at(1), Some(1100));
701
702 mgr.register(1, 2200, policy);
703 assert_eq!(mgr.len(), 1);
704 assert_eq!(mgr.expires_at(1), Some(2200));
705 }
706
707 #[test]
708 fn unknown_lease_is_not_near_expiry() {
709 let mgr = RenewalManager::new();
710 assert!(!mgr.is_near_expiry(999, 5000));
711 }
712
713 #[test]
714 fn renewal_extends_expiry() {
715 let mut mgr = RenewalManager::new();
716 let policy = RenewalPolicy {
717 renew_at_fraction: 0.75,
718 jitter_fraction: 0.0,
719 min_renew_secs: 100,
720 max_backoff_secs: 5,
721 };
722 mgr.register_with_created_at(1, 1000, 1100, policy);
723
724 let old_exp = mgr.expires_at(1).unwrap();
725 assert_eq!(old_exp, 1100);
726
727 mgr.tick(1080);
729 let new_exp = mgr.expires_at(1).unwrap();
730 assert_eq!(new_exp, 1180);
732 }
733
734 #[test]
735 fn on_revoked_callback_fires() {
736 use alloc::sync::Arc;
737 use core::sync::atomic::{AtomicBool, Ordering};
738
739 let mut mgr = RenewalManager::new();
740 let handle = mgr.register_handle(42, 1100, RenewalPolicy::default());
741 let fired = Arc::new(AtomicBool::new(false));
742 let fired_clone = Arc::clone(&fired);
743 mgr.on_revoked(move |_lease_id, _reason| {
744 fired_clone.store(true, Ordering::SeqCst);
745 });
746
747 mgr.notify_revoked(42, 1);
748 assert!(fired.load(Ordering::SeqCst));
749 assert_eq!(handle.revoke_state(), RevokeState::RevokeWarning);
750 }
751
752 #[test]
753 fn multiple_revocation_callbacks_fire() {
754 use alloc::sync::Arc;
755 use core::sync::atomic::{AtomicBool, Ordering};
756
757 let mut mgr = RenewalManager::new();
758
759 let fired_a = Arc::new(AtomicBool::new(false));
760 let fired_b = Arc::new(AtomicBool::new(false));
761
762 let a = Arc::clone(&fired_a);
763 mgr.on_revoked(move |_, _| {
764 a.store(true, Ordering::SeqCst);
765 });
766
767 let b = Arc::clone(&fired_b);
768 mgr.on_revoked(move |_, _| {
769 b.store(true, Ordering::SeqCst);
770 });
771
772 mgr.notify_revoked(99, 2);
773 assert!(fired_a.load(Ordering::SeqCst));
774 assert!(fired_b.load(Ordering::SeqCst));
775 }
776
777 #[test]
778 fn notify_revoked_with_no_callbacks_is_noop() {
779 let mgr = RenewalManager::new();
780 mgr.notify_revoked(7, 0);
782 }
783
784 #[test]
785 fn revocation_callback_receives_correct_args() {
786 use alloc::sync::Arc;
787 use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
788
789 let mut mgr = RenewalManager::new();
790 let count = Arc::new(AtomicUsize::new(0));
791 let first = Arc::new(AtomicU64::new(0));
792 let second = Arc::new(AtomicU64::new(0));
793 let count_clone = Arc::clone(&count);
794 let first_clone = Arc::clone(&first);
795 let second_clone = Arc::clone(&second);
796 mgr.on_revoked(move |lease_id, reason| {
797 let packed = ((lease_id as u64) << 8) | u64::from(reason);
798 match count_clone.fetch_add(1, Ordering::SeqCst) {
799 0 => first_clone.store(packed, Ordering::SeqCst),
800 1 => second_clone.store(packed, Ordering::SeqCst),
801 _ => {}
802 }
803 });
804
805 mgr.notify_revoked(42, 1);
806 mgr.notify_revoked(1000, 255);
807
808 assert_eq!(count.load(Ordering::SeqCst), 2);
809 assert_eq!(first.load(Ordering::SeqCst), (42 << 8) | 1);
810 assert_eq!(second.load(Ordering::SeqCst), (1000 << 8) | 255);
811 }
812
813 #[test]
814 fn backoff_escalation_on_repeated_failure() {
815 let mut mgr = RenewalManager::new();
816 let policy = RenewalPolicy {
817 renew_at_fraction: 0.75,
818 jitter_fraction: 0.0,
819 min_renew_secs: 100,
820 max_backoff_secs: 5,
821 };
822 mgr.register_with_created_at(1, 1000, 1100, policy);
823
824 let s = mgr.tick_with(1075, |_, _| Err(()));
826 assert_eq!(s.failed, 1);
827
828 let s = mgr.tick_with(1076, |_, _| Err(()));
830 assert_eq!(s.failed, 1);
831
832 let s = mgr.tick_with(1077, |_, _| Ok(100));
834 assert_eq!(s.skipped, 1);
835
836 let s = mgr.tick_with(1078, |_, _| Ok(100));
838 assert_eq!(s.renewed, 1);
839 }
840}