use std::sync::atomic::Ordering::{AcqRel, Acquire};
use super::core::WalManager;
use crate::types::Lsn;
impl WalManager {
pub async fn wait_durable(&self, lsn: Lsn) -> crate::Result<()> {
let target = lsn.as_u64();
if self.durable_lsn.load(Acquire) >= target {
return Ok(());
}
loop {
let notified = self.durable_notify.notified();
if self.durable_lsn.load(Acquire) >= target {
return Ok(());
}
match self.commit_lock.try_lock() {
Ok(_guard) => {
if self.durable_lsn.load(Acquire) >= target {
return Ok(());
}
let wal = std::sync::Arc::clone(&self.wal);
let join = tokio::task::spawn_blocking(move || -> crate::Result<u64> {
let mut guard = wal.lock().unwrap_or_else(|p| p.into_inner());
guard.sync().map_err(crate::Error::Wal)?;
Ok(guard.next_lsn().saturating_sub(1))
})
.await;
let outcome = match join {
Ok(Ok(durable_through)) => {
self.durable_lsn.fetch_max(durable_through, AcqRel);
Ok(())
}
Ok(Err(e)) => Err(e),
Err(join_err) => Err(crate::Error::Internal {
detail: format!(
"WAL group-commit fsync task failed to join: {join_err}"
),
}),
};
self.durable_notify.notify_waiters();
return outcome;
}
Err(_) => {
notified.await;
if self.durable_lsn.load(Acquire) >= target {
return Ok(());
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{DatabaseId, TenantId, VShardId};
fn open_wal(dir: &std::path::Path) -> WalManager {
WalManager::open_for_testing(&dir.join("test.wal")).expect("open wal")
}
#[tokio::test]
async fn wait_durable_makes_appended_record_durable() {
let dir = tempfile::tempdir().expect("tempdir");
let wal = open_wal(dir.path());
let lsn = wal
.append_put(
TenantId::new(1),
VShardId::new(0),
DatabaseId::DEFAULT,
b"payload",
)
.expect("append");
wal.wait_durable(lsn).await.expect("wait_durable");
assert!(wal.durable_lsn.load(Acquire) >= lsn.as_u64());
}
#[tokio::test]
async fn wait_durable_fast_path_when_already_durable() {
let dir = tempfile::tempdir().expect("tempdir");
let wal = open_wal(dir.path());
let lsn = wal
.append_put(
TenantId::new(1),
VShardId::new(0),
DatabaseId::DEFAULT,
b"payload",
)
.expect("append");
wal.wait_durable(lsn).await.expect("first");
wal.wait_durable(lsn).await.expect("second");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn concurrent_waiters_coalesce() {
let dir = tempfile::tempdir().expect("tempdir");
let wal = std::sync::Arc::new(open_wal(dir.path()));
let mut lsns = Vec::new();
for _ in 0..16 {
lsns.push(
wal.append_put(
TenantId::new(1),
VShardId::new(0),
DatabaseId::DEFAULT,
b"payload",
)
.expect("append"),
);
}
let max = *lsns.iter().max().expect("nonempty");
let mut handles = Vec::new();
for lsn in lsns {
let wal = std::sync::Arc::clone(&wal);
handles.push(tokio::spawn(async move {
wal.wait_durable(lsn).await.expect("wait_durable");
}));
}
for h in handles {
h.await.expect("join");
}
assert!(wal.durable_lsn.load(Acquire) >= max.as_u64());
}
}