use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;
use parking_lot::{Condvar, Mutex};
use rocksdb::OptimisticTransactionDB;
use web_time::{SystemTime, UNIX_EPOCH};
use super::TARGET;
use crate::kvs::err::{Error, Result};
const GC_INTERVAL: Duration = Duration::from_secs(60);
pub struct GarbageCollector {
notify: Arc<ShutdownSignal>,
handle: Mutex<Option<thread::JoinHandle<()>>>,
}
struct ShutdownSignal {
flag: AtomicBool,
condvar: Condvar,
mutex: Mutex<()>,
}
impl GarbageCollector {
pub fn new(db: Pin<Arc<OptimisticTransactionDB>>, retention: Duration) -> Result<Self> {
let retention_millis = retention.as_millis().try_into().unwrap_or(u64::MAX);
info!(target: TARGET, "Version garbage collector: enabled (retention={}ms, interval={}s)",
retention_millis,
GC_INTERVAL.as_secs(),
);
let notify = Arc::new(ShutdownSignal {
flag: AtomicBool::new(false),
condvar: Condvar::new(),
mutex: Mutex::new(()),
});
let signal = Arc::clone(¬ify);
let handle = thread::Builder::new()
.name("rocksdb-garbage-collector".to_string())
.spawn(move || {
loop {
let mut guard = signal.mutex.lock();
signal.condvar.wait_for(&mut guard, GC_INTERVAL);
drop(guard);
if signal.flag.load(Ordering::Relaxed) {
break;
}
let now_millis = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time cannot be before epoch")
.as_millis() as u64;
let threshold_millis = now_millis.saturating_sub(retention_millis);
let threshold_ts = threshold_millis << 16;
let ts_bytes = threshold_ts.to_le_bytes();
let Some(cf) = db.cf_handle("default") else {
error!(target: TARGET, "Failed to get default column family handle for GC");
continue;
};
if let Err(err) = db.increase_full_history_ts_low(cf, ts_bytes) {
error!(target: TARGET, "Failed to advance GC watermark: {err}");
} else {
trace!(target: TARGET, "Advanced GC watermark to {threshold_ts} (threshold={}ms ago)", retention_millis);
}
}
})
.map_err(|_| {
Error::Datastore(
"failed to spawn RocksDB garbage collector thread".to_string(),
)
})?;
Ok(Self {
notify,
handle: Mutex::new(Some(handle)),
})
}
pub fn shutdown(&self) -> Result<()> {
self.notify.flag.store(true, Ordering::Relaxed);
self.notify.condvar.notify_one();
if let Some(handle) = self.handle.lock().take() {
let _ = handle.join();
}
Ok(())
}
}