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