async-foundation 0.2.1

Foundational async primitives for Rust - timers, networking, and common utilities
Documentation
use mio::Token;
use std::time::Instant;

use crate::net::TokenTimeout;

pub struct Timeouts {
    pub(crate) queue: Vec<TokenTimeout>,
}

impl Timeouts {
    pub fn new() -> Timeouts {
        Timeouts { queue: Vec::new() }
    }

    pub fn add(&mut self, token: Token, instant: Instant) {
        let index = self
            .get_index_by(|item| item.timeout > instant)
            .map(|index| index + 1)
            .unwrap_or(0);
        self.queue.insert(index, TokenTimeout::new(token, instant));
    }

    pub fn remove(&mut self, token: Token) -> Option<TokenTimeout> {
        self.get_token_index(token)
            .map(|index| self.queue.remove(index))
    }

    fn get_index_by<F>(&self, func: F) -> Option<usize>
    where
        F: Fn(&TokenTimeout) -> bool,
    {
        for i in (0..self.queue.len()).rev() {
            let token_timeout = &self.queue[i];
            if func(token_timeout) {
                return Some(i);
            }
        }
        None
    }

    pub fn get_token_index(&self, token: Token) -> Option<usize> {
        self.get_index_by(|item| item.token == token)
    }

    pub fn current(&self) -> Option<&TokenTimeout> {
        self.queue.last()
    }

    pub(crate) fn pop_timeouts(&mut self, instant: Instant) -> Vec<TokenTimeout> {
        if self.queue.len() == 0 {
            return vec![];
        }
        let index = match self.get_index_by(|item| item.timeout > instant) {
            Some(index) => index + 1,
            None => 0,
        };

        let mut result = self.queue.split_off(index);
        result.reverse();
        result
    }

    pub fn is_empty(&self) -> bool {
        self.queue.is_empty()
    }

    pub fn len(&self) -> usize {
        self.queue.len()
    }
}

// cargo test --features net
#[cfg(test)]
mod tests {
    use std::time::Duration;

    use super::*;

    #[test]
    fn test_pop_timeouts() {
        let mut timeouts = Timeouts::new();
        let instant = Instant::now();
        timeouts.add(Token(0), instant + Duration::from_secs(1));
        timeouts.add(Token(1), instant + Duration::from_secs(2));
        timeouts.add(Token(3), instant + Duration::from_secs(3));

        let result = timeouts.pop_timeouts(instant + Duration::from_secs(2));
        assert_eq!(result.len(), 2);
        assert_eq!(result[0].token, Token(0));
        assert_eq!(result[1].token, Token(1));

        let result = timeouts.pop_timeouts(instant + Duration::from_secs(2));
        assert_eq!(result.len(), 0);

        let result = timeouts.pop_timeouts(instant + Duration::from_secs(3));
        assert_eq!(result.len(), 1);
    }

    #[test]
    fn test_timeouts_new() {
        let timeouts = Timeouts::new();
        assert!(timeouts.is_empty());
        assert_eq!(timeouts.len(), 0);
    }

    #[test]
    fn test_timeouts_add_single() {
        let mut timeouts = Timeouts::new();
        let instant = Instant::now();
        let token = Token(42);
        
        timeouts.add(token, instant);
        
        assert!(!timeouts.is_empty());
        assert_eq!(timeouts.len(), 1);
        
        let current = timeouts.current();
        assert!(current.is_some());
        assert_eq!(current.unwrap().token, token);
        assert_eq!(current.unwrap().timeout, instant);
    }

    #[test]
    fn test_timeouts_add_multiple_ordered() {
        let mut timeouts = Timeouts::new();
        let instant = Instant::now();
        
        timeouts.add(Token(1), instant + Duration::from_secs(3));
        timeouts.add(Token(2), instant + Duration::from_secs(1));
        timeouts.add(Token(3), instant + Duration::from_secs(2));
        
        assert_eq!(timeouts.len(), 3);
        
        // Should be ordered by timeout (shortest first)
        let current = timeouts.current();
        assert!(current.is_some());
        assert_eq!(current.unwrap().token, Token(2)); // Shortest timeout
    }

    #[test]
    fn test_timeouts_remove() {
        let mut timeouts = Timeouts::new();
        let instant = Instant::now();
        
        timeouts.add(Token(1), instant + Duration::from_secs(1));
        timeouts.add(Token(2), instant + Duration::from_secs(2));
        timeouts.add(Token(3), instant + Duration::from_secs(3));
        
        assert_eq!(timeouts.len(), 3);
        
        // Remove middle token
        let removed = timeouts.remove(Token(2));
        assert!(removed.is_some());
        assert_eq!(removed.unwrap().token, Token(2));
        assert_eq!(timeouts.len(), 2);
        
        // Remove non-existent token
        let removed = timeouts.remove(Token(99));
        assert!(removed.is_none());
        assert_eq!(timeouts.len(), 2);
    }

