Local cache lock (#5193)

This commit is contained in:
François-Xavier Talbot
2026-01-23 18:35:46 -05:00
committed by GitHub
parent fa421b4b83
commit 2d7e87a4cb
5 changed files with 215 additions and 136 deletions

View File

@@ -1,10 +1,16 @@
use std::fmt::Debug;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use deadpool_redis::PoolError;
use derive_more::{Deref, DerefMut};
use redis::{FromRedisValue, RedisResult, ToRedisArgs};
use tokio::sync::Notify;
use tokio::time::{Duration, timeout};
use tracing::{Instrument, info_span};
use crate::database::models::DatabaseError;
#[derive(Debug, Clone, Deref, DerefMut)]
pub struct InstrumentedPool {
inner: deadpool_redis::Pool,
@@ -91,3 +97,83 @@ impl InstrumentedCmd {
self.inner.query_async(con).instrument(span).await
}
}
pub fn cache() -> (CacheWriter, CacheSubscriber) {
let shared = Arc::new(Shared::new());
(
CacheWriter {
shared: shared.clone(),
},
CacheSubscriber { shared },
)
}
pub struct CacheWriter {
shared: Arc<Shared>,
}
impl CacheWriter {
pub fn write(&self) {
self.shared.make_ready();
}
}
#[derive(Clone)]
pub struct CacheSubscriber {
shared: Arc<Shared>,
}
impl CacheSubscriber {
pub async fn wait_timeout(
self,
duration: Duration,
) -> Result<(), DatabaseError> {
timeout(duration, self.shared.wait()).await.map_err(|_| {
DatabaseError::LocalCacheTimeout {
released: 0,
total: 1,
}
})
}
}
struct Shared {
ready: AtomicBool,
// With this implementation's intrusive linked lists, the waiters are stored inline in the future
// so there's no heap allocation per waiter.
wakers: Notify,
}
impl Shared {
fn new() -> Self {
Self {
ready: AtomicBool::new(false),
wakers: Notify::new(),
}
}
fn make_ready(&self) {
self.ready.store(true, Ordering::Release);
self.wakers.notify_waiters();
}
async fn wait(&self) {
let ready = self.ready.load(Ordering::Acquire);
if ready {
return;
}
let notification = self.wakers.notified();
// Don't need to call `enable` as we use notify_waiters
// Prevent race where the writer set the ready bit and notified waiters between the load and registering the waiter
let ready = self.ready.load(Ordering::SeqCst);
if ready {
return;
}
notification.await;
}
}