use std::collections::HashSet;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use tokio::time::Instant;
use super::{CovSubscriptionKey, DropReason, MultipleContextKey, TimedChange, TimedStore};
use crate::cov::CovRevisits;
#[derive(Debug)]
pub(crate) struct TimedClaim {
store: TimedStore,
changes: Vec<(CovSubscriptionKey, u64, Vec<TimedChange>)>,
untimed: Vec<(CovSubscriptionKey, u64, Option<Instant>)>,
evict: bool,
owing: bool,
}
impl TimedClaim {
pub(crate) fn new(store: TimedStore) -> Self {
Self {
store,
changes: Vec::new(),
untimed: Vec::new(),
evict: true,
owing: false,
}
}
pub(crate) fn add_untimed(
&mut self,
key: CovSubscriptionKey,
generation: u64,
owed_since: Option<Instant>,
) {
self.untimed.push((key, generation, owed_since));
}
pub(crate) fn split_untimed(&mut self, keys: &HashSet<CovSubscriptionKey>) -> Self {
let (moved, kept) = std::mem::take(&mut self.untimed)
.into_iter()
.partition(|(key, _, _)| keys.contains(key));
self.untimed = kept;
Self {
store: self.store.clone(),
changes: Vec::new(),
untimed: moved,
evict: self.evict,
owing: self.owing,
}
}
pub(crate) fn forgo_untimed(&mut self) {
let left_out = std::mem::take(&mut self.untimed).len();
if left_out > 0 {
self.store
.lock()
.counters
.untimed_references_oversized
.fetch_add(left_out as u64, Ordering::Relaxed);
}
}
pub(crate) fn without_eviction(mut self) -> Self {
self.evict = false;
self
}
pub(crate) fn owing(mut self) -> Self {
self.owing = true;
self
}
pub(crate) fn add(
&mut self,
key: CovSubscriptionKey,
incarnation: u64,
changes: Vec<TimedChange>,
) {
if !changes.is_empty() {
self.changes.push((key, incarnation, changes));
}
}
pub(crate) fn split_oldest(&mut self, count: usize) -> Self {
let mut part = Self {
store: self.store.clone(),
changes: Vec::new(),
untimed: Vec::new(),
evict: self.evict,
owing: self.owing,
};
let cut = {
let all = self.in_order();
let moved = count.min(all.len());
moved.checked_sub(1).map(|last| all[last].1.seq)
};
let Some(cut) = cut else {
return part;
};
for (key, incarnation, changes) in &mut self.changes {
let moved = changes.partition_point(|change| change.seq <= cut);
if moved > 0 {
part.changes
.push((key.clone(), *incarnation, changes.drain(..moved).collect()));
}
}
self.changes.retain(|(_, _, changes)| !changes.is_empty());
part
}
pub(crate) fn split_values(
mut self,
fit: impl Fn(&CovSubscriptionKey, &TimedChange) -> ValueFit,
) -> Vec<Self> {
let mut all: Vec<_> = std::mem::take(&mut self.changes)
.into_iter()
.flat_map(|(key, incarnation, changes)| {
changes
.into_iter()
.map(move |change| (key.clone(), incarnation, change))
})
.collect();
all.sort_by_key(|(_, _, change)| change.seq);
let mut parts = Vec::new();
let mut lost = Vec::new();
for (key, incarnation, change) in all {
let mut kept = Vec::new();
let mut too_large = false;
for value in change.into_values() {
match fit(&key, &value) {
ValueFit::Fits => kept.push(value),
ValueFit::TooLarge => too_large = true,
ValueFit::Empty => {}
}
}
if too_large {
lost.push(key.clone());
}
if let Some(last) = kept.last_mut() {
last.continues = false;
}
parts.extend(kept.into_iter().map(|value| Self {
store: self.store.clone(),
changes: vec![(key.clone(), incarnation, vec![value])],
untimed: Vec::new(),
evict: self.evict,
owing: self.owing,
}));
}
if !lost.is_empty() {
let mut store = self.store.lock();
for key in lost {
store.dropped(&key, 1, DropReason::TooLarge);
}
}
parts
}
pub(crate) fn in_order(&self) -> Vec<(&CovSubscriptionKey, &TimedChange)> {
let mut all: Vec<_> = self
.changes
.iter()
.flat_map(|(key, _, changes)| changes.iter().map(move |change| (key, change)))
.collect();
all.sort_by_key(|(_, change)| change.seq);
all
}
pub(crate) fn last_changes(&self) -> impl Iterator<Item = (&CovSubscriptionKey, &TimedChange)> {
self.changes
.iter()
.filter_map(|(key, _, changes)| Some((key, changes.last()?)))
}
pub(crate) fn commit(mut self) {
self.untimed.clear();
let mut store = self.store.lock();
for (key, incarnation, changes) in self.changes.drain(..) {
match changes.iter().rev().find(|change| !change.continues) {
Some(last) => store.commit(&key, incarnation, last.seq),
None => {
if let Some(part) = changes.last() {
store.deliver_by_value(&key, incarnation, part.seq);
}
}
}
}
}
pub(crate) fn going_out(&self) {
let mut ahead = self
.changes
.iter()
.filter_map(|(key, incarnation, changes)| {
let part = changes.last().filter(|part| part.continues)?;
Some((key, *incarnation, part.seq))
})
.peekable();
if ahead.peek().is_some() {
let mut store = self.store.lock();
for (key, incarnation, seq) in ahead {
store.deliver_by_value(key, incarnation, seq);
}
}
}
}
impl Drop for TimedClaim {
fn drop(&mut self) {
if self.changes.is_empty() && self.untimed.is_empty() {
return;
}
let mut store = self.store.lock();
for (key, incarnation, changes) in self.changes.drain(..) {
store.requeue(&key, incarnation, changes, self.evict);
}
for (key, generation, owed_since) in self.untimed.drain(..) {
if let Some(since) = owed_since.or_else(|| self.owing.then(Instant::now)) {
store.owe(&key, generation, since);
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ValueFit {
Fits,
TooLarge,
Empty,
}
#[must_use = "dropping the turn ends the report"]
pub(crate) struct SendTurn {
store: TimedStore,
context: MultipleContextKey,
revisits: Arc<CovRevisits>,
keys: Vec<CovSubscriptionKey>,
}
impl SendTurn {
pub(crate) fn begin(
store: &TimedStore,
context: &MultipleContextKey,
revisits: &Arc<CovRevisits>,
keys: Vec<CovSubscriptionKey>,
) -> Option<Self> {
store.lock().begin_send(context).then(|| Self {
store: store.clone(),
context: context.clone(),
revisits: Arc::clone(revisits),
keys,
})
}
}
impl Drop for SendTurn {
fn drop(&mut self) {
let owed = self.store.lock().end_send(&self.context);
if owed {
self.revisits.request(std::mem::take(&mut self.keys));
}
}
}