Skip to main content

git_policy_cli/worktree/
worktree_trash_entries.rs

1//! What:
2//!  The removal of the entries a snapshot's leave-out moved into its trash, side by side.
3//! Why:
4//!  The entries are separate directories that share only the held trash handle, each opened by its own name and removed only
5//!  when it is the recorded object, so they are independent items: a reason for one entry never stops the others. They run
6//!  within the budget the host allows (`parallel_budget.rs`), which inside the commit service's control group is the two
7//!  threads and the memory that unit is given, and the reasons come back in entry order (`parallel_runner.rs`).
8//!
9//! In TS you'd write (pseudocode):
10//! ```ts
11//! const left = await removeEntries({ inside, trash, manifest, host, remove }); // one reason per entry that stays
12//! ```
13
14use crate::parallel_budget::{BudgetRequest, HostResources, worker_budget};
15use crate::parallel_runner::{Event, OrderedRun, run_ordered};
16use crate::worktree_trash_manifest::{TrashManifest, entry_name};
17use crate::worktree_trash_remove::Removal;
18use std::fs::File;
19use std::path::Path;
20
21/// The peak resident growth of removing one flat directory of a million names (`remove_tree_beneath`, names of 42 characters,
22///  a fresh process, a throwaway directory on btrfs, one run): 97,320 KiB, which is 99,655,680 bytes. Runs of 100,000, 200,000
23///  (twice) and 400,000 names grew by 131, 113, 113 and 104 bytes a name, so the figure per name falls as the count rises and
24///  the one-million run is the case the estimate is for.
25const MEASURED_MILLION_NAME_PEAK: u64 = 99_655_680;
26
27/// The bytes the removal of one trash entry holds at its peak: `MEASURED_MILLION_NAME_PEAK` rounded up to a power of two. The
28///  listing of a directory is held whole while its entries are removed. The coordinator's pick under the owner's delegation,
29///  open to the owner's veto (`doc/planning/cli-git-shipped-limits-audit.md`, "Parallelism caps and memory estimates").
30const ENTRY_MEMORY: u64 = MEASURED_MILLION_NAME_PEAK.next_power_of_two();
31
32/// The removal of one entry beneath the trash handle, replaceable so a control can watch or delay it.
33pub type RemoveOne<'a> = dyn Fn(&Path, &File, &std::ffi::OsStr, Option<crate::fs_beneath::ObjectId>) -> Result<crate::worktree_trash_remove::Removal, String> + Sync + 'a;
34
35/// What:
36///  What the removal of a trash's entries is told.
37/// Why:
38///   One value names the inputs: where the trash is, its manifest, the host the budget is derived from, and the removal of one entry.
39///
40/// In TS you'd write (pseudocode):
41/// ```ts
42/// type EntryRemoval = { inside: string; trash: Dir; manifest: TrashManifest; host: HostResources; remove: RemoveOne };
43/// ```
44pub struct EntryRemoval<'a> {
45    /// The path of the trash directory, for messages.
46    pub inside: &'a Path,
47    /// The trash directory, held open; every entry is opened by name below it.
48    pub trash: &'a File,
49    /// What the leave-out recorded.
50    pub manifest: &'a TrashManifest,
51    /// The CPUs and memory the budget is derived from.
52    pub host: &'a HostResources,
53    /// The removal of one entry.
54    pub remove: &'a RemoveOne<'a>,
55}
56
57/// What:
58///  Remove every entry the manifest records, side by side, and return the reason each entry that stays gives, in entry order.
59/// Why:
60///   The entries are separate directories moved into the trash, each removed only when it is the recorded object, and share no
61///  state but the held trash handle, which opens each by its own name. A reason for one never stops the others.
62///
63/// In TS you'd write (pseudocode):
64/// ```ts
65/// const left = await removeEntries({ inside, trash, manifest, host, remove }); // one reason per entry that stays
66/// ```
67pub fn remove_entries(request: &EntryRemoval<'_>) -> Vec<String> {
68    let EntryRemoval { inside, trash, manifest, host, remove } = *request;
69    let indices: Vec<usize> = (0..manifest.entries.len()).collect();
70    let lanes: Vec<Vec<usize>> = indices.iter().map(|index: &usize| { return vec![*index]; }).collect();
71    let workers: usize = worker_budget(&BudgetRequest { items: indices.len(), per_item_memory: ENTRY_MEMORY, ceiling: None, cap: None, host });
72    let mut left: Vec<String> = Vec::new();
73    run_ordered(OrderedRun {
74        items: indices.as_slice(),
75        lanes: lanes.as_slice(),
76        workers,
77        work: &|_slot: usize, index: &usize, _say: &mut dyn FnMut(&str)| { return remove(inside, trash, std::ffi::OsStr::new(entry_name(*index).as_str()), Some(manifest.entries[*index])); },
78        on_event: &mut |event: Event<'_, Result<Removal, String>>| {
79            if let Event::End(_slot, Err(reason)) = event {
80                left.push(reason);
81            }
82        },
83    });
84    return left;
85}
86
87/// The controls for the entries of a trash removed side by side stay out of the release executable.
88#[cfg(test)]
89#[path = "worktree_trash_entries_tests.rs"]
90mod tests;