timed_map/
map.rs

1use super::*;
2
3macro_rules! cfg_std_feature {
4    ($($item:item)*) => {
5        $(
6            #[cfg(feature = "std")]
7            $item
8        )*
9    };
10}
11
12macro_rules! cfg_not_std_feature {
13    ($($item:item)*) => {
14        $(
15            #[cfg(not(feature = "std"))]
16            $item
17        )*
18    };
19}
20
21cfg_not_std_feature! {
22    /// Generic trait for `no_std` keys that is gated by the `std` feature
23    /// and handled at compile time.
24    pub trait GenericKey: Clone + Eq + Ord {}
25    impl<T: Clone + Eq + Ord> GenericKey for T {}
26}
27
28cfg_std_feature! {
29    /// Generic trait for `std` keys that is gated by the `std` feature
30    /// and handled at compile time.
31    pub trait GenericKey: Clone + Eq + Ord + Hash {}
32    impl<T: Clone + Eq + Ord + Hash> GenericKey for T {}
33}
34
35/// Wraps different map implementations and provides a single interface to access them.
36#[allow(clippy::enum_variant_names)]
37#[derive(Debug)]
38enum GenericMap<K, V> {
39    BTreeMap(BTreeMap<K, V>),
40    #[cfg(feature = "std")]
41    HashMap(HashMap<K, V>),
42    #[cfg(all(feature = "std", feature = "rustc-hash"))]
43    FxHashMap(FxHashMap<K, V>),
44}
45
46impl<K, V> Default for GenericMap<K, V> {
47    fn default() -> Self {
48        Self::BTreeMap(BTreeMap::default())
49    }
50}
51
52impl<K, V> GenericMap<K, V>
53where
54    K: GenericKey,
55{
56    #[inline(always)]
57    fn get(&self, k: &K) -> Option<&V> {
58        match self {
59            Self::BTreeMap(inner) => inner.get(k),
60            #[cfg(feature = "std")]
61            Self::HashMap(inner) => inner.get(k),
62            #[cfg(all(feature = "std", feature = "rustc-hash"))]
63            Self::FxHashMap(inner) => inner.get(k),
64        }
65    }
66
67    #[inline(always)]
68    fn get_mut(&mut self, k: &K) -> Option<&mut V> {
69        match self {
70            Self::BTreeMap(inner) => inner.get_mut(k),
71            #[cfg(feature = "std")]
72            Self::HashMap(inner) => inner.get_mut(k),
73            #[cfg(all(feature = "std", feature = "rustc-hash"))]
74            Self::FxHashMap(inner) => inner.get_mut(k),
75        }
76    }
77
78    #[inline(always)]
79    fn len(&self) -> usize {
80        match self {
81            Self::BTreeMap(inner) => inner.len(),
82            #[cfg(feature = "std")]
83            Self::HashMap(inner) => inner.len(),
84            #[cfg(all(feature = "std", feature = "rustc-hash"))]
85            Self::FxHashMap(inner) => inner.len(),
86        }
87    }
88
89    #[inline(always)]
90    fn keys(&self) -> Vec<K> {
91        match self {
92            Self::BTreeMap(inner) => inner.keys().cloned().collect(),
93            #[cfg(feature = "std")]
94            Self::HashMap(inner) => inner.keys().cloned().collect(),
95            #[cfg(all(feature = "std", feature = "rustc-hash"))]
96            Self::FxHashMap(inner) => inner.keys().cloned().collect(),
97        }
98    }
99
100    #[inline(always)]
101    fn is_empty(&self) -> bool {
102        match self {
103            Self::BTreeMap(inner) => inner.is_empty(),
104            #[cfg(feature = "std")]
105            Self::HashMap(inner) => inner.is_empty(),
106            #[cfg(all(feature = "std", feature = "rustc-hash"))]
107            Self::FxHashMap(inner) => inner.is_empty(),
108        }
109    }
110
111    #[inline(always)]
112    fn insert(&mut self, k: K, v: V) -> Option<V> {
113        match self {
114            Self::BTreeMap(inner) => inner.insert(k, v),
115            #[cfg(feature = "std")]
116            Self::HashMap(inner) => inner.insert(k, v),
117            #[cfg(all(feature = "std", feature = "rustc-hash"))]
118            Self::FxHashMap(inner) => inner.insert(k, v),
119        }
120    }
121
122    #[inline(always)]
123    fn clear(&mut self) {
124        match self {
125            Self::BTreeMap(inner) => inner.clear(),
126            #[cfg(feature = "std")]
127            Self::HashMap(inner) => inner.clear(),
128            #[cfg(all(feature = "std", feature = "rustc-hash"))]
129            Self::FxHashMap(inner) => inner.clear(),
130        }
131    }
132
133    #[inline(always)]
134    fn remove(&mut self, k: &K) -> Option<V> {
135        match self {
136            Self::BTreeMap(inner) => inner.remove(k),
137            #[cfg(feature = "std")]
138            Self::HashMap(inner) => inner.remove(k),
139            #[cfg(all(feature = "std", feature = "rustc-hash"))]
140            Self::FxHashMap(inner) => inner.remove(k),
141        }
142    }
143}
144
145/// Specifies the inner map implementation for `TimedMap`.
146#[cfg(feature = "std")]
147#[allow(clippy::enum_variant_names)]
148pub enum MapKind {
149    BTreeMap,
150    HashMap,
151    #[cfg(feature = "rustc-hash")]
152    FxHashMap,
153}
154
155/// Associates keys of type `K` with values of type `V`. Each entry may optionally expire after a
156/// specified duration.
157///
158/// Mutable functions automatically clears expired entries when called.
159///
160/// If no expiration is set, the entry remains constant.
161#[derive(Debug)]
162pub struct TimedMap<K, V, #[cfg(feature = "std")] C = StdClock, #[cfg(not(feature = "std"))] C> {
163    clock: C,
164
165    map: GenericMap<K, ExpirableEntry<V>>,
166    expiries: BTreeMap<u64, BTreeSet<K>>,
167
168    expiration_tick: u16,
169    expiration_tick_cap: u16,
170}
171
172impl<K, V, C> Default for TimedMap<K, V, C>
173where
174    C: Default,
175{
176    fn default() -> Self {
177        Self {
178            clock: Default::default(),
179
180            map: GenericMap::default(),
181            expiries: BTreeMap::default(),
182
183            expiration_tick: 0,
184            expiration_tick_cap: 1,
185        }
186    }
187}
188
189#[cfg(feature = "std")]
190impl<K, V> TimedMap<K, V, StdClock>
191where
192    K: GenericKey,
193{
194    /// Creates an empty map.
195    pub fn new() -> Self {
196        Self::default()
197    }
198
199    /// Creates an empty map based on the chosen map implementation specified by `MapKind`.
200    pub fn new_with_map_kind(map_kind: MapKind) -> Self {
201        let map = match map_kind {
202            MapKind::BTreeMap => GenericMap::<K, ExpirableEntry<V>>::BTreeMap(BTreeMap::default()),
203            MapKind::HashMap => GenericMap::HashMap(HashMap::default()),
204            #[cfg(feature = "rustc-hash")]
205            MapKind::FxHashMap => GenericMap::FxHashMap(FxHashMap::default()),
206        };
207
208        Self {
209            map,
210
211            clock: StdClock::default(),
212            expiries: BTreeMap::default(),
213
214            expiration_tick: 0,
215            expiration_tick_cap: 1,
216        }
217    }
218}
219
220impl<K, V, C> TimedMap<K, V, C>
221where
222    C: Clock,
223    K: GenericKey,
224{
225    /// Creates an empty `TimedMap`.
226    ///
227    /// Uses the provided `clock` to handle expiration times.
228    #[cfg(not(feature = "std"))]
229    pub fn new(clock: C) -> Self {
230        Self {
231            clock,
232            map: GenericMap::default(),
233            expiries: BTreeMap::default(),
234            expiration_tick: 0,
235            expiration_tick_cap: 1,
236        }
237    }
238
239    /// Configures `expiration_tick_cap`, which sets how often `TimedMap::drop_expired_entries`
240    /// is automatically called. The default value is 1.
241    ///
242    /// On each insert (excluding `unchecked` ones), an internal counter `expiration_tick` is incremented.
243    /// When `expiration_tick` meets or exceeds `expiration_tick_cap`, `TimedMap::drop_expired_entries` is
244    /// triggered to remove expired entries.
245    ///
246    /// Use this to control cleanup frequency and optimize performance. For example, if your workload
247    /// involves about 100 inserts within couple seconds, setting `expiration_tick_cap` to 100 can improve
248    /// the performance significantly.
249    #[inline(always)]
250    pub fn expiration_tick_cap(mut self, expiration_tick_cap: u16) -> Self {
251        self.expiration_tick_cap = expiration_tick_cap;
252        self
253    }
254
255    /// Returns the associated value if present and not expired.
256    ///
257    /// To retrieve the value without checking expiration, use `TimedMap::get_unchecked`.
258    pub fn get(&self, k: &K) -> Option<&V> {
259        self.map
260            .get(k)
261            .filter(|v| !v.is_expired(self.clock.elapsed_seconds_since_creation()))
262            .map(|v| v.value())
263    }
264
265    /// Returns a mutable reference to the value corresponding to the key.
266    ///
267    /// To retrieve the value without checking expiration, use `TimedMap::get_mut_unchecked`.
268    pub fn get_mut(&mut self, k: &K) -> Option<&mut V> {
269        self.map
270            .get_mut(k)
271            .filter(|v| !v.is_expired(self.clock.elapsed_seconds_since_creation()))
272            .map(|v| v.value_mut())
273    }
274
275    /// Returns the associated value if present, regardless of whether it is expired.
276    ///
277    /// If you only want non-expired entries, use `TimedMap::get` instead.
278    #[inline(always)]
279    pub fn get_unchecked(&self, k: &K) -> Option<&V> {
280        self.map.get(k).map(|v| v.value())
281    }
282
283    /// Returns a mutable reference to the associated value if present, regardless of
284    /// whether it is expired.
285    ///
286    /// If you only want non-expired entries, use `TimedMap::get_mut` instead.
287    #[inline(always)]
288    pub fn get_mut_unchecked(&mut self, k: &K) -> Option<&mut V> {
289        self.map.get_mut(k).map(|v| v.value_mut())
290    }
291
292    /// Returns the associated value's `Duration` if present and not expired.
293    ///
294    /// Returns `None` if the entry does not exist or is constant.
295    pub fn get_remaining_duration(&self, k: &K) -> Option<Duration> {
296        match self.map.get(k) {
297            Some(v) => {
298                let now = self.clock.elapsed_seconds_since_creation();
299                if v.is_expired(now) {
300                    return None;
301                }
302
303                v.remaining_duration(now)
304            }
305            None => None,
306        }
307    }
308
309    /// Returns the number of unexpired elements in the map.
310    ///
311    /// See `TimedMap::len_expired` and `TimedMap::len_unchecked` for other usages.
312    #[inline(always)]
313    pub fn len(&self) -> usize {
314        self.map.len() - self.len_expired()
315    }
316
317    /// Returns the number of expired elements in the map.
318    ///
319    /// See `TimedMap::len` and `TimedMap::len_unchecked` for other usages.
320    #[inline(always)]
321    pub fn len_expired(&self) -> usize {
322        let now = self.clock.elapsed_seconds_since_creation();
323        self.expiries
324            .iter()
325            .filter_map(
326                |(exp, keys)| {
327                    if exp <= &now {
328                        Some(keys.len())
329                    } else {
330                        None
331                    }
332                },
333            )
334            .sum()
335    }
336
337    /// Returns the total number of elements (including expired ones) in the map.
338    ///
339    /// See `TimedMap::len` and `TimedMap::len_expired` for other usages.
340    #[inline(always)]
341    pub fn len_unchecked(&self) -> usize {
342        self.map.len()
343    }
344
345    /// Returns keys of the map
346    #[inline(always)]
347    pub fn keys(&self) -> Vec<K> {
348        self.map.keys()
349    }
350
351    /// Returns true if the map contains no elements.
352    #[inline(always)]
353    pub fn is_empty(&self) -> bool {
354        self.map.is_empty()
355    }
356
357    /// Inserts a key-value pair with an expiration duration. If duration is `None`,
358    /// entry will be stored in a non-expirable way.
359    ///
360    /// If a value already exists for the given key, it will be updated and then
361    /// the old one will be returned.
362    #[inline(always)]
363    fn insert(&mut self, k: K, v: V, expires_at: Option<u64>) -> Option<V> {
364        let entry = ExpirableEntry::new(v, expires_at);
365        match self.map.insert(k.clone(), entry) {
366            Some(old) => {
367                // Remove the old expiry record
368                if let EntryStatus::ExpiresAtSeconds(e) = old.status() {
369                    self.drop_key_from_expiry(e, &k)
370                }
371
372                Some(old.owned_value())
373            }
374            None => None,
375        }
376    }
377
378    /// Inserts a key-value pair with an expiration duration, and then drops the
379    /// expired entries.
380    ///
381    /// If a value already exists for the given key, it will be updated and then
382    /// the old one will be returned.
383    ///
384    /// If you don't want to the check expired entries, consider using `TimedMap::insert_expirable_unchecked`
385    /// instead.
386    pub fn insert_expirable(&mut self, k: K, v: V, duration: Duration) -> Option<V> {
387        self.expiration_tick += 1;
388
389        let now = self.clock.elapsed_seconds_since_creation();
390        if self.expiration_tick >= self.expiration_tick_cap {
391            self.drop_expired_entries_inner(now);
392            self.expiration_tick = 0;
393        }
394
395        let expires_at = now + duration.as_secs();
396
397        let res = self.insert(k.clone(), v, Some(expires_at));
398
399        self.expiries.entry(expires_at).or_default().insert(k);
400
401        res
402    }
403
404    /// Inserts a key-value pair with an expiration duration, without checking the expired
405    /// entries.
406    ///
407    /// If a value already exists for the given key, it will be updated and then
408    /// the old one will be returned.
409    ///
410    /// If you want to check the expired entries, consider using `TimedMap::insert_expirable`
411    /// instead.
412    pub fn insert_expirable_unchecked(&mut self, k: K, v: V, duration: Duration) -> Option<V> {
413        let now = self.clock.elapsed_seconds_since_creation();
414        let expires_at = now + duration.as_secs();
415
416        let res = self.insert(k.clone(), v, Some(expires_at));
417
418        self.expiries.entry(expires_at).or_default().insert(k);
419
420        res
421    }
422
423    /// Inserts a key-value pair with that doesn't expire, and then drops the
424    /// expired entries.
425    ///
426    /// If a value already exists for the given key, it will be updated and then
427    /// the old one will be returned.
428    ///
429    /// If you don't want to check the expired entries, consider using `TimedMap::insert_constant_unchecked`
430    /// instead.
431    pub fn insert_constant(&mut self, k: K, v: V) -> Option<V> {
432        self.expiration_tick += 1;
433
434        let now = self.clock.elapsed_seconds_since_creation();
435        if self.expiration_tick >= self.expiration_tick_cap {
436            self.drop_expired_entries_inner(now);
437            self.expiration_tick = 0;
438        }
439
440        self.insert(k, v, None)
441    }
442
443    /// Inserts a key-value pair with that doesn't expire without checking the expired
444    /// entries.
445    ///
446    /// If a value already exists for the given key, it will be updated and then
447    /// the old one will be returned.
448    ///
449    /// If you want to check the expired entries, consider using `TimedMap::insert_constant`
450    /// instead.
451    pub fn insert_constant_unchecked(&mut self, k: K, v: V) -> Option<V> {
452        self.expiration_tick += 1;
453        self.insert(k, v, None)
454    }
455
456    /// Removes a key-value pair from the map and returns the associated value if present
457    /// and not expired.
458    ///
459    /// If you want to retrieve the entry after removal even if it is expired, consider using
460    /// `TimedMap::remove_unchecked`.
461    #[inline(always)]
462    pub fn remove(&mut self, k: &K) -> Option<V> {
463        self.map
464            .remove(k)
465            .filter(|v| {
466                if let EntryStatus::ExpiresAtSeconds(expires_at_seconds) = v.status() {
467                    self.drop_key_from_expiry(expires_at_seconds, k);
468                }
469
470                !v.is_expired(self.clock.elapsed_seconds_since_creation())
471            })
472            .map(|v| v.owned_value())
473    }
474
475    /// Removes a key-value pair from the map and returns the associated value if present,
476    /// regardless of expiration status.
477    ///
478    /// If you only want the entry when it is not expired, consider using `TimedMap::remove`.
479    #[inline(always)]
480    pub fn remove_unchecked(&mut self, k: &K) -> Option<V> {
481        self.map
482            .remove(k)
483            .filter(|v| {
484                if let EntryStatus::ExpiresAtSeconds(expires_at_seconds) = v.status() {
485                    self.drop_key_from_expiry(expires_at_seconds, k);
486                }
487
488                true
489            })
490            .map(|v| v.owned_value())
491    }
492
493    /// Clears the map, removing all elements.
494    #[inline(always)]
495    pub fn clear(&mut self) {
496        self.map.clear()
497    }
498
499    /// Updates the expiration status of an entry and returns the old status.
500    ///
501    /// If the entry does not exist, returns Err.
502    /// If the entry's old status is `EntryStatus::Constant`, returns None.
503    pub fn update_expiration_status(
504        &mut self,
505        key: K,
506        duration: Duration,
507    ) -> Result<Option<EntryStatus>, &'static str> {
508        match self.map.get_mut(&key) {
509            Some(entry) => {
510                let old_status = *entry.status();
511                let now = self.clock.elapsed_seconds_since_creation();
512                let expires_at = now + duration.as_secs();
513
514                entry.update_status(EntryStatus::ExpiresAtSeconds(expires_at));
515
516                if let EntryStatus::ExpiresAtSeconds(t) = &old_status {
517                    self.drop_key_from_expiry(t, &key);
518                }
519                self.expiries
520                    .entry(expires_at)
521                    .or_default()
522                    .insert(key.clone());
523
524                Ok(Some(old_status))
525            }
526            None => Err("entry not found"),
527        }
528    }
529
530    /// Clears expired entries from the map.
531    ///
532    /// Call this function when using `*_unchecked` inserts, as these do not
533    /// automatically clear expired entries.
534    #[inline(always)]
535    pub fn drop_expired_entries(&mut self) {
536        let now = self.clock.elapsed_seconds_since_creation();
537        self.drop_expired_entries_inner(now);
538    }
539
540    fn drop_expired_entries_inner(&mut self, now: u64) {
541        // Iterates through `expiries` in order and drops expired ones.
542        while let Some((exp, keys)) = self.expiries.pop_first() {
543            // It's safe to do early-break here as keys are sorted by expiration.
544            if exp > now {
545                self.expiries.insert(exp, keys);
546                break;
547            }
548
549            for key in keys {
550                self.map.remove(&key);
551            }
552        }
553    }
554
555    fn drop_key_from_expiry(&mut self, expiry_key: &u64, map_key: &K) {
556        if let Some(list) = self.expiries.get_mut(expiry_key) {
557            list.remove(map_key);
558
559            if list.is_empty() {
560                self.expiries.remove(expiry_key);
561            }
562        }
563    }
564}
565
566#[cfg(test)]
567#[cfg(not(feature = "std"))]
568mod tests {
569    use super::*;
570
571    struct MockClock {
572        current_time: u64,
573    }
574
575    impl Clock for MockClock {
576        fn elapsed_seconds_since_creation(&self) -> u64 {
577            self.current_time
578        }
579    }
580
581    #[test]
582    fn nostd_insert_and_get_constant_entry() {
583        let clock = MockClock { current_time: 1000 };
584        let mut map = TimedMap::new(clock);
585
586        map.insert_constant(1, "constant value");
587
588        assert_eq!(map.get(&1), Some(&"constant value"));
589        assert_eq!(map.get_remaining_duration(&1), None);
590    }
591
592    #[test]
593    fn nostd_insert_and_get_expirable_entry() {
594        let clock = MockClock { current_time: 1000 };
595        let mut map = TimedMap::new(clock);
596        let duration = Duration::from_secs(60);
597
598        map.insert_expirable(1, "expirable value", duration);
599
600        assert_eq!(map.get(&1), Some(&"expirable value"));
601        assert_eq!(map.get_remaining_duration(&1), Some(duration));
602    }
603
604    #[test]
605    fn nostd_expired_entry() {
606        let clock = MockClock { current_time: 1000 };
607        let mut map = TimedMap::new(clock);
608        let duration = Duration::from_secs(60);
609
610        // Insert entry that expires in 60 seconds
611        map.insert_expirable(1, "expirable value", duration);
612
613        // Simulate time passage beyond expiration
614        let clock = MockClock { current_time: 1070 };
615        map.clock = clock;
616
617        // The entry should be considered expired
618        assert_eq!(map.get(&1), None);
619        assert_eq!(map.get_remaining_duration(&1), None);
620    }
621
622    #[test]
623    fn nostd_remove_entry() {
624        let clock = MockClock { current_time: 1000 };
625        let mut map = TimedMap::new(clock);
626
627        map.insert_constant(1, "constant value");
628
629        assert_eq!(map.remove(&1), Some("constant value"));
630        assert_eq!(map.get(&1), None);
631    }
632
633    #[test]
634    fn nostd_drop_expired_entries() {
635        let clock = MockClock { current_time: 1000 };
636        let mut map = TimedMap::new(clock);
637
638        // Insert one constant and 2 expirable entries
639        map.insert_expirable(1, "expirable value1", Duration::from_secs(50));
640        map.insert_expirable(2, "expirable value2", Duration::from_secs(70));
641        map.insert_constant(3, "constant value");
642
643        // Simulate time passage beyond the expiration of the first entry
644        let clock = MockClock { current_time: 1055 };
645        map.clock = clock;
646
647        // Entry 1 should be removed and entry 2 and 3 should still exist
648        assert_eq!(map.get(&1), None);
649        assert_eq!(map.get(&2), Some(&"expirable value2"));
650        assert_eq!(map.get(&3), Some(&"constant value"));
651
652        // Simulate time passage again to expire second expirable entry
653        let clock = MockClock { current_time: 1071 };
654        map.clock = clock;
655
656        assert_eq!(map.get(&1), None);
657        assert_eq!(map.get(&2), None);
658        assert_eq!(map.get(&3), Some(&"constant value"));
659    }
660
661    #[test]
662    fn nostd_update_existing_entry() {
663        let clock = MockClock { current_time: 1000 };
664        let mut map = TimedMap::new(clock);
665
666        map.insert_constant(1, "initial value");
667        assert_eq!(map.get(&1), Some(&"initial value"));
668
669        // Update the value of the existing key and make it expirable
670        map.insert_expirable(1, "updated value", Duration::from_secs(15));
671        assert_eq!(map.get(&1), Some(&"updated value"));
672
673        // Simulate time passage and expire the updated entry
674        let clock = MockClock { current_time: 1016 };
675        map.clock = clock;
676
677        assert_eq!(map.get(&1), None);
678    }
679
680    #[test]
681    fn nostd_update_expirable_entry_status() {
682        let clock = MockClock { current_time: 1000 };
683        let mut map = TimedMap::new(clock);
684
685        map.insert_constant(1, "initial value");
686        assert_eq!(map.get(&1), Some(&"initial value"));
687
688        // Update the value of the existing key and make it expirable
689        map.update_expiration_status(1, Duration::from_secs(16))
690            .expect("entry update shouldn't fail");
691        assert_eq!(map.get(&1), Some(&"initial value"));
692
693        // Simulate time passage and expire the updated entry
694        let clock = MockClock { current_time: 1017 };
695        map.clock = clock;
696        assert_eq!(map.get(&1), None);
697    }
698
699    #[test]
700    fn nostd_update_expirable_entry_status_with_previou_time() {
701        let clock = MockClock { current_time: 1000 };
702        let mut map = TimedMap::new(clock);
703
704        // Insert map entry followed by immediately updating expiration time
705        map.insert_expirable(1, "expirable value", Duration::from_secs(15));
706        map.update_expiration_status(1, Duration::from_secs(15))
707            .expect("entry update shouldn't fail");
708
709        // We should still have our entry.
710        assert_eq!(map.get(&1), Some(&"expirable value"));
711        assert!(map.expiries.contains_key(&1015));
712    }
713}
714
715#[cfg(feature = "std")]
716#[cfg(test)]
717mod std_tests {
718    use core::ops::Add;
719
720    use super::*;
721
722    #[test]
723    fn std_expirable_and_constant_entries() {
724        let mut map = TimedMap::new();
725
726        map.insert_constant(1, "constant value");
727        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
728
729        assert_eq!(map.get(&1), Some(&"constant value"));
730        assert_eq!(map.get(&2), Some(&"expirable value"));
731
732        assert_eq!(map.get_remaining_duration(&1), None);
733        assert!(map.get_remaining_duration(&2).is_some());
734    }
735
736    #[test]
737    fn std_expired_entry_removal() {
738        let mut map = TimedMap::new();
739        let duration = Duration::from_secs(2);
740
741        map.insert_expirable(1, "expirable value", duration);
742
743        // Wait for expiration
744        std::thread::sleep(Duration::from_secs(3));
745
746        // Entry should now be expired
747        assert_eq!(map.get(&1), None);
748        assert_eq!(map.get_remaining_duration(&1), None);
749    }
750
751    #[test]
752    fn std_remove_entry() {
753        let mut map = TimedMap::new();
754
755        map.insert_constant(1, "constant value");
756        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
757
758        assert_eq!(map.remove(&1), Some("constant value"));
759        assert_eq!(map.remove(&2), Some("expirable value"));
760
761        assert_eq!(map.get(&1), None);
762        assert_eq!(map.get(&2), None);
763    }
764
765    #[test]
766    fn std_drop_expired_entries() {
767        let mut map = TimedMap::new();
768
769        map.insert_expirable(1, "expirable value1", Duration::from_secs(2));
770        map.insert_expirable(2, "expirable value2", Duration::from_secs(4));
771
772        // Wait for expiration
773        std::thread::sleep(Duration::from_secs(3));
774
775        // Entry 1 should be removed and entry 2 should still exist
776        assert_eq!(map.get(&1), None);
777        assert_eq!(map.get(&2), Some(&"expirable value2"));
778    }
779
780    #[test]
781    fn std_update_existing_entry() {
782        let mut map = TimedMap::new();
783
784        map.insert_constant(1, "initial value");
785        assert_eq!(map.get(&1), Some(&"initial value"));
786
787        // Update the value of the existing key and make it expirable
788        map.insert_expirable(1, "updated value", Duration::from_secs(1));
789        assert_eq!(map.get(&1), Some(&"updated value"));
790
791        std::thread::sleep(Duration::from_secs(2));
792
793        // Should be expired now
794        assert_eq!(map.get(&1), None);
795    }
796
797    #[test]
798    fn std_insert_constant_and_expirable_combined() {
799        let mut map = TimedMap::new();
800
801        // Insert a constant entry and an expirable entry
802        map.insert_constant(1, "constant value");
803        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
804
805        // Check both entries exist
806        assert_eq!(map.get(&1), Some(&"constant value"));
807        assert_eq!(map.get(&2), Some(&"expirable value"));
808
809        // Simulate passage of time beyond expiration
810        std::thread::sleep(Duration::from_secs(3));
811
812        // Constant entry should still exist, expirable should be expired
813        assert_eq!(map.get(&1), Some(&"constant value"));
814        assert_eq!(map.get(&2), None);
815    }
816
817    #[test]
818    fn std_expirable_entry_still_valid_before_expiration() {
819        let mut map = TimedMap::new();
820
821        // Insert an expirable entry with a duration of 3 seconds
822        map.insert_expirable(1, "expirable value", Duration::from_secs(3));
823
824        // Simulate a short sleep of 2 seconds (still valid)
825        std::thread::sleep(Duration::from_secs(2));
826
827        // The entry should still be valid
828        assert_eq!(map.get(&1), Some(&"expirable value"));
829        assert!(map.get_remaining_duration(&1).unwrap().as_secs() == 1);
830    }
831
832    #[test]
833    fn std_length_functions() {
834        let mut map = TimedMap::new();
835
836        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
837        map.insert_expirable(2, "expirable value", Duration::from_secs(1));
838        map.insert_expirable(3, "expirable value", Duration::from_secs(3));
839        map.insert_expirable(4, "expirable value", Duration::from_secs(3));
840        map.insert_expirable(5, "expirable value", Duration::from_secs(3));
841        map.insert_expirable(6, "expirable value", Duration::from_secs(3));
842
843        std::thread::sleep(Duration::from_secs(2).add(Duration::from_millis(1)));
844
845        assert_eq!(map.len(), 4);
846        assert_eq!(map.len_expired(), 2);
847        assert_eq!(map.len_unchecked(), 6);
848    }
849
850    #[test]
851    fn std_update_expirable_entry() {
852        let mut map = TimedMap::new();
853
854        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
855        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
856
857        std::thread::sleep(Duration::from_secs(2));
858
859        assert!(!map.expiries.contains_key(&1));
860        assert!(map.expiries.contains_key(&5));
861        assert_eq!(map.get(&1), Some(&"expirable value"));
862    }
863
864    #[test]
865    fn std_update_expirable_entry_status() {
866        let mut map = TimedMap::new();
867
868        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
869        map.update_expiration_status(1, Duration::from_secs(5))
870            .expect("entry update shouldn't fail");
871
872        std::thread::sleep(Duration::from_secs(3));
873        assert!(!map.expiries.contains_key(&1));
874        assert!(map.expiries.contains_key(&5));
875        assert_eq!(map.get(&1), Some(&"expirable value"));
876    }
877
878    #[test]
879    fn std_update_expirable_entry_status_with_previou_time() {
880        let mut map = TimedMap::new();
881
882        // Insert map entry followed by immediately updating expiration time
883        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
884        map.update_expiration_status(1, Duration::from_secs(5))
885            .expect("entry update shouldn't fail");
886
887        // We should still have our entry.
888        assert_eq!(map.get(&1), Some(&"expirable value"));
889        assert!(map.expiries.contains_key(&5));
890    }
891}