1use std::{
7 cell::RefCell,
8 collections::{BTreeMap, BTreeSet, HashMap, HashSet},
9};
10
11use super::Saved;
12use crate::{
13 graph::EvalStatus,
14 store::RequestView,
15 types::{ArtifactName, EvalId, RequestStatus, RunStatus},
16};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Source {
21 Executed,
22 Provisional,
23 Reused,
24 Saved,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum Queue {
31 Ready,
33 Jobs,
35 Slot(crate::config::Backend),
37 Joined,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum Activity {
43 Running,
44 Human(Option<crate::types::ReviewerId>),
46 Queued(Queue),
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct Waits {
53 pub artifacts: Vec<crate::types::ArtifactName>,
54 pub root: Option<(crate::types::ArtifactName, crate::types::ArtifactName)>,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub enum NotRun {
59 Dependency(Waits),
61 Budget,
62 Stale,
63 Unreviewed,
64 Absent,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub enum EvalView {
70 Done(Source),
71 Failed {
73 verdict: bool,
74 },
75 InProgress(Activity),
76 WaitingOn(Waits),
78 BlockedBy(Vec<crate::types::ArtifactName>),
80 NotRun(NotRun),
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
85pub enum Rank {
86 Failed,
87 InProgress,
88 Blocked,
89 Waiting,
90 NotRun,
91 Done,
92}
93
94impl EvalView {
95 pub fn rank(&self) -> Rank {
96 match self {
97 Self::Failed { .. } => Rank::Failed,
98 Self::InProgress(_) => Rank::InProgress,
99 Self::BlockedBy(_) => Rank::Blocked,
100 Self::WaitingOn(_) => Rank::Waiting,
101 Self::NotRun(_) => Rank::NotRun,
102 Self::Done(_) => Rank::Done,
103 }
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
109pub enum Busy {
110 Running,
111 Human,
112 Queued,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum Completion {
118 Complete,
120 InProgress(Busy),
121 Waiting {
123 blocked: bool,
124 },
125 Failed {
127 verdict: bool,
128 },
129}
130
131impl Completion {
132 fn rank(self) -> Rank {
134 match self {
135 Self::Failed { .. } => Rank::Failed,
136 Self::InProgress(_) => Rank::InProgress,
137 Self::Waiting { .. } => Rank::Waiting,
138 Self::Complete => Rank::Done,
139 }
140 }
141}
142
143#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct Upstream {
146 pub artifact: crate::types::ArtifactName,
147 pub completion: Completion,
148}
149
150pub(super) struct Index<'a> {
153 pub order: Vec<&'a ArtifactName>,
154 position: HashMap<&'a ArtifactName, usize>,
155 requests: HashMap<&'a EvalId, &'a RequestView>,
156 pub evals: BTreeMap<&'a ArtifactName, Vec<&'a EvalId>>,
157 pub upstream: BTreeMap<&'a EvalId, Vec<&'a ArtifactName>>,
159 gates: BTreeMap<&'a EvalId, Vec<&'a EvalId>>,
160 execution_order: Vec<&'a EvalId>,
161 targets: BTreeMap<&'a EvalId, &'a ArtifactName>,
162 pub peers: BTreeMap<&'a ArtifactName, Vec<&'a ArtifactName>>,
164 pub cyclic: BTreeSet<&'a ArtifactName>,
165}
166
167type Component = crate::store::definitions::Component;
168
169fn members(component: &Component) -> Vec<&ArtifactName> {
170 component.artifacts.value().into_iter().flatten().collect()
171}
172
173impl<'a> Index<'a> {
174 pub fn new(saved: &Saved<'a>) -> Self {
175 let order = saved.artifact_ids();
176 let position: HashMap<_, _> = order
177 .iter()
178 .enumerate()
179 .map(|(index, id)| (*id, index))
180 .collect();
181 let mut requests = HashMap::new();
182 for view in saved.requests {
183 requests.entry(&view.request.eval_id).or_insert(view);
184 }
185 let definitions: HashMap<_, _> = saved
186 .eval_definitions()
187 .iter()
188 .map(|eval| (&eval.id, eval))
189 .collect();
190 let mut targets: HashMap<&ArtifactName, Vec<&EvalId>> = HashMap::new();
192 for eval in saved.eval_definitions() {
193 targets.entry(&eval.target).or_default().push(&eval.id);
194 }
195 for view in saved.requests {
196 let request = &view.request;
197 if !definitions.contains_key(&request.eval_id) {
198 let evals = targets.entry(&request.target).or_default();
199 if !evals.contains(&&request.eval_id) {
200 evals.push(&request.eval_id);
201 }
202 }
203 }
204 let evals: BTreeMap<_, _> = order
205 .iter()
206 .map(|&artifact| {
207 let mut ids = targets.remove(artifact).unwrap_or_default();
208 ids.sort_unstable();
209 (artifact, ids)
210 })
211 .collect();
212 let components = saved.components();
213 let mut peers = BTreeMap::new();
214 let mut cyclic = BTreeSet::new();
215 for component in components {
216 let mut names = members(component);
217 names.sort_unstable();
218 for &artifact in &names {
219 peers.insert(
220 artifact,
221 names.iter().copied().filter(|id| *id != artifact).collect(),
222 );
223 if component.cyclic.value() == Some(&true) {
224 cyclic.insert(artifact);
225 }
226 }
227 }
228 let targets: BTreeMap<_, _> = evals
229 .iter()
230 .flat_map(|(&target, ids)| ids.iter().map(move |&id| (id, target)))
231 .collect();
232 let children: BTreeMap<_, Vec<_>> = saved
233 .artifacts()
234 .map(|(parent, artifact)| {
235 let children = artifact.children.value().into_iter().flatten();
236 (parent, children.map(|(_, child)| child).collect())
237 })
238 .collect();
239 let inputs: BTreeMap<_, Vec<_>> = targets
242 .iter()
243 .map(|(&eval, &target)| {
244 let mut deps: Vec<_> = definitions
245 .get(eval)
246 .and_then(|definition| definition.deps.value())
247 .map(|deps| deps.iter().collect())
248 .unwrap_or_else(|| {
249 requests
250 .get(eval)
251 .map_or(Vec::new(), |view| view.request.deps.iter().collect())
252 });
253 deps.extend(children.get(target).into_iter().flatten().copied());
254 (eval, deps)
255 })
256 .collect();
257 let human = |eval: &EvalId| match requests.get(eval) {
258 Some(view) => view.request.profile.kind() == crate::config::ProfileKind::Human,
259 None => definitions
260 .get(eval)
261 .and_then(|definition| definition.declaration.value())
262 .and_then(|declaration| declaration.profile.value())
263 .is_some_and(|profile| {
264 matches!(profile.known, crate::config::StoredProfile::Human { .. })
265 }),
266 };
267 let schedule = crate::graph::schedule::Schedule::new(
268 targets
269 .iter()
270 .map(|(&eval, &target)| crate::graph::schedule::Input {
271 id: eval,
272 target,
273 deps: inputs[eval].clone(),
274 human: human(eval),
275 }),
276 children
277 .iter()
278 .flat_map(|(&parent, children)| children.iter().map(move |&child| (parent, child))),
279 );
280 let upstream = inputs
281 .into_iter()
282 .map(|(eval, mut deps)| {
283 deps.extend(schedule.gates[eval].iter().map(|gate| targets[gate]));
284 let mut deps: Vec<_> = deps
285 .into_iter()
286 .collect::<BTreeSet<_>>()
287 .into_iter()
288 .collect();
289 deps.sort_by_key(|id| position.get(id).copied().unwrap_or(usize::MAX));
290 (eval, deps)
291 })
292 .collect();
293 let execution_order = schedule.order;
294 Self {
295 order,
296 position,
297 requests,
298 evals,
299 upstream,
300 gates: schedule.gates,
301 execution_order,
302 targets,
303 peers,
304 cyclic,
305 }
306 }
307}
308
309pub(super) struct States<'a> {
311 run: &'a crate::store::Run,
312 #[cfg(any(test, feature = "test-support"))]
313 pub derivations: std::cell::Cell<usize>,
314 pub index: Index<'a>,
315 pub running: bool,
316 views: RefCell<HashMap<&'a EvalId, Option<EvalView>>>,
317 effective: RefCell<HashMap<&'a EvalId, Option<EvalStatus>>>,
318 completions: RefCell<HashMap<&'a ArtifactName, Completion>>,
319 waits: RefCell<HashMap<&'a ArtifactName, Vec<ArtifactName>>>,
320 held: HashMap<&'a EvalId, bool>,
321}
322
323impl<'a> States<'a> {
324 pub fn new(saved: &Saved<'a>) -> Self {
327 let run = &saved.run.run;
328 let mut states = Self {
329 run,
330 #[cfg(any(test, feature = "test-support"))]
331 derivations: std::cell::Cell::new(0),
332 index: Index::new(saved),
333 running: run.status() == RunStatus::Running,
334 views: RefCell::new(HashMap::new()),
335 effective: RefCell::new(HashMap::new()),
336 completions: RefCell::new(HashMap::new()),
337 waits: RefCell::new(HashMap::new()),
338 held: HashMap::new(),
339 };
340 for &eval in &states.index.execution_order {
341 states.effective(eval);
342 let held = states.index.gates[eval].iter().any(|&gate| {
343 matches!(
344 states.effective(gate),
345 EvalStatus::Red | EvalStatus::Blocked | EvalStatus::Error
346 ) || states.held[gate]
347 });
348 states.held.insert(eval, held);
349 }
350 for artifact in states.upstream_first() {
351 states.completion(artifact);
352 states.waits_of(artifact);
353 }
354 states
355 }
356
357 fn upstream_first(&self) -> Vec<&'a ArtifactName> {
360 let mut done = HashSet::new();
361 let mut seen = HashSet::new();
362 let mut ordered = Vec::with_capacity(self.index.order.len());
363 for &start in &self.index.order {
364 if !seen.insert(start) {
365 continue;
366 }
367 let mut stack = vec![(start, self.dependencies(start), 0)];
368 while let Some((artifact, dependencies, next)) = stack.last_mut() {
369 if let Some(&dependency) = dependencies.get(*next) {
370 *next += 1;
371 if seen.insert(dependency) {
372 let dependencies = self.dependencies(dependency);
373 stack.push((dependency, dependencies, 0));
374 }
375 } else {
376 if done.insert(*artifact) {
377 ordered.push(*artifact);
378 }
379 stack.pop();
380 }
381 }
382 }
383 ordered
384 }
385
386 fn dependencies(&self, artifact: &ArtifactName) -> Vec<&'a ArtifactName> {
387 let mut dependencies: Vec<&'a ArtifactName> = Vec::new();
388 for eval in self.evals(artifact) {
389 for &upstream in self.upstream(eval) {
390 if !dependencies.contains(&upstream) {
391 dependencies.push(upstream);
392 }
393 }
394 }
395 dependencies
396 }
397
398 fn key(&self, artifact: &ArtifactName) -> Option<&'a ArtifactName> {
400 self.index
401 .position
402 .get_key_value(artifact)
403 .map(|(&id, _)| id)
404 }
405
406 pub fn request(&self, eval: &EvalId) -> Option<&'a RequestView> {
407 self.index.requests.get(eval).copied()
408 }
409
410 fn outside(&self, eval: &EvalId) -> Option<RequestStatus> {
413 if let Some(status) = self.run.evidence.get(eval) {
414 return Some(*status);
415 }
416 let saved = self.run.validation.snapshot()?.evals.value()?;
417 let status = *saved
418 .iter()
419 .find(|saved| &saved.id == eval)?
420 .status
421 .value()?;
422 matches!(
423 status,
424 RequestStatus::Green | RequestStatus::Red | RequestStatus::Error | RequestStatus::Stale
425 )
426 .then_some(status)
427 }
428
429 pub fn evals(&self, artifact: &ArtifactName) -> &[&'a EvalId] {
430 self.index.evals.get(artifact).map_or(&[], Vec::as_slice)
431 }
432
433 pub fn upstream(&self, eval: &EvalId) -> &[&'a ArtifactName] {
434 self.index.upstream.get(eval).map_or(&[], Vec::as_slice)
435 }
436
437 fn position(&self, artifact: &ArtifactName) -> usize {
438 self.index
439 .position
440 .get(artifact)
441 .copied()
442 .unwrap_or(usize::MAX)
443 }
444
445 pub fn view(&self, eval: &'a EvalId) -> EvalView {
446 if let Some(view) = self.views.borrow().get(eval) {
447 return view.clone().unwrap_or(EvalView::NotRun(NotRun::Unreviewed));
449 }
450 self.views.borrow_mut().insert(eval, None);
451 #[cfg(any(test, feature = "test-support"))]
452 self.derivations.set(self.derivations.get() + 1);
453 let view = self.derive(eval);
454 self.views.borrow_mut().insert(eval, Some(view.clone()));
455 view
456 }
457
458 pub fn effective(&self, eval: &'a EvalId) -> EvalStatus {
461 if let Some(status) = self.effective.borrow().get(eval) {
462 return status.unwrap_or(EvalStatus::Wait);
464 }
465 self.effective.borrow_mut().insert(eval, None);
466 #[cfg(any(test, feature = "test-support"))]
467 self.derivations.set(self.derivations.get() + 1);
468 let status = self.evaluate(eval);
469 self.effective.borrow_mut().insert(eval, Some(status));
470 status
471 }
472
473 fn evaluate(&self, eval: &'a EvalId) -> EvalStatus {
476 let request = self.request(eval).map(|view| &view.request);
477 let status = request
478 .map(|request| request.status())
479 .or_else(|| self.outside(eval));
480 let unmet: Vec<_> = self
481 .index
482 .gates
483 .get(eval)
484 .into_iter()
485 .flatten()
486 .map(|&gate| (gate, self.effective(gate)))
487 .filter(|(_, status)| !status.passes_gate())
488 .collect();
489 if unmet
490 .iter()
491 .any(|&(gate, status)| self.status_blocks(eval, gate, status))
492 {
493 return EvalStatus::Blocked;
494 }
495 if !unmet.is_empty() {
496 return EvalStatus::Wait;
497 }
498 match status {
499 Some(RequestStatus::Deferred) => EvalStatus::Deferred,
500 Some(RequestStatus::Provisional) => EvalStatus::Provisional,
501 Some(RequestStatus::Green) => EvalStatus::Green,
502 Some(RequestStatus::Red) => EvalStatus::Red,
503 Some(RequestStatus::Error) => EvalStatus::Error,
504 Some(RequestStatus::Stale) => EvalStatus::Stale,
505 _ => EvalStatus::Unreviewed,
506 }
507 }
508
509 fn met(&self, artifact: &ArtifactName) -> bool {
510 self.evals(artifact)
511 .iter()
512 .all(|eval| self.effective(eval) == EvalStatus::Green)
513 }
514
515 fn derive(&self, eval: &'a EvalId) -> EvalView {
516 let Some(view) = self.request(eval) else {
517 return match self.outside(eval) {
519 Some(RequestStatus::Green) => EvalView::Done(Source::Saved),
520 Some(RequestStatus::Red) => EvalView::Failed { verdict: true },
521 Some(RequestStatus::Error) => EvalView::Failed { verdict: false },
522 Some(RequestStatus::Stale) => EvalView::NotRun(NotRun::Stale),
523 _ => EvalView::NotRun(NotRun::Absent),
524 };
525 };
526 let request = &view.request;
527 match request.status() {
528 RequestStatus::Provisional => EvalView::Done(Source::Provisional),
529 RequestStatus::Green => EvalView::Done(if crate::query::reused(request) {
530 Source::Reused
531 } else {
532 Source::Executed
533 }),
534 RequestStatus::Red => EvalView::Failed { verdict: true },
535 RequestStatus::Error => EvalView::Failed { verdict: false },
536 RequestStatus::Running => EvalView::InProgress(Activity::Running),
537 RequestStatus::WaitingHuman | RequestStatus::Deferred => EvalView::InProgress(
538 Activity::Human(view.claim.as_ref().map(|claim| claim.reviewer.clone())),
539 ),
540 RequestStatus::BudgetExhausted => EvalView::NotRun(NotRun::Budget),
541 RequestStatus::Stale => EvalView::NotRun(NotRun::Stale),
542 RequestStatus::Queued
543 | RequestStatus::WaitDependency
544 | RequestStatus::Unreviewed
545 | RequestStatus::Blocked => {
546 let unmet = self.unmet(eval);
547 let blocking: Vec<_> = unmet
548 .iter()
549 .filter(|artifact| self.gate_blocks(eval, artifact))
550 .cloned()
551 .collect();
552 if !blocking.is_empty() {
553 return EvalView::BlockedBy(blocking);
554 }
555 if self.run.modes.speculate {
556 return self.ready(view);
557 }
558 if !unmet.is_empty() {
559 let waits = Waits {
560 root: self.root(&unmet),
561 artifacts: unmet,
562 };
563 return if self.running {
564 EvalView::WaitingOn(waits)
565 } else {
566 EvalView::NotRun(NotRun::Dependency(waits))
567 };
568 }
569 self.ready(view)
570 }
571 }
572 }
573
574 fn ready(&self, view: &RequestView) -> EvalView {
576 let request = &view.request;
577 if !self.running || request.status() != RequestStatus::Queued {
578 return EvalView::NotRun(NotRun::Unreviewed);
579 }
580 EvalView::InProgress(Activity::Queued(
581 if let Some(crate::store::QueueReason::Slot { backend }) = request.queue {
582 Queue::Slot(backend)
583 } else if request.execution_id.is_some() {
584 Queue::Joined
585 } else if self.run.jobs.is_some() {
586 Queue::Jobs
587 } else {
588 Queue::Ready
589 },
590 ))
591 }
592
593 fn unmet(&self, eval: &EvalId) -> Vec<ArtifactName> {
595 let mut artifacts: Vec<_> = self
596 .index
597 .gates
598 .get(eval)
599 .into_iter()
600 .flatten()
601 .filter(|&&gate| !self.effective(gate).passes_gate())
602 .map(|gate| self.index.targets[gate])
603 .collect::<BTreeSet<_>>()
604 .into_iter()
605 .collect();
606 artifacts
607 .sort_by_key(|artifact| (self.completion(artifact).rank(), self.position(artifact)));
608 artifacts.into_iter().cloned().collect()
609 }
610
611 fn views_of(&self, artifact: &ArtifactName) -> Vec<EvalView> {
612 self.evals(artifact)
613 .iter()
614 .map(|eval| self.view(eval))
615 .collect()
616 }
617
618 pub fn completion(&self, artifact: &ArtifactName) -> Completion {
621 if let Some(completion) = self.completions.borrow().get(artifact) {
622 return *completion;
623 }
624 let views = self.views_of(artifact);
625 let busy = views
626 .iter()
627 .filter_map(|view| match view {
628 EvalView::InProgress(Activity::Running) => Some(Busy::Running),
629 EvalView::InProgress(Activity::Human(_)) => Some(Busy::Human),
630 EvalView::InProgress(Activity::Queued(_)) => Some(Busy::Queued),
631 _ => None,
632 })
633 .min();
634 let completion = if views
635 .iter()
636 .any(|view| matches!(view, EvalView::Failed { .. }))
637 {
638 Completion::Failed {
640 verdict: !views.contains(&EvalView::Failed { verdict: false }),
641 }
642 } else if let Some(busy) = busy {
643 Completion::InProgress(busy)
644 } else if views.iter().any(|view| !matches!(view, EvalView::Done(_))) || !self.met(artifact)
645 {
646 Completion::Waiting {
647 blocked: self.blocks(artifact)
648 || views
649 .iter()
650 .any(|view| matches!(view, EvalView::BlockedBy(_))),
651 }
652 } else {
653 Completion::Complete
654 };
655 if let Some(id) = self.key(artifact) {
656 self.completions.borrow_mut().insert(id, completion);
657 }
658 completion
659 }
660
661 fn gate_blocks(&self, eval: &EvalId, artifact: &ArtifactName) -> bool {
663 self.index
664 .gates
665 .get(eval)
666 .into_iter()
667 .flatten()
668 .filter(|gate| self.index.targets[*gate] == artifact)
669 .any(|&gate| self.status_blocks(eval, gate, self.effective(gate)))
670 }
671
672 fn status_blocks(&self, eval: &EvalId, gate: &EvalId, status: EvalStatus) -> bool {
675 match status {
676 EvalStatus::Red | EvalStatus::Blocked => true,
677 EvalStatus::Error => {
678 self.run.modes.speculate
679 || self.index.targets.get(gate) == self.index.targets.get(eval)
680 }
681 _ => false,
682 }
683 }
684
685 pub fn held_for_run(&self, eval: &EvalId) -> bool {
688 self.held.get(eval).copied().unwrap_or(false)
689 }
690
691 fn blocks(&self, artifact: &ArtifactName) -> bool {
693 self.evals(artifact).iter().any(|eval| {
694 matches!(self.effective(eval), EvalStatus::Red | EvalStatus::Blocked)
695 || (self.run.modes.speculate && self.effective(eval) == EvalStatus::Error)
696 })
697 }
698
699 fn held(&self, artifact: &ArtifactName, blocked: bool) -> Vec<crate::types::ArtifactName> {
701 let mut x: Vec<crate::types::ArtifactName> = Vec::new();
702 for &eval in self.evals(artifact) {
703 let wanted = if blocked {
704 EvalStatus::Blocked
705 } else {
706 EvalStatus::Wait
707 };
708 if matches!(self.view(eval), EvalView::Done(_)) && self.effective(eval) == wanted {
709 for id in self.unmet(eval) {
710 if (!blocked || self.gate_blocks(eval, &id)) && !x.contains(&id) {
711 x.push(id);
712 }
713 }
714 }
715 }
716 x
717 }
718
719 pub fn waits_of(&self, artifact: &ArtifactName) -> Vec<crate::types::ArtifactName> {
721 if let Some(x) = self.waits.borrow().get(artifact) {
722 return x.clone();
723 }
724 let mut x = self.held(artifact, false);
725 for view in self.views_of(artifact) {
726 if let EvalView::WaitingOn(waits) | EvalView::NotRun(NotRun::Dependency(waits)) = view {
727 for id in waits.artifacts {
728 if !x.contains(&id) {
729 x.push(id);
730 }
731 }
732 }
733 }
734 x.sort_by_key(|id| (self.completion(id).rank(), self.position(id)));
735 if let Some(id) = self.key(artifact) {
736 self.waits.borrow_mut().insert(id, x.clone());
737 }
738 x
739 }
740
741 pub fn blockers_of(&self, artifact: &ArtifactName) -> Vec<crate::types::ArtifactName> {
743 let mut x = self.held(artifact, true);
744 for view in self.views_of(artifact) {
745 if let EvalView::BlockedBy(blockers) = view {
746 for id in blockers {
747 if !x.contains(&id) {
748 x.push(id);
749 }
750 }
751 }
752 }
753 x
754 }
755
756 fn root(
758 &self,
759 x: &[crate::types::ArtifactName],
760 ) -> Option<(crate::types::ArtifactName, crate::types::ArtifactName)> {
761 if x.iter()
762 .any(|id| !matches!(self.completion(id), Completion::Waiting { blocked: false }))
763 {
764 return None;
765 }
766 let via = x.first()?.clone();
767 let cause = self.waits_of(&via).into_iter().next()?;
768 Some((via, cause))
769 }
770
771 pub fn upstream_states(&self, eval: &EvalId) -> Vec<Upstream> {
773 let mut upstream: Vec<_> = self
774 .upstream(eval)
775 .iter()
776 .map(|artifact| Upstream {
777 artifact: (*artifact).clone(),
778 completion: self.completion(artifact),
779 })
780 .collect();
781 upstream.sort_by_key(|upstream| {
782 (
783 upstream.completion.rank(),
784 self.position(&upstream.artifact),
785 )
786 });
787 upstream
788 }
789}