use crate::fs::Fs;
use crate::path::PathBuf;
use crate::tree::inner::TreeId;
use crate::{DescriptorTable, GlobalTableId};
use alloc::sync::Arc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use parking_lot::Mutex;
#[cfg(not(feature = "std"))]
use spin::Mutex;
#[derive(Clone, Copy)]
enum Kind {
Table,
BlobFile,
}
struct Output {
kind: Kind,
id: u64,
path: PathBuf,
fs: Arc<dyn Fs>,
}
#[derive(Clone, Default)]
pub struct OutputLedger(Arc<Ledger>);
#[derive(Default)]
struct Ledger {
files: Mutex<Vec<Output>>,
room: Mutex<Option<Arc<super::capacity::Reservation>>>,
}
impl OutputLedger {
pub fn record_table(&self, id: crate::TableId, path: PathBuf, fs: Arc<dyn Fs>) {
self.record(Kind::Table, id, path, fs);
}
pub fn record_blob_file(&self, id: crate::vlog::BlobFileId, path: PathBuf, fs: Arc<dyn Fs>) {
self.record(Kind::BlobFile, id, path, fs);
}
fn record(&self, kind: Kind, id: u64, path: PathBuf, fs: Arc<dyn Fs>) {
self.0.files.lock().push(Output { kind, id, path, fs });
}
pub fn reserve(&self, room: super::capacity::Reservation) {
let replaced = self.0.room.lock().replace(Arc::new(room));
drop(replaced);
}
pub fn release_room(&self) {
let released = self.0.room.lock().take();
drop(released);
}
fn room(&self) -> Option<Arc<super::capacity::Reservation>> {
self.0.room.lock().clone()
}
pub fn try_reserve_more(
&self,
pool: &super::capacity::CapacityPool,
tree: crate::tree::inner::TreeId,
demand: &super::capacity::Demand<'_>,
check: super::capacity::Check,
) -> bool {
match self.room() {
Some(room) => room.try_add(demand, check),
None => pool.would_fit(tree, core::slice::from_ref(demand), check),
}
}
pub fn landed(&self, fs: &dyn Fs, path: &crate::path::Path) -> crate::Result<()> {
let Some(room) = self.room() else {
return Ok(());
};
room.landed(fs, path.parent().unwrap_or(path), fs.metadata(path)?.len)
}
pub fn out_of_room(&self) -> bool {
self.room().is_some_and(|room| room.refused())
}
pub fn installed(&self) {
self.0.files.lock().clear();
}
pub fn keep_uninstalled(&self) -> KeptOutputs {
let files = core::mem::take(&mut *self.0.files.lock());
let room = self.0.room.lock().take();
let hold = room.as_ref().map(|room| {
room.strand(files.iter().try_fold(0u64, |sum, output| {
Some(sum + output.fs.metadata(&output.path).ok()?.len)
}))
});
drop(room);
KeptOutputs { files, hold }
}
pub fn remove_uninstalled(&self, tree_id: TreeId, descriptor_table: Option<&DescriptorTable>) {
let outputs = core::mem::take(&mut *self.0.files.lock());
let room = self.0.room.lock().take();
let stranded = remove_files(outputs, tree_id, descriptor_table);
if let Some(room) = &room {
drop(room.strand(stranded));
}
drop(room);
}
}
pub struct KeptOutputs {
files: Vec<Output>,
hold: Option<super::capacity::QuotaHold>,
}
impl KeptOutputs {
pub fn remove(self, tree_id: TreeId, descriptor_table: Option<&DescriptorTable>) {
let stranded = remove_files(self.files, tree_id, descriptor_table);
if let Some(hold) = self.hold {
hold.release_but(stranded);
}
}
}
fn remove_files(
outputs: Vec<Output>,
tree_id: TreeId,
descriptor_table: Option<&DescriptorTable>,
) -> Option<u64> {
let mut stranded = Some(0u64);
for output in outputs {
let global_id = GlobalTableId::from((tree_id, output.id));
if let Some(descriptors) = descriptor_table {
match output.kind {
Kind::Table => descriptors.remove_for_table(&global_id),
Kind::BlobFile => descriptors.remove_for_blob_file(&global_id),
}
}
match output.fs.remove_file(&output.path) {
Ok(()) => log::debug!(
"Removed uninstalled compaction output {}",
output.path.display(),
),
Err(e) if e.kind() == crate::io::ErrorKind::NotFound => {}
Err(e) => {
log::warn!(
"Could not remove uninstalled compaction output {} ({e}); it stays until \
the next open's orphan sweep",
output.path.display(),
);
stranded = stranded
.zip(output.fs.metadata(&output.path).ok())
.map(|(sum, metadata)| sum + metadata.len);
}
}
}
stranded
}
#[cfg(test)]
mod tests;