use std::collections::HashMap;
use std::sync::{Arc, Mutex, PoisonError, Weak};
#[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
use std::sync::atomic::{AtomicU64, Ordering};
use crate::client::SharedBackend;
const SWEEP_THRESHOLD: usize = 128;
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
const FILL_LOCK_TIMEOUT_MS: u64 = 5_000;
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
const FILL_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(100);
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
const FILL_POLL_BUDGET: u32 = 50;
#[derive(Default)]
pub(crate) struct FlightMap {
entries: Mutex<HashMap<String, Weak<tokio::sync::Mutex<()>>>>,
}
impl FlightMap {
fn handle(&self, key: &str) -> Arc<tokio::sync::Mutex<()>> {
let mut map = self.entries.lock().unwrap_or_else(PoisonError::into_inner);
if map.len() > SWEEP_THRESHOLD {
map.retain(|_, w| w.strong_count() > 0);
}
if let Some(existing) = map.get(key).and_then(Weak::upgrade) {
return existing;
}
let fresh = Arc::new(tokio::sync::Mutex::new(()));
map.insert(key.to_owned(), Arc::downgrade(&fresh));
fresh
}
}
#[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
pub(crate) struct MutationState {
lock: Arc<tokio::sync::Mutex<()>>,
version: AtomicU64,
}
#[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
impl MutationState {
fn new() -> Self {
Self {
lock: Arc::new(tokio::sync::Mutex::new(())),
version: AtomicU64::new(0),
}
}
pub(crate) fn version(&self) -> u64 {
self.version.load(Ordering::Acquire)
}
}
#[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
#[derive(Default)]
pub(crate) struct MutationMap {
entries: Mutex<HashMap<String, Weak<MutationState>>>,
}
#[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
impl MutationMap {
pub(crate) fn state(&self, key: &str) -> Arc<MutationState> {
let mut map = self.entries.lock().unwrap_or_else(PoisonError::into_inner);
if map.len() > SWEEP_THRESHOLD {
map.retain(|_, weak| weak.strong_count() > 0);
}
if let Some(existing) = map.get(key).and_then(Weak::upgrade) {
return existing;
}
let fresh = Arc::new(MutationState::new());
map.insert(key.to_owned(), Arc::downgrade(&fresh));
fresh
}
pub(crate) async fn lock(&self, key: &str) -> MutationGuard {
let state = self.state(key);
let lock = Arc::clone(&state.lock).lock_owned().await;
MutationGuard { state, _lock: lock }
}
}
#[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
pub(crate) struct MutationGuard {
state: Arc<MutationState>,
_lock: tokio::sync::OwnedMutexGuard<()>,
}
#[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
impl MutationGuard {
pub(crate) fn snapshot(&self) -> (Arc<MutationState>, u64) {
(Arc::clone(&self.state), self.state.version())
}
pub(crate) fn is_current(&self, state: &Arc<MutationState>, version: u64) -> bool {
Arc::ptr_eq(&self.state, state) && self.state.version() == version
}
pub(crate) fn advance(&self) {
self.state.version.fetch_add(1, Ordering::Release);
}
}
enum Role {
Leader,
LocalFollower { rechecked: bool },
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
RemoteContested { polls_left: u32 },
}
pub struct SingleFlight {
_local: tokio::sync::OwnedMutexGuard<()>,
role: Role,
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
dist: Option<DistLock>,
}
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
struct DistLock {
backend: SharedBackend,
full_key: String,
lock_id: String,
}
impl SingleFlight {
pub async fn wait_for_fill(&mut self) -> bool {
match &mut self.role {
Role::Leader => false,
Role::LocalFollower { rechecked } => {
let first = !*rechecked;
*rechecked = true;
first
}
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
Role::RemoteContested { polls_left } => {
if *polls_left == 0 {
return false;
}
*polls_left -= 1;
tokio::time::sleep(FILL_POLL_INTERVAL).await;
true
}
}
}
pub async fn release(self) {
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
if let Some(dist) = self.dist {
if let Some(lockable) = dist.backend.as_lockable() {
let _ = lockable.release_lock(&dist.full_key, &dist.lock_id).await;
}
}
}
pub(crate) async fn acquire(map: &FlightMap, backend: &SharedBackend, full_key: &str) -> Self {
let handle = map.handle(full_key);
match Arc::clone(&handle).try_lock_owned() {
Ok(local) => Self::lead(local, backend, full_key).await,
Err(_) => {
let local = handle.lock_owned().await;
Self {
_local: local,
role: Role::LocalFollower { rechecked: false },
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
dist: None,
}
}
}
}
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
async fn lead(
local: tokio::sync::OwnedMutexGuard<()>,
backend: &SharedBackend,
full_key: &str,
) -> Self {
let (role, dist) = match backend.as_lockable() {
Some(lockable) => match lockable.acquire_lock(full_key, FILL_LOCK_TIMEOUT_MS).await {
Ok(Some(lock_id)) => (
Role::Leader,
Some(DistLock {
backend: backend.clone(),
full_key: full_key.to_owned(),
lock_id,
}),
),
Ok(None) => (
Role::RemoteContested {
polls_left: FILL_POLL_BUDGET,
},
None,
),
Err(_) => (Role::Leader, None),
},
None => (Role::Leader, None),
};
Self {
_local: local,
role,
dist,
}
}
#[cfg(not(all(feature = "reliability", not(target_arch = "wasm32"))))]
async fn lead(
local: tokio::sync::OwnedMutexGuard<()>,
_backend: &SharedBackend,
_full_key: &str,
) -> Self {
Self {
_local: local,
role: Role::Leader,
}
}
}