use std::cmp::{Ordering, Reverse};
use std::collections::{BinaryHeap, HashMap, VecDeque};
use std::fmt;
use std::future::{Future, ready};
use std::sync::Mutex;
use tokio::time::Instant;
use crate::Result;
use crate::frontier::seed::{Fingerprint, Seed};
use crate::frontier::{Ack, Admission, Frontier, Pop, Report, Schedule};
use crate::sync::lock;
pub struct InMemFrontier {
seeds: Mutex<Store>,
scheduled: Mutex<Option<Scheduled>>,
}
#[derive(Default)]
struct Scheduled {
seeds: HashMap<Fingerprint, (Instant, u64, Seed)>,
due: BinaryHeap<Reverse<(Instant, u64, Fingerprint)>>,
sequence: u64,
}
impl Scheduled {
fn insert(&mut self, seed: Seed, due: Instant) {
self.sequence += 1;
let fingerprint = seed.fingerprint();
self.due.push(Reverse((due, self.sequence, fingerprint)));
self.seeds.insert(fingerprint, (due, self.sequence, seed));
}
fn settle(&mut self) {
while let Some(Reverse((_, sequence, fingerprint))) = self.due.peek() {
let current = self
.seeds
.get(fingerprint)
.is_some_and(|(_, current, _)| current == sequence);
if current {
break;
}
self.due.pop();
}
}
fn pop_due(&mut self, now: Instant) -> Option<Seed> {
self.settle();
let Reverse((due, _, fingerprint)) = *self.due.peek()?;
if due > now {
return None;
}
self.due.pop();
self.seeds.remove(&fingerprint).map(|(_, _, seed)| seed)
}
fn next_due(&mut self) -> Option<Instant> {
self.settle();
self.due.peek().map(|Reverse((due, _, _))| *due)
}
}
enum Store {
Fifo(VecDeque<Seed>),
Lifo(VecDeque<Seed>),
Priority(BinaryHeap<Prioritized>, u64),
}
struct Prioritized {
priority: i32,
sequence: u64,
seed: Seed,
}
impl Ord for Prioritized {
fn cmp(&self, other: &Self) -> Ordering {
self.priority
.cmp(&other.priority)
.then_with(|| other.sequence.cmp(&self.sequence))
}
}
impl PartialOrd for Prioritized {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Prioritized {
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
}
}
impl Eq for Prioritized {}
impl Store {
fn len(&self) -> usize {
match self {
Self::Fifo(seeds) | Self::Lifo(seeds) => seeds.len(),
Self::Priority(seeds, _) => seeds.len(),
}
}
const fn order(&self) -> &'static str {
match self {
Self::Fifo(_) => "fifo",
Self::Lifo(_) => "lifo",
Self::Priority(..) => "priority",
}
}
}
impl InMemFrontier {
#[must_use]
pub const fn fifo() -> Self {
Self::with_store(Store::Fifo(VecDeque::new()))
}
#[must_use]
pub const fn lifo() -> Self {
Self::with_store(Store::Lifo(VecDeque::new()))
}
#[must_use]
pub const fn priority() -> Self {
Self::with_store(Store::Priority(BinaryHeap::new(), 0))
}
const fn with_store(store: Store) -> Self {
Self {
seeds: Mutex::new(store),
scheduled: Mutex::new(None),
}
}
fn insert(&self, seed: Seed) {
match &mut *lock(&self.seeds) {
Store::Fifo(seeds) | Store::Lifo(seeds) => seeds.push_back(seed),
Store::Priority(seeds, sequence) => {
*sequence += 1;
let priority = seed.priority();
seeds.push(Prioritized {
priority,
sequence: *sequence,
seed,
});
}
}
}
}
impl Default for InMemFrontier {
fn default() -> Self {
Self::fifo()
}
}
impl fmt::Debug for InMemFrontier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let store = lock(&self.seeds);
let scheduled = lock(&self.scheduled)
.as_ref()
.map_or(0, |scheduled| scheduled.seeds.len());
f.debug_struct("InMemFrontier")
.field("order", &store.order())
.field("len", &store.len())
.field("scheduled", &scheduled)
.finish()
}
}
impl Frontier for InMemFrontier {
fn push(&self, seed: Seed) -> impl Future<Output = Result<Admission>> + Send {
self.insert(seed);
ready(Ok(Admission::Queued))
}
fn pop(&self) -> impl Future<Output = Result<Pop>> + Send {
let due = lock(&self.scheduled)
.as_mut()
.and_then(|scheduled| scheduled.pop_due(Instant::now()));
if let Some(seed) = due {
return ready(Ok(Pop::Ready(seed)));
}
let seed = match &mut *lock(&self.seeds) {
Store::Fifo(seeds) => seeds.pop_front(),
Store::Lifo(seeds) => seeds.pop_back(),
Store::Priority(seeds, _) => seeds.pop().map(|prioritized| prioritized.seed),
};
ready(Ok(seed.map_or(Pop::Empty, Pop::Ready)))
}
fn report(&self, report: Report) -> impl Future<Output = Result<Ack>> + Send {
let ack = match report {
Report::Cancelled(seed) => {
self.insert(seed);
Ack::Applied
}
Report::Retry { .. } => Ack::RetryRefused,
_ => Ack::Applied,
};
ready(Ok(ack))
}
fn size_hint(&self) -> Option<usize> {
Some(lock(&self.seeds).len())
}
fn snapshot(&self) -> Option<Vec<Seed>> {
let store = lock(&self.seeds);
let mut seeds: Vec<Seed> = match &*store {
Store::Fifo(seeds) | Store::Lifo(seeds) => seeds.iter().cloned().collect(),
Store::Priority(seeds, _) => seeds.iter().map(|entry| entry.seed.clone()).collect(),
};
if let Some(scheduled) = &*lock(&self.scheduled) {
seeds.extend(scheduled.seeds.values().map(|(_, _, seed)| seed.clone()));
}
Some(seeds)
}
}
impl Schedule for InMemFrontier {
fn schedule(&self, seed: Seed, due: Instant) -> impl Future<Output = Result<()>> + Send {
lock(&self.scheduled)
.get_or_insert_with(Scheduled::default)
.insert(seed, due);
ready(Ok(()))
}
fn next_due(&self) -> impl Future<Output = Result<Option<Instant>>> + Send {
ready(Ok(lock(&self.scheduled)
.as_mut()
.and_then(Scheduled::next_due)))
}
}
impl Extend<Seed> for InMemFrontier {
fn extend<I: IntoIterator<Item = Seed>>(&mut self, seeds: I) {
for seed in seeds {
self.insert(seed);
}
}
}
impl FromIterator<Seed> for InMemFrontier {
fn from_iter<I: IntoIterator<Item = Seed>>(seeds: I) -> Self {
let mut frontier = Self::fifo();
frontier.extend(seeds);
frontier
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontier::test_util::{pop_path, seed};
#[tokio::test]
async fn fifo_and_lifo_order() {
let fifo = InMemFrontier::fifo();
let lifo = InMemFrontier::lifo();
for path in ["a", "b"] {
assert!(fifo.push(seed(path)).await.unwrap().is_queued());
assert!(lifo.push(seed(path)).await.unwrap().is_queued());
}
assert_eq!(fifo.size_hint(), Some(2));
assert_eq!(pop_path(&fifo).await.as_deref(), Some("a"));
assert_eq!(pop_path(&lifo).await.as_deref(), Some("b"));
assert_eq!(pop_path(&fifo).await.as_deref(), Some("b"));
assert!(matches!(fifo.pop().await.unwrap(), Pop::Empty));
}
#[tokio::test]
async fn priority_order() {
let frontier = InMemFrontier::priority();
for (path, priority) in [("low", -1), ("first", 0), ("high", 5), ("second", 0)] {
frontier
.push(seed(path).with_priority(priority))
.await
.unwrap();
}
let mut order = Vec::new();
while let Some(path) = pop_path(&frontier).await {
order.push(path);
}
assert_eq!(order, ["high", "first", "second", "low"]);
}
}