1use std::time::{Duration, Instant};
4
5use crate::config::Config;
6
7#[derive(Debug, Clone)]
13pub struct SlotMeta {
14 pub created_at: Instant,
16 pub last_used: Instant,
18 pub uses: u64,
20 pub generation: u64,
22}
23
24impl SlotMeta {
25 pub(crate) fn new(generation: u64) -> Self {
26 let now = Instant::now();
27 Self {
28 created_at: now,
29 last_used: now,
30 uses: 0,
31 generation,
32 }
33 }
34
35 pub fn age(&self) -> Duration {
37 self.created_at.elapsed()
38 }
39
40 pub fn idle_for(&self) -> Duration {
42 self.last_used.elapsed()
43 }
44}
45
46#[derive(Debug)]
48pub(crate) struct Slot<C> {
49 pub conn: C,
50 pub meta: SlotMeta,
51}
52
53impl<C> Slot<C> {
54 pub(crate) fn new(conn: C, generation: u64) -> Self {
55 Self {
56 conn,
57 meta: SlotMeta::new(generation),
58 }
59 }
60
61 pub(crate) fn is_expired(&self, cfg: &Config, current_generation: u64) -> bool {
66 if self.meta.generation != current_generation {
67 return true;
68 }
69 if let Some(max) = cfg.max_lifetime
70 && self.meta.age() >= max
71 {
72 return true;
73 }
74 if let Some(idle) = cfg.idle_timeout
75 && self.meta.idle_for() >= idle
76 {
77 return true;
78 }
79 if let Some(max_uses) = cfg.max_uses
80 && self.meta.uses >= max_uses
81 {
82 return true;
83 }
84 false
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 fn no_limits() -> Config {
94 Config::new()
95 .max_lifetime(None)
96 .idle_timeout(None)
97 .max_uses(None)
98 }
99
100 #[test]
101 fn a_fresh_meta_starts_unused_at_its_generation() {
102 let m = SlotMeta::new(7);
103 assert_eq!(m.uses, 0);
104 assert_eq!(m.generation, 7);
105 assert_eq!(
106 m.created_at, m.last_used,
107 "a connection counts as just returned"
108 );
109 }
110
111 #[test]
112 fn age_and_idle_for_advance() {
113 let m = SlotMeta::new(0);
114 assert!(m.age() <= m.created_at.elapsed());
116 assert!(m.idle_for() <= m.last_used.elapsed());
117 }
118
119 #[test]
120 fn meta_is_clone_and_debug() {
121 let m = SlotMeta::new(3);
122 let c = m.clone();
123 assert_eq!(c.generation, 3);
124 assert!(format!("{m:?}").contains("generation"));
125 }
126
127 #[test]
128 fn a_new_slot_carries_its_connection_and_generation() {
129 let slot = Slot::new("conn", 4);
130 assert_eq!(slot.conn, "conn");
131 assert_eq!(slot.meta.generation, 4);
132 assert!(format!("{slot:?}").contains("conn"));
133 }
134
135 #[test]
136 fn a_slot_with_no_limits_never_expires() {
137 let slot = Slot::new((), 0);
138 assert!(!slot.is_expired(&no_limits(), 0));
139 }
140
141 #[test]
142 fn a_stale_generation_expires_the_slot() {
143 let slot = Slot::new((), 0);
144 assert!(
145 slot.is_expired(&no_limits(), 1),
146 "invalidate() bumps the generation; older connections must go"
147 );
148 }
149
150 #[test]
153 fn any_generation_mismatch_expires_the_slot() {
154 let slot = Slot::new((), 5);
155 assert!(slot.is_expired(&no_limits(), 4));
156 assert!(!slot.is_expired(&no_limits(), 5));
157 }
158
159 #[test]
160 fn max_lifetime_expires_the_slot() {
161 let slot = Slot::new((), 0);
162 assert!(slot.is_expired(&no_limits().max_lifetime(Duration::ZERO), 0));
164 }
165
166 #[test]
167 fn a_lifetime_that_has_not_elapsed_does_not_expire_the_slot() {
168 let slot = Slot::new((), 0);
169 assert!(!slot.is_expired(&no_limits().max_lifetime(Duration::from_secs(3600)), 0));
170 }
171
172 #[test]
173 fn idle_timeout_expires_the_slot() {
174 let slot = Slot::new((), 0);
175 assert!(slot.is_expired(&no_limits().idle_timeout(Duration::ZERO), 0));
176 }
177
178 #[test]
179 fn an_idle_timeout_that_has_not_elapsed_does_not_expire_the_slot() {
180 let slot = Slot::new((), 0);
181 assert!(!slot.is_expired(&no_limits().idle_timeout(Duration::from_secs(3600)), 0));
182 }
183
184 #[test]
185 fn max_uses_expires_the_slot_once_reached() {
186 let mut slot = Slot::new((), 0);
187 let cfg = no_limits().max_uses(2u64);
188
189 assert!(!slot.is_expired(&cfg, 0), "unused");
190 slot.meta.uses = 1;
191 assert!(!slot.is_expired(&cfg, 0), "one checkout of two");
192 slot.meta.uses = 2;
193 assert!(slot.is_expired(&cfg, 0), "the cap is inclusive");
194 slot.meta.uses = 3;
195 assert!(slot.is_expired(&cfg, 0), "and stays tripped past it");
196 }
197
198 #[test]
201 fn all_limits_set_and_none_tripped() {
202 let cfg = Config::new()
203 .max_lifetime(Duration::from_secs(3600))
204 .idle_timeout(Duration::from_secs(3600))
205 .max_uses(10u64);
206 assert!(!Slot::new((), 0).is_expired(&cfg, 0));
207 }
208}