use std::cell::{Cell, RefCell};
use std::fs::File;
use std::sync::OnceLock;
use std::sync::mpsc::{Receiver, Sender, channel};
struct Pending {
handle: File,
what: &'static str,
}
enum Job {
Batch(Vec<Pending>),
Barrier(Sender<()>),
}
thread_local! {
static DEFERRING: Cell<bool> = const { Cell::new(false) };
static INDEX_HARDENED: Cell<bool> = const { Cell::new(true) };
static QUEUED: RefCell<Vec<Pending>> = const { RefCell::new(Vec::new()) };
}
static BACKGROUND: OnceLock<Sender<Job>> = OnceLock::new();
pub static BACKGROUND_SYNCED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
fn sync_batch(batch: Vec<Pending>) {
std::thread::scope(|scope| {
for pending in &batch {
scope.spawn(move || {
if let Err(error) = pending.handle.sync_all() {
crate::service_warn!("the background sync of the {} failed: {error}", pending.what);
}
BACKGROUND_SYNCED.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
});
}
});
}
fn background(jobs: &Receiver<Job>) {
while let Ok(job) = jobs.recv() {
match job {
Job::Batch(batch) => sync_batch(batch),
Job::Barrier(done) => {
let _answered = done.send(());
}
}
}
}
fn sender() -> &'static Sender<Job> {
return BACKGROUND.get_or_init(|| {
let (sender, jobs) = channel::<Job>();
let started = std::thread::Builder::new().name(String::from("cli-git-durable")).spawn(move || { background(&jobs); });
if let Err(error) = started {
crate::service_warn!("the thread that makes the landing's syncs could not start ({error}); they are made where they are asked for");
}
return sender;
});
}
pub fn drain() {
let (done, answered) = channel::<()>();
if sender().send(Job::Barrier(done)).is_ok() {
let _waited = answered.recv();
}
}
pub struct Deferral {
settled: bool,
}
pub fn begin(hardens_index: bool) -> Deferral {
DEFERRING.with(|flag| { flag.set(true); });
INDEX_HARDENED.with(|flag| { flag.set(hardens_index); });
QUEUED.with(|queue| { queue.borrow_mut().clear(); });
return Deferral { settled: false };
}
fn end() -> Vec<Pending> {
DEFERRING.with(|flag| { flag.set(false); });
INDEX_HARDENED.with(|flag| { flag.set(true); });
return QUEUED.with(|queue| { return std::mem::take(&mut *queue.borrow_mut()); });
}
fn force(batch: Vec<Pending>) {
for pending in &batch {
crate::service_fault::note(format!("sync-waited:{}", pending.what).as_str());
}
std::thread::scope(|scope| {
for pending in &batch {
scope.spawn(move || {
if let Err(error) = pending.handle.sync_all() {
crate::service_warn!("the sync of the {} failed: {error}", pending.what);
}
});
}
});
}
impl Deferral {
pub fn settle(mut self, published: bool) {
self.settled = true;
let batch: Vec<Pending> = end();
if batch.is_empty() {
return;
}
if published {
if let Err(rejected) = sender().send(Job::Batch(batch)) {
if let Job::Batch(returned) = rejected.0 {
force(returned);
}
}
} else {
force(batch);
}
}
}
impl Drop for Deferral {
fn drop(&mut self) {
if !self.settled {
force(end());
}
}
}
pub fn flush_file(file: &File, what: &'static str) -> std::io::Result<()> {
if DEFERRING.with(Cell::get) {
crate::service_fault::note(format!("sync-queued:{what}").as_str());
QUEUED.with(|queue| -> std::io::Result<()> {
queue.borrow_mut().push(Pending { handle: file.try_clone()?, what });
return Ok(());
})?;
return Ok(());
}
let synced: std::io::Result<()> = file.sync_all();
crate::service_fault::note(format!("sync-waited:{what}").as_str());
return synced;
}
pub fn flush_directory(directory: &std::path::Path, what: &'static str) -> std::io::Result<()> {
return flush_file(&File::open(directory)?, what);
}
pub fn flush_index(file: &File, what: &'static str) -> std::io::Result<()> {
if INDEX_HARDENED.with(Cell::get) {
let synced: std::io::Result<()> = file.sync_all();
crate::service_fault::note(format!("sync-waited:{what}").as_str());
return synced;
}
return Ok(());
}
pub fn index_is_hardened() -> bool {
return INDEX_HARDENED.with(Cell::get);
}
#[cfg(test)]
#[path = "service_durable_tests.rs"]
mod tests;