    #[test]
    fn test_timeouts_get_token_index() {
        let mut timeouts = Timeouts::new();
        let instant = Instant::now();
        
        timeouts.add(Token(1), instant + Duration::from_secs(1));
        timeouts.add(Token(2), instant + Duration::from_secs(2));
        timeouts.add(Token(3), instant + Duration::from_secs(3));
        
        assert_eq!(timeouts.get_token_index(Token(1)), Some(2));
        assert_eq!(timeouts.get_token_index(Token(2)), Some(1));
        assert_eq!(timeouts.get_token_index(Token(3)), Some(0));
        assert_eq!(timeouts.get_token_index(Token(99)), None);
    }

    #[test]
    fn test_timeouts_current() {
        let mut timeouts = Timeouts::new();
        let instant = Instant::now();
        
        assert!(timeouts.current().is_none());
        
        timeouts.add(Token(1), instant + Duration::from_secs(1));
        let current = timeouts.current();
        assert!(current.is_some());
        assert_eq!(current.unwrap().token, Token(1));
        
        timeouts.add(Token(2), instant + Duration::from_secs(2));
        timeouts.add(Token(3), instant + Duration::from_secs(3));
        
        let current = timeouts.current();
        assert!(current.is_some());
        assert_eq!(current.unwrap().token, Token(1)); // Shortest timeout
    }

    #[test]
    fn test_timeouts_pop_timeouts_empty() {
        let mut timeouts = Timeouts::new();
        let instant = Instant::now();
        
        let result = timeouts.pop_timeouts(instant);
        assert!(result.is_empty());
    }

    #[test]
    fn test_timeouts_pop_timeouts_all() {
        let mut timeouts = Timeouts::new();
        let instant = Instant::now();
        
        timeouts.add(Token(1), instant + Duration::from_secs(1));
        timeouts.add(Token(2), instant + Duration::from_secs(2));
        timeouts.add(Token(3), instant + Duration::from_secs(3));
        
        // Pop all timeouts
        let result = timeouts.pop_timeouts(instant + Duration::from_secs(5));
        assert_eq!(result.len(), 3);
        assert_eq!(result[0].token, Token(1));
        assert_eq!(result[1].token, Token(2));
        assert_eq!(result[2].token, Token(3));
        
        assert!(timeouts.is_empty());
    }

    #[test]
    fn test_timeouts_pop_timeouts_partial() {
        let mut timeouts = Timeouts::new();
        let instant = Instant::now();
        
        timeouts.add(Token(1), instant + Duration::from_secs(1));
        timeouts.add(Token(2), instant + Duration::from_secs(2));
        timeouts.add(Token(3), instant + Duration::from_secs(3));
        timeouts.add(Token(4), instant + Duration::from_secs(4));
        
        let result = timeouts.pop_timeouts(instant + Duration::from_secs(2));
        assert_eq!(result.len(), 2);
        assert_eq!(result[0].token, Token(1));
        assert_eq!(result[1].token, Token(2));
        assert_eq!(timeouts.len(), 2);
        
        let result = timeouts.pop_timeouts(instant + Duration::from_secs(5));
        assert_eq!(result.len(), 2);
        assert_eq!(result[0].token, Token(3));
        assert_eq!(result[1].token, Token(4));
        
        assert!(timeouts.is_empty());
    }

    #[test]
    fn test_timeouts_ordering() {
        let mut timeouts = Timeouts::new();
        let instant = Instant::now();
        
        timeouts.add(Token(5), instant + Duration::from_secs(5));
        timeouts.add(Token(3), instant + Duration::from_secs(3));
        timeouts.add(Token(1), instant + Duration::from_secs(1));
        timeouts.add(Token(4), instant + Duration::from_secs(4));
        timeouts.add(Token(2), instant + Duration::from_secs(2));
        
        let result = timeouts.pop_timeouts(instant + Duration::from_secs(10));
        assert_eq!(result.len(), 5);
        assert_eq!(result[0].token, Token(1));
        assert_eq!(result[1].token, Token(2));
        assert_eq!(result[2].token, Token(3));
        assert_eq!(result[3].token, Token(4));
        assert_eq!(result[4].token, Token(5));
    }
}