diff options
| author | tv <tv@krebsco.de> | 2026-11-08 20:22:10 +0000 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-08 21:22:30 +0100 |
| commit | 3813a447485f54f8e5beed307dd690c4c7d1584b (patch) | |
| tree | 23f03d8b0a776445346d31dad754acfefbb30624 | |
| parent | b40d3f7ca3f92373eca8dd0683d37183559c78e6 (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.
| -rw-r--r-- | src/access/screen.rs | 37 |
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 |
