use crate::scoped_start::{Started, start_or_run_with};
use crate::side_by_side_threads::{ScopeBody, in_thread_scope};
use std::cell::{Cell, RefCell};
use std::fs::File;
use std::thread::Scope;
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>) {
sync_all_at_once(batch.as_slice(), true);
}
fn sync_all_at_once(batch: &[Pending], background: bool) {
in_thread_scope(SyncAll { batch, background });
}
struct SyncAll<'batch> {
batch: &'batch [Pending],
background: bool,
}
impl<'batch> ScopeBody<'batch> for SyncAll<'batch> {
type Output = ();
fn run<'scope>(self, scope: &'scope Scope<'scope, 'batch>) {
let mut started: Vec<Started<'scope, ()>> = Vec::new();
for pending in self.batch {
started.push(start_or_run_with(scope, (pending, self.background), sync_one_pending));
}
say_once_if_inline(started.as_slice());
}
}
fn sync_one_pending((pending, background): (&Pending, bool)) {
if let Err(error) = pending.handle.sync_all() {
if background {
crate::service_warn!("the background sync of the {} failed: {error}", pending.what);
} else {
crate::service_warn!("the sync of the {} failed: {error}", pending.what);
}
}
if background {
BACKGROUND_SYNCED.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
}
fn say_once_if_inline<T>(started: &[Started<'_, T>]) {
if started.iter().any(Started::ran_inline) {
crate::service_warn!("the system could not start a thread for a sync of the landing's records; it was made where it was asked for");
}
}
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(start_background);
}
fn start_background() -> Sender<Job> {
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.set(true);
INDEX_HARDENED.set(hardens_index);
QUEUED.with_borrow_mut(Vec::clear);
return Deferral { settled: false };
}
fn end() -> Vec<Pending> {
DEFERRING.set(false);
INDEX_HARDENED.set(true);
return QUEUED.take();
}
fn force(batch: Vec<Pending>) {
for pending in &batch {
crate::service_fault::note(format!("sync-waited:{}", pending.what).as_str());
}
sync_all_at_once(batch.as_slice(), false);
}
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());
let handle: File = file.try_clone()?;
let mut queued: Vec<Pending> = QUEUED.take();
queued.push(Pending { handle, what });
QUEUED.set(queued);
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;