use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, Mutex, Weak};
use tokio::sync::{Mutex as AsyncMutex, OwnedMutexGuard};
use crate::control::cluster::calvin::scheduler::lock_manager::LockKey;
const SHARDS: usize = 64;
const MIN_REAP: usize = 16;
struct Shard {
map: HashMap<LockKey, Weak<AsyncMutex<()>>>,
reap_at: usize,
}
impl Shard {
fn new() -> Self {
Self {
map: HashMap::new(),
reap_at: MIN_REAP,
}
}
}
pub struct KeyedWriteOrderLock {
shards: [Mutex<Shard>; SHARDS],
}
impl KeyedWriteOrderLock {
pub fn new() -> Self {
Self {
shards: std::array::from_fn(|_| Mutex::new(Shard::new())),
}
}
pub async fn lock_owned(&self, key: LockKey) -> OwnedMutexGuard<()> {
self.mutex_for(key).lock_owned().await
}
fn shard_index(key: &LockKey) -> usize {
let mut hasher = DefaultHasher::new();
key.hash(&mut hasher);
(hasher.finish() as usize) & (SHARDS - 1)
}
fn mutex_for(&self, key: LockKey) -> Arc<AsyncMutex<()>> {
let shard = &self.shards[Self::shard_index(&key)];
let mut shard = shard.lock().unwrap_or_else(|p| p.into_inner());
if let Some(existing) = shard.map.get(&key).and_then(Weak::upgrade) {
return existing;
}
if shard.map.len() >= shard.reap_at {
shard.map.retain(|_, weak| weak.strong_count() > 0);
shard.reap_at = shard.map.len().saturating_mul(2).max(MIN_REAP);
}
let created = Arc::new(AsyncMutex::new(()));
shard.map.insert(key, Arc::downgrade(&created));
created
}
}
impl Default for KeyedWriteOrderLock {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
fn key(k: &[u8]) -> LockKey {
LockKey::Kv {
collection: Arc::from("c"),
key: Arc::from(k),
}
}
#[tokio::test]
async fn warm_key_reuses_mutex_and_reaps_when_idle() {
let lock = KeyedWriteOrderLock::new();
let k = key(b"a");
{
let _g = lock.lock_owned(k.clone()).await;
let idx = KeyedWriteOrderLock::shard_index(&k);
let shard = lock.shards[idx].lock().expect("shard");
assert!(
shard.map.get(&k).and_then(Weak::upgrade).is_some(),
"held key must have a live entry"
);
}
let idx = KeyedWriteOrderLock::shard_index(&k);
let dead = {
let shard = lock.shards[idx].lock().expect("shard");
shard.map.get(&k).and_then(Weak::upgrade).is_none()
};
assert!(dead, "idle key's mutex must be released");
}
#[tokio::test]
async fn same_key_serializes_fifo() {
let lock = Arc::new(KeyedWriteOrderLock::new());
let k = key(b"K");
let order = Arc::new(std::sync::Mutex::new(Vec::<u32>::new()));
let held = lock.lock_owned(k.clone()).await;
let mut handles = Vec::new();
for id in [1u32, 2u32] {
let lock = Arc::clone(&lock);
let order = Arc::clone(&order);
let k = k.clone();
let h = tokio::spawn(async move {
let _g = lock.lock_owned(k).await;
order.lock().expect("order").push(id);
});
handles.push(h);
tokio::task::yield_now().await;
tokio::task::yield_now().await;
}
assert!(
order.lock().expect("order").is_empty(),
"same-key waiters must block behind the holder"
);
drop(held);
for h in handles {
h.await.expect("waiter");
}
assert_eq!(
*order.lock().expect("order"),
vec![1, 2],
"same-key waiters must acquire in FIFO arrival order"
);
}
#[tokio::test]
async fn distinct_keys_do_not_block() {
let lock = KeyedWriteOrderLock::new();
let g1 = lock.lock_owned(key(b"one")).await;
let g2 = tokio::time::timeout(
std::time::Duration::from_secs(5),
lock.lock_owned(key(b"two")),
)
.await
.expect("a distinct key must not block on a held key");
drop(g2);
drop(g1);
}
}