// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // //! `MuxerKillQ` implements a helper object that `VsockMuxer` can use for scheduling forced //! connection termination. I.e. after one peer issues a clean shutdown request //! (VSOCK_OP_SHUTDOWN), the concerned connection is queued for termination (VSOCK_OP_RST) in //! the near future (herein implemented via an expiring timer). //! //! Whenever the muxer needs to schedule a connection for termination, it pushes it (or rather //! an identifier - the connection key) to this queue. A subsequent pop() operation will //! succeed if and only if the first connection in the queue is ready to be terminated (i.e. //! its kill timer expired). //! //! Without using this queue, the muxer would have to walk its entire connection pool //! (hashmap), whenever it needs to check for expired kill timers. With this queue, both //! scheduling and termination are performed in constant time. However, since we don't want to //! waste space on a kill queue that's as big as the connection hashmap itself, it is possible //! that this queue may become full at times. We call this kill queue "synchronized" if we are //! certain that all connections that are awaiting termination are present in the queue. This //! means a simple constant-time pop() operation is enough to check whether any connections //! need to be terminated. When the kill queue becomes full, though, pushing fails, so //! connections that should be terminated are left out. The queue is not synchronized anymore. //! When that happens, the muxer will first drain the queue, and then replace it with a new //! queue, created by walking the connection pool, looking for connections that will be //! expiring in the future. use std::collections::{HashMap, VecDeque}; use std::time::Instant; use super::defs; use super::muxer::ConnMapKey; use super::MuxerConnection; /// A kill queue item, holding the connection key and the scheduled time for termination. /// #[derive(Clone, Copy)] struct MuxerKillQItem { key: ConnMapKey, kill_time: Instant, } /// The connection kill queue: a FIFO structure, storing the connections that are scheduled for /// termination. /// pub struct MuxerKillQ { /// The kill queue contents. q: VecDeque, /// The kill queue sync status: /// - when true, all connections that are awaiting termination are guaranteed to be in this /// queue; /// - when false, some connections may have been left out. /// synced: bool, } impl MuxerKillQ { const SIZE: usize = defs::MUXER_KILLQ_SIZE; /// Trivial kill queue constructor. /// pub fn new() -> Self { Self { q: VecDeque::with_capacity(Self::SIZE), synced: true, } } /// Create a kill queue by walking the connection pool, looking for connections that are /// set to expire at some point in the future. /// Note: if more than `Self::SIZE` connections are found, the queue will be created in an /// out-of-sync state, and will be discarded after it is emptied. /// pub fn from_conn_map(conn_map: &HashMap) -> Self { let mut q_buf: Vec = Vec::with_capacity(Self::SIZE); let mut synced = true; for (key, conn) in conn_map.iter() { if !conn.will_expire() { continue; } if q_buf.len() >= Self::SIZE { synced = false; break; } q_buf.push(MuxerKillQItem { key: *key, kill_time: conn.expiry().unwrap(), }); } q_buf.sort_unstable_by_key(|it| it.kill_time); Self { q: q_buf.into(), synced, } } /// Push a connection key to the queue, scheduling it for termination at /// `CONN_SHUTDOWN_TIMEOUT_MS` from now (the push time). /// pub fn push(&mut self, key: ConnMapKey, kill_time: Instant) { if !self.is_synced() || self.is_full() { self.synced = false; return; } self.q.push_back(MuxerKillQItem { key, kill_time }); } /// Attempt to pop an expired connection from the kill queue. /// /// This will succeed and return a connection key, only if the connection at the front of /// the queue has expired. Otherwise, `None` is returned. /// pub fn pop(&mut self) -> Option { if let Some(item) = self.q.front() { if Instant::now() > item.kill_time { return Some(self.q.pop_front().unwrap().key); } } None } /// Check if the kill queue is synchronized with the connection pool. /// pub fn is_synced(&self) -> bool { self.synced } /// Check if the kill queue is empty, obviously. /// pub fn is_empty(&self) -> bool { self.q.len() == 0 } /// Check if the kill queue is full. /// pub fn is_full(&self) -> bool { self.q.len() == Self::SIZE } }