aboutsummaryrefslogtreecommitdiffstats
path: root/src/access
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-08 20:22:10 +0000
committertv <tv@krebsco.de>2026-11-08 21:22:30 +0100
commit3813a447485f54f8e5beed307dd690c4c7d1584b (patch)
tree23f03d8b0a776445346d31dad754acfefbb30624 /src/access
parentb40d3f7ca3f92373eca8dd0683d37183559c78e6 (diff)
screen: read once per batch whether the policy is strict
The profile of bench_screening puts screening at about 21% of the samples, half of it Automerge applying the changes. Of the other half, reading the policy for every change taken is the largest part (3.4%): reaches_signed reads it, and what users sign, to tell whether the change must go in before the next is judged. Read them once, and again only after the document took the accepted ones; a change to the policy or to what users sign is relevant, taken at once. A change touching nothing reaches nothing.
Diffstat (limited to 'src/access')
-rw-r--r--src/access/screen.rs37
1 files changed, 29 insertions, 8 deletions
diff --git a/src/access/screen.rs b/src/access/screen.rs
index 793e8bb..3fbc7ab 100644
--- a/src/access/screen.rs
+++ b/src/access/screen.rs
@@ -386,6 +386,9 @@ pub(crate) fn apply(
let mut unapplied: BTreeSet<Path> = BTreeSet::new();
// What the rules read at the heads judged at, kept while they stay the same.
let mut reads = rules::Reads::default();
+ // The values their users sign, under a strict policy, as the document holds them:
+ // read again once the document takes the accepted (`reaches_signed`).
+ let mut signed: Option<Option<Vec<Vec<String>>>> = None;
for (index, change) in ready.iter().enumerate() {
let hash = change.hash();
let committer = committers[index].as_deref();
@@ -453,6 +456,7 @@ pub(crate) fn apply(
apply_taken(doc, std::mem::take(&mut accepted), log)?;
frontier.clear();
unapplied.clear();
+ signed = None;
}
let mut seen_at: BTreeSet<ChangeHash> = BTreeSet::new();
for dep in change.deps() {
@@ -490,12 +494,16 @@ pub(crate) fn apply(
screened.taken_from.insert(committer.to_owned());
}
// A root too: whether another may be one is read in the document.
- if root || relevant(&paths[index]) || reaches_signed(doc, &paths[index]) {
+ if root
+ || relevant(&paths[index])
+ || reaches_signed(doc, &mut signed, &paths[index])
+ {
// What rules read changed: in before the next is judged.
screened.applied += accepted.len();
apply_taken(doc, std::mem::take(&mut accepted), log)?;
frontier.clear();
unapplied.clear();
+ signed = None;
} else {
frontier.insert(hash, seen_at);
unapplied.extend(paths[index].iter().cloned());
@@ -748,15 +756,28 @@ fn relevant(paths: &BTreeSet<Path>) -> bool {
/// Whether a change touching `paths` changes values their users sign, under a strict
/// policy, at, within or above one (`envelope::reaches_signed`): the rules read the
/// value at a signed place (`prev`) and what holds one, so it goes in before the next
-/// is judged.
-fn reaches_signed(doc: &Document, paths: &BTreeSet<Path>) -> bool {
- if !super::policy::of(doc).strict() {
+/// is judged. Where they are, None if the policy isn't strict, is read into `signed`
+/// once: a read costs more than the rest of screening a change, and what it reads
+/// changes only as the document takes changes (and one changing the policy, or what
+/// users sign, is `relevant`, taken at once).
+fn reaches_signed(
+ doc: &Document,
+ signed: &mut Option<Option<Vec<Vec<String>>>>,
+ paths: &BTreeSet<Path>,
+) -> bool {
+ if paths.is_empty() {
return false;
}
- let signed = super::envelope::signed_paths(doc);
- paths
- .iter()
- .any(|path| super::envelope::reaches_signed(&signed, path))
+ let signed = signed.get_or_insert_with(|| {
+ super::policy::of(doc)
+ .strict()
+ .then(|| super::envelope::signed_paths(doc))
+ });
+ signed.as_ref().is_some_and(|signed| {
+ paths
+ .iter()
+ .any(|path| super::envelope::reaches_signed(signed, path))
+ })
}
/// `candidates` in an order where every change comes after its dependencies among them