use std::hash::Hash;
use indexmap::map::Entry;
use indexmap::{IndexMap, IndexSet};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use smallvec::SmallVec;
use tokio::time::Instant;
use super::lru_candidates::LruCandidates;
use super::{PendingCallback, ShardConfig, TakeDataSender};
use crate::ServiceData;
type PendingCallbacks<Data> = SmallVec<[PendingCallback<Data>; 1]>;
enum InternalDataState<Key, Data> {
Loading(PendingCallbacks<Data>, Option<TakeDataSender<Key, Data>>),
Loaded(Data, u64),
}
pub(super) enum DataState<'a, 'b: 'a, Key: Eq + Hash + Clone, Data: ServiceData> {
Loaded(DataGuard<'a, 'b, Key, Data>),
MustLoad(Key, &'a mut PendingCallbacks<Data>),
Loading(&'a mut PendingCallbacks<Data>),
}
impl<Key, Data> InternalDataState<Key, Data> {
#[inline]
fn is_loading(&self) -> bool {
match self {
InternalDataState::Loading(_, _) => true,
InternalDataState::Loaded(_, _) => false,
}
}
#[inline]
fn is_loaded(&self) -> bool {
match self {
InternalDataState::Loaded(_, _) => true,
InternalDataState::Loading(_, _) => false,
}
}
fn into_pending_action(self) -> Option<PendingActions<Key, Data>> {
match self {
InternalDataState::Loaded(_, _) => None,
InternalDataState::Loading(callbacks, take_data_sender) => Some(PendingActions {
callbacks,
take_data_sender,
}),
}
}
fn get_pending_callbacks_mut(&mut self) -> &mut PendingCallbacks<Data> {
match self {
InternalDataState::Loading(pending_callbacks, _) => pending_callbacks,
InternalDataState::Loaded(_, _) => {
panic!("invariant: `get_pending_callbacks_mut` called when data state is loaded.")
}
}
}
fn get_data_mut(&mut self) -> &mut Data {
match self {
InternalDataState::Loaded(data, _) => data,
InternalDataState::Loading(_, _) => {
panic!("invariant: `get_data_mut` called when data state is loading.")
}
}
}
}
#[derive(Default)]
struct AccessOffset(u64);
impl AccessOffset {
#[inline]
fn next(&mut self) -> u64 {
self.0 += 1;
self.0
}
}
pub(super) struct DataMap<Key, Data> {
data: IndexMap<Key, InternalDataState<Key, Data>>,
expiring_keys: IndexSet<Key>,
lru_candidates: LruCandidates<Key>,
lru_candidates_num_probes: u16,
max_data_capacity: usize,
rng: StdRng,
access_offset: AccessOffset,
}
impl<Key: Hash + Eq + Clone, Data: ServiceData> DataMap<Key, Data> {
pub(super) fn new(config: &ShardConfig) -> Self {
let data = IndexMap::with_capacity(config.max_data_capacity);
let expiring_keys = IndexSet::with_capacity(config.max_data_capacity);
let lru_candidates = LruCandidates::default();
Self {
data,
max_data_capacity: config.max_data_capacity,
expiring_keys,
lru_candidates,
lru_candidates_num_probes: config.lru_candidates_num_probes,
rng: StdRng::from_entropy(),
access_offset: AccessOffset::default(),
}
}
#[inline]
pub(super) fn is_at_capacity(&self) -> bool {
self.data.len() == self.max_data_capacity
}
#[inline]
pub(super) fn len(&self) -> usize {
self.data.len()
}
#[inline]
pub(super) fn expiring_keys_len(&self) -> usize {
self.expiring_keys.len()
}
#[inline]
pub(super) fn would_exceed_capacity_if_inserted(&self, key: &Key) -> bool {
self.is_at_capacity() && !self.data.contains_key(key)
}
#[inline]
pub(super) fn get_loaded_data<'a, 'b: 'a>(
&'a mut self,
key: &'b Key,
) -> Option<DataGuard<'a, 'b, Key, Data>> {
match self.data.get_mut(key) {
Some(InternalDataState::Loaded(data, access_offset)) => {
self.lru_candidates
.remove_candidate_by_access_offset(*access_offset);
*access_offset = self.access_offset.next();
Some(DataGuard {
data_ref: data,
key_ref: key,
prev_expires: PrevExpires::Unknown,
expiring_keys: &mut self.expiring_keys,
})
}
_ => None,
}
}
#[must_use = "you must do something with the loaded data."]
pub(super) fn remove_loaded_data(&mut self, key: &Key) -> Option<(Key, Data)> {
match self.data.swap_remove_full(key)? {
(_, key, InternalDataState::Loaded(data, access_offset)) => {
self.lru_candidates
.remove_candidate_by_access_offset(access_offset);
if data.get_expires_at().is_some() {
self.expiring_keys.remove(&key);
}
Some((key, data))
}
(_, _, InternalDataState::Loading(_, _)) => {
panic!("invariant: remove_loaded_data attempted to remove data which is loading.")
}
}
}
pub(super) fn probe_and_take_best_lru_candidate(&mut self) -> Option<Key> {
self.probe_lru_candidates();
self.lru_candidates.take_best_candidate()
}
pub(super) fn probe_and_take_expiring_key(
&mut self,
now: &Instant,
) -> Result<Option<Key>, ProbeEmpty> {
if self.expiring_keys.is_empty() {
return Err(ProbeEmpty);
}
let idx = self.rng.gen_range(0..self.expiring_keys.len());
let maybe_expiring_key = self
.expiring_keys
.get_index(idx)
.expect("invariant: Tried to get_index on an invalid range.");
let data = self
.data
.get(maybe_expiring_key)
.expect("invariant: tried to get key that existed in `expiring_keys` but not `data`");
let data = match data {
InternalDataState::Loading(_, _) => return Ok(None),
InternalDataState::Loaded(data, _) => data,
};
let expiry = match data.get_expires_at() {
None => {
panic!("invariant: a key was in `expiring_keys` that does not have an expiration.")
}
Some(expiry) => expiry,
};
if expiry > now {
return Ok(None);
}
let expiring_key = self.expiring_keys
.swap_remove_index(idx)
.expect("invariant: tried to `swap_remove_index` on an index that no longer exists in `expiring_keys`");
Ok(Some(expiring_key))
}
pub(super) fn swap_loaded_data(
&mut self,
key: Key,
data: Data,
) -> Option<(DataGuard<'_, '_, Key, Data>, PendingActions<Key, Data>)> {
match self.data.get_full_mut(&key) {
None | Some((_, _, InternalDataState::Loaded(_, _))) => None,
Some((_idx, key_ref, entry @ InternalDataState::Loading(_, _))) => {
let access_offset = self.access_offset.next();
self.lru_candidates
.try_insert_candidate(key_ref, access_offset);
let loading_data_state =
std::mem::replace(entry, InternalDataState::Loaded(data, access_offset));
let pending_actions = loading_data_state
.into_pending_action()
.expect("invariant: data state was not loading");
let data_ref = match entry {
InternalDataState::Loaded(data, _) => data,
InternalDataState::Loading(_, _) => unreachable!(),
};
let guard = DataGuard {
data_ref,
key_ref,
prev_expires: PrevExpires::Known(None),
expiring_keys: &mut self.expiring_keys,
};
Some((guard, pending_actions))
}
}
}
pub(super) fn take_pending_actions(
&mut self,
key: Key,
) -> Option<PendingActionsWithKey<Key, Data>> {
match self.data.entry(key) {
Entry::Occupied(ent) if ent.get().is_loading() => {
let (k, v) = ent.swap_remove_entry();
Some(PendingActionsWithKey::new(k, v.into_pending_action()?))
}
_ => None,
}
}
pub(super) fn get_or_insert_loaded_or_loading_data<F>(
&mut self,
key: Key,
try_takeover: F,
) -> DataState<Key, Data>
where
F: FnOnce(&Key) -> Option<Data>,
{
match self.data.entry(key) {
Entry::Vacant(ent) => {
let maybe_data = (try_takeover)(ent.key());
match maybe_data {
None => {
let key = ent.key().clone();
let data_state =
ent.insert(InternalDataState::Loading(SmallVec::new(), None));
DataState::MustLoad(key, data_state.get_pending_callbacks_mut())
}
Some(data) => {
let access_offset = self.access_offset.next();
let (key_ref, value) =
ent.insert_entry(InternalDataState::Loaded(data, access_offset));
self.lru_candidates
.try_insert_candidate(key_ref, access_offset);
DataState::Loaded(DataGuard {
data_ref: value.get_data_mut(),
key_ref,
prev_expires: PrevExpires::Known(None),
expiring_keys: &mut self.expiring_keys,
})
}
}
}
Entry::Occupied(ent) => {
let (key_ref, value) = ent.into_entry_mut();
match value {
InternalDataState::Loading(callbacks, _) => DataState::Loading(callbacks),
InternalDataState::Loaded(data_ref, access_offset) => {
self.lru_candidates
.remove_candidate_by_access_offset(*access_offset);
*access_offset = self.access_offset.next();
DataState::Loaded(DataGuard {
data_ref,
key_ref,
prev_expires: PrevExpires::Known(None),
expiring_keys: &mut self.expiring_keys,
})
}
}
}
}
}
pub(super) fn try_take(
&mut self,
key: Key,
sender: TakeDataSender<Key, Data>,
) -> TryTakeResult<Key, Data> {
let mut occupied_entry = match self.data.entry(key) {
Entry::Vacant(ent) => return TryTakeResult::NotFound(ent.into_key(), sender),
Entry::Occupied(ent) => ent,
};
match occupied_entry.get_mut() {
InternalDataState::Loading(_pending_callbacks, existing_sender) => {
if existing_sender.is_some() {
return TryTakeResult::TakeAlreadyEnqueued(sender);
}
*existing_sender = Some(sender);
TryTakeResult::Enqueued
}
InternalDataState::Loaded(_, _) => {
let (key, data) = match occupied_entry.swap_remove_entry() {
(key, InternalDataState::Loaded(data, access_offset)) => {
self.lru_candidates
.remove_candidate_by_access_offset(access_offset);
if data.get_expires_at().is_some() {
self.expiring_keys.remove(&key);
}
(key, data)
}
_ => unreachable!(),
};
TryTakeResult::Taken(key, data, sender)
}
}
}
#[inline]
fn probe_lru_candidates(&mut self) {
if self.data.is_empty() {
return;
}
for _ in 0..self.lru_candidates_num_probes {
let index = self.rng.gen_range(0..self.data.len());
match &mut self.data.get_index_mut(index) {
Some((key, InternalDataState::Loaded(_data, access_offset))) => {
self.lru_candidates
.try_insert_candidate(key, *access_offset);
}
Some(_) => {}
None => break,
};
}
}
}
enum PrevExpires {
Unknown,
Known(Option<Instant>),
}
enum ExpirationChangeStatus {
Unchanged,
NowExpiring,
NoLongerExpiring,
}
impl PrevExpires {
#[inline]
fn update_if_unknown<Data: ServiceData>(&mut self, data: &Data) {
match self {
PrevExpires::Unknown => {
let _ = std::mem::replace(self, PrevExpires::Known(data.get_expires_at().cloned()));
}
PrevExpires::Known(_) => {}
}
}
#[inline]
fn get_change_status<Data: ServiceData>(&self, data: &Data) -> ExpirationChangeStatus {
match self {
PrevExpires::Unknown => ExpirationChangeStatus::Unchanged,
PrevExpires::Known(prev_expires) => match (prev_expires, data.get_expires_at()) {
(None, None) | (Some(_), Some(_)) => ExpirationChangeStatus::Unchanged,
(Some(_), None) => ExpirationChangeStatus::NoLongerExpiring,
(None, Some(_)) => ExpirationChangeStatus::NowExpiring,
},
}
}
}
pub(super) struct DataGuard<'a, 'b: 'a, Key: Eq + Hash + Clone, Data: ServiceData> {
key_ref: &'b Key,
data_ref: &'a mut Data,
expiring_keys: &'a mut IndexSet<Key>,
prev_expires: PrevExpires,
}
impl<'a, 'b, Key: Eq + Hash + Clone, Data: ServiceData> DataGuard<'a, 'b, Key, Data> {
#[inline]
pub(super) fn as_ref(&self) -> &Data {
self.data_ref
}
#[inline]
pub(super) fn as_mut(&mut self) -> &mut Data {
self.prev_expires.update_if_unknown(self.data_ref);
self.data_ref
}
#[inline]
pub(super) fn clone_key(&self) -> Key {
self.key_ref.clone()
}
#[inline]
pub(super) fn into_cloned_key(self) -> Key {
self.clone_key()
}
}
impl<'a, 'b, Key: Eq + Hash + Clone, Data: ServiceData> Drop for DataGuard<'a, 'b, Key, Data> {
fn drop(&mut self) {
match self.prev_expires.get_change_status(self.data_ref) {
ExpirationChangeStatus::Unchanged => {}
ExpirationChangeStatus::NoLongerExpiring => {
self.expiring_keys.remove(self.key_ref);
}
ExpirationChangeStatus::NowExpiring => {
self.expiring_keys.insert(self.clone_key());
}
}
}
}
pub(super) enum TryTakeResult<Key, Data> {
NotFound(Key, TakeDataSender<Key, Data>),
Taken(Key, Data, TakeDataSender<Key, Data>),
TakeAlreadyEnqueued(TakeDataSender<Key, Data>),
Enqueued,
}
pub(super) struct ProbeEmpty;
pub(super) struct PendingActions<Key, Data> {
pub(super) callbacks: PendingCallbacks<Data>,
pub(super) take_data_sender: Option<TakeDataSender<Key, Data>>,
}
pub(super) struct PendingActionsWithKey<Key, Data> {
pub(super) key: Key,
pub(super) callbacks: PendingCallbacks<Data>,
pub(super) take_data_sender: Option<TakeDataSender<Key, Data>>,
}
impl<Key, Data> PendingActionsWithKey<Key, Data> {
fn new(key: Key, pending_actions: PendingActions<Key, Data>) -> Self {
Self {
key,
callbacks: pending_actions.callbacks,
take_data_sender: pending_actions.take_data_sender,
}
}
}