use std::{
cell::RefCell,
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
};
use super::Saved;
use crate::{
config::ProfileKind,
graph::EvalStatus,
store::RequestView,
types::{RequestStatus, RunStatus},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Source {
Executed,
Reused,
Derived,
Saved,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Queue {
Jobs,
Slot(String),
Joined,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Activity {
Running,
Human(Option<String>),
Queued(Queue),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Waits {
pub x: Vec<String>,
pub root: Option<(String, String)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NotRun {
Dependency(Waits),
Budget,
Stale,
Unreviewed,
Absent,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EvalView {
Done(Source),
Failed {
verdict: bool,
},
InProgress(Activity),
WaitingOn(Waits),
BlockedBy(Vec<String>),
NotRun(NotRun),
}
impl EvalView {
pub fn rank(&self) -> u8 {
match self {
Self::Failed { .. } => 0,
Self::InProgress(_) => 1,
Self::BlockedBy(_) => 2,
Self::WaitingOn(_) => 3,
Self::NotRun(_) => 4,
Self::Done(_) => 5,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Busy {
Running,
Human,
Queued,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Completion {
Complete,
InProgress(Busy),
Waiting {
blocked: bool,
},
Failed {
verdict: bool,
},
}
impl Completion {
fn rank(self) -> u8 {
match self {
Self::Failed { .. } => 0,
Self::InProgress(_) => 1,
Self::Waiting { .. } => 2,
Self::Complete => 3,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Upstream {
pub artifact: String,
pub completion: Completion,
}
pub(super) struct Index<'a> {
pub order: Vec<&'a str>,
position: HashMap<&'a str, usize>,
requests: HashMap<&'a str, &'a RequestView>,
pub evals: BTreeMap<&'a str, Vec<&'a str>>,
pub upstream: BTreeMap<&'a str, Vec<&'a str>>,
pub dependency: BTreeSet<&'a str>,
pub peers: BTreeMap<&'a str, Vec<&'a str>>,
pub cyclic: BTreeSet<&'a str>,
}
type Component = crate::store::definitions::Component;
fn members(component: &Component) -> Vec<&str> {
component
.artifacts
.value()
.into_iter()
.flatten()
.map(String::as_str)
.collect()
}
impl<'a> Index<'a> {
pub fn new(saved: &Saved<'a>) -> Self {
let order = saved.artifact_ids();
let position: HashMap<_, _> = order
.iter()
.enumerate()
.map(|(index, id)| (*id, index))
.collect();
let mut requests = HashMap::new();
for view in saved.requests {
requests
.entry(view.request.eval_id.as_str())
.or_insert(view);
}
let definitions: HashMap<_, _> = saved
.eval_definitions()
.iter()
.map(|eval| (eval.id.as_str(), eval))
.collect();
let mut targets: HashMap<&str, Vec<&str>> = HashMap::new();
for eval in saved.eval_definitions() {
targets
.entry(eval.target.as_str())
.or_default()
.push(eval.id.as_str());
}
for view in saved.requests {
let request = &view.request;
if !definitions.contains_key(request.eval_id.as_str()) {
let evals = targets.entry(request.target.as_str()).or_default();
if !evals.contains(&request.eval_id.as_str()) {
evals.push(request.eval_id.as_str());
}
}
}
let mut dependency = BTreeSet::new();
let mut evals = BTreeMap::new();
for &artifact in &order {
let mut ids: Vec<(bool, &str)> = targets
.remove(artifact)
.unwrap_or_default()
.into_iter()
.map(|eval| {
let kind = definitions
.get(eval)
.and_then(|eval| eval.declaration.value())
.and_then(|declaration| declaration.profile.value())
.map(|profile| profile.known.kind())
.or_else(|| requests.get(eval).map(|view| view.request.profile.kind()));
(kind == Some(ProfileKind::Dependency), eval)
})
.collect();
ids.sort();
dependency.extend(
ids.iter()
.filter(|(derived, _)| *derived)
.map(|(_, id)| *id),
);
evals.insert(artifact, ids.into_iter().map(|(_, id)| id).collect());
}
let components = saved.components();
let by_id: HashMap<usize, &Component> = components
.iter()
.enumerate()
.map(|(index, component)| (*component.id.value().unwrap_or(&index), component))
.collect();
let mut component_of: HashMap<&str, &Component> = HashMap::new();
let mut peers = BTreeMap::new();
let mut cyclic = BTreeSet::new();
for component in components {
let mut names = members(component);
names.sort_unstable();
for &artifact in &names {
component_of.entry(artifact).or_insert(component);
peers.insert(
artifact,
names.iter().copied().filter(|id| *id != artifact).collect(),
);
if component.cyclic.value() == Some(&true) {
cyclic.insert(artifact);
}
}
}
let mut upstream = BTreeMap::new();
for (&artifact, ids) in &evals {
let component = component_of.get(artifact).copied();
let own = component.map(members).unwrap_or_else(|| vec![artifact]);
let gates: Option<Vec<&str>> = component.map(|component| {
match component.dependencies.value() {
Some(dependencies) => dependencies
.iter()
.filter_map(|id| by_id.get(id))
.flat_map(|component| members(component))
.collect(),
None => saved
.definitions()
.into_iter()
.flat_map(|graph| graph.relations())
.filter(|relation| {
own.contains(&relation.target.as_str())
&& !own.contains(&relation.source.as_str())
})
.flat_map(|relation| {
component_of
.get(relation.source.as_str())
.map(|component| members(component))
.unwrap_or_else(|| vec![relation.source.as_str()])
})
.collect(),
}
});
for &eval in ids {
let request = requests.get(eval).map(|view| &view.request);
let deps: Vec<&str> = if dependency.contains(eval) {
let definition = definitions
.get(eval)
.and_then(|definition| definition.deps.value());
match (definition, request) {
(Some(deps), _) => deps.iter().map(String::as_str).collect(),
(None, Some(request)) => request.deps.iter().map(String::as_str).collect(),
(None, None) => Vec::new(),
}
} else {
match (&gates, request) {
(Some(gates), _) => gates.clone(),
(None, Some(request)) => request.deps.iter().map(String::as_str).collect(),
(None, None) => Vec::new(),
}
};
let mut deps: Vec<&str> = deps
.into_iter()
.filter(|id| *id != artifact)
.filter(|id| dependency.contains(eval) || !own.contains(id))
.collect::<BTreeSet<_>>()
.into_iter()
.collect();
deps.sort_by_key(|id| position.get(id).copied().unwrap_or(usize::MAX));
upstream.insert(eval, deps);
}
}
Self {
order,
position,
requests,
evals,
upstream,
dependency,
peers,
cyclic,
}
}
}
pub(super) struct States<'a> {
run: &'a crate::store::Run,
pub index: Index<'a>,
pub running: bool,
ignore_gates: bool,
views: RefCell<HashMap<&'a str, Option<EvalView>>>,
effective: RefCell<HashMap<&'a str, Option<EvalStatus>>>,
completions: RefCell<HashMap<&'a str, Completion>>,
waits: RefCell<HashMap<&'a str, Vec<String>>>,
}
impl<'a> States<'a> {
pub fn new(saved: &Saved<'a>) -> Self {
let run = &saved.run.run;
let states = Self {
run,
index: Index::new(saved),
running: run.status == RunStatus::Running,
ignore_gates: run.ignore_gates,
views: RefCell::new(HashMap::new()),
effective: RefCell::new(HashMap::new()),
completions: RefCell::new(HashMap::new()),
waits: RefCell::new(HashMap::new()),
};
for artifact in states.upstream_first() {
for &eval in states.evals(artifact) {
states.effective(eval);
}
states.completion(artifact);
states.waits_of(artifact);
}
states
}
fn upstream_first(&self) -> Vec<&'a str> {
let mut done = HashSet::new();
let mut seen = HashSet::new();
let mut ordered = Vec::with_capacity(self.index.order.len());
for &start in &self.index.order {
if !seen.insert(start) {
continue;
}
let mut stack = vec![(start, self.dependencies(start), 0)];
while let Some((artifact, dependencies, next)) = stack.last_mut() {
if let Some(&dependency) = dependencies.get(*next) {
*next += 1;
if seen.insert(dependency) {
let dependencies = self.dependencies(dependency);
stack.push((dependency, dependencies, 0));
}
} else {
if done.insert(*artifact) {
ordered.push(*artifact);
}
stack.pop();
}
}
}
ordered
}
fn dependencies(&self, artifact: &str) -> Vec<&'a str> {
let mut dependencies: Vec<&'a str> = Vec::new();
for eval in self.evals(artifact) {
for &upstream in self.upstream(eval) {
if !dependencies.contains(&upstream) {
dependencies.push(upstream);
}
}
}
dependencies
}
fn key(&self, artifact: &str) -> Option<&'a str> {
self.index
.position
.get_key_value(artifact)
.map(|(&id, _)| id)
}
pub fn request(&self, eval: &str) -> Option<&'a RequestView> {
self.index.requests.get(eval).copied()
}
fn outside(&self, eval: &str) -> Option<RequestStatus> {
if let Some(status) = self.run.evidence.get(eval) {
return Some(*status);
}
let saved = self.run.validation["evals"].as_array()?;
let status = saved.iter().find(|saved| saved["id"] == eval)?["status"].as_str()?;
let status: RequestStatus = status.parse().ok()?;
matches!(
status,
RequestStatus::Green | RequestStatus::Red | RequestStatus::Error | RequestStatus::Stale
)
.then_some(status)
}
pub fn evals(&self, artifact: &str) -> &[&'a str] {
self.index.evals.get(artifact).map_or(&[], Vec::as_slice)
}
pub fn upstream(&self, eval: &str) -> &[&'a str] {
self.index.upstream.get(eval).map_or(&[], Vec::as_slice)
}
fn position(&self, artifact: &str) -> usize {
self.index
.position
.get(artifact)
.copied()
.unwrap_or(usize::MAX)
}
pub fn view(&self, eval: &'a str) -> EvalView {
if let Some(view) = self.views.borrow().get(eval) {
return view.clone().unwrap_or(EvalView::NotRun(NotRun::Unreviewed));
}
self.views.borrow_mut().insert(eval, None);
let view = self.derive(eval);
self.views.borrow_mut().insert(eval, Some(view.clone()));
view
}
pub fn effective(&self, eval: &'a str) -> EvalStatus {
if let Some(status) = self.effective.borrow().get(eval) {
return status.unwrap_or(EvalStatus::Wait);
}
self.effective.borrow_mut().insert(eval, None);
let status = self.evaluate(eval);
self.effective.borrow_mut().insert(eval, Some(status));
status
}
fn evaluate(&self, eval: &'a str) -> EvalStatus {
let request = self.request(eval).map(|view| &view.request);
let status = request
.map(|request| request.status)
.or_else(|| self.outside(eval));
let derived = self.index.dependency.contains(eval);
if derived && status == Some(RequestStatus::Error) {
return EvalStatus::Error;
}
let mut unmet = Vec::new();
if derived || !self.ignore_gates {
for artifact in self.upstream(eval) {
for &gate in self.evals(artifact) {
let status = self.effective(gate);
if status != EvalStatus::Green {
unmet.push(status);
}
}
}
}
if unmet
.iter()
.any(|status| matches!(status, EvalStatus::Red | EvalStatus::Blocked))
{
return EvalStatus::Blocked;
}
if !unmet.is_empty() {
return EvalStatus::Wait;
}
if derived {
return EvalStatus::Green;
}
match status {
Some(RequestStatus::Green) => EvalStatus::Green,
Some(RequestStatus::Red) => EvalStatus::Red,
Some(RequestStatus::Error) => EvalStatus::Error,
Some(RequestStatus::Stale) => EvalStatus::Stale,
_ => EvalStatus::Unreviewed,
}
}
fn met(&self, artifact: &str) -> bool {
self.evals(artifact)
.iter()
.all(|eval| self.effective(eval) == EvalStatus::Green)
}
fn derive(&self, eval: &'a str) -> EvalView {
let Some(view) = self.request(eval) else {
return match self.outside(eval) {
Some(RequestStatus::Green) => EvalView::Done(Source::Saved),
Some(RequestStatus::Red) => EvalView::Failed { verdict: true },
Some(RequestStatus::Error) => EvalView::Failed { verdict: false },
Some(RequestStatus::Stale) => EvalView::NotRun(NotRun::Stale),
_ => EvalView::NotRun(NotRun::Absent),
};
};
let request = &view.request;
let dependency = request.profile.kind() == ProfileKind::Dependency;
match request.status {
RequestStatus::Green => EvalView::Done(if dependency {
Source::Derived
} else if crate::query::reused(request) {
Source::Reused
} else {
Source::Executed
}),
RequestStatus::Red => EvalView::Failed { verdict: true },
RequestStatus::Error => EvalView::Failed { verdict: false },
RequestStatus::Running => EvalView::InProgress(Activity::Running),
RequestStatus::WaitingHuman => EvalView::InProgress(Activity::Human(
view.claim.as_ref().map(|claim| claim.reviewer.clone()),
)),
RequestStatus::BudgetExhausted => EvalView::NotRun(NotRun::Budget),
RequestStatus::Stale => EvalView::NotRun(NotRun::Stale),
RequestStatus::Queued
| RequestStatus::WaitDependency
| RequestStatus::Unreviewed
| RequestStatus::Blocked => {
if self.ignore_gates && !dependency {
return self.ready(view);
}
let unmet = self.unmet(self.upstream(eval));
let blocking: Vec<_> = unmet
.iter()
.filter(|artifact| self.blocks(artifact))
.cloned()
.collect();
if !blocking.is_empty() {
return EvalView::BlockedBy(blocking);
}
if !unmet.is_empty() {
let waits = Waits {
root: self.root(&unmet),
x: unmet,
};
return if self.running {
EvalView::WaitingOn(waits)
} else {
EvalView::NotRun(NotRun::Dependency(waits))
};
}
if dependency {
return EvalView::Done(Source::Derived);
}
self.ready(view)
}
}
}
fn ready(&self, view: &RequestView) -> EvalView {
let request = &view.request;
if !self.running || request.status != RequestStatus::Queued {
return EvalView::NotRun(NotRun::Unreviewed);
}
let slot = request
.blocked_reason
.as_deref()
.and_then(|reason| reason.strip_prefix("Waiting for a free "))
.and_then(|rest| rest.split_once(" slot"))
.map(|(backend, _)| backend.to_owned());
EvalView::InProgress(Activity::Queued(if let Some(backend) = slot {
Queue::Slot(backend)
} else if request.execution_id.is_some() {
Queue::Joined
} else {
Queue::Jobs
}))
}
pub fn unmet(&self, artifacts: &[&'a str]) -> Vec<String> {
let mut unmet: Vec<_> = artifacts
.iter()
.filter(|artifact| !self.met(artifact))
.map(|artifact| (*artifact, self.completion(artifact)))
.collect();
unmet.sort_by_key(|(artifact, completion)| (completion.rank(), self.position(artifact)));
unmet
.into_iter()
.map(|(artifact, _)| artifact.to_owned())
.collect()
}
fn views_of(&self, artifact: &str) -> Vec<EvalView> {
self.evals(artifact)
.iter()
.map(|eval| self.view(eval))
.collect()
}
pub fn completion(&self, artifact: &str) -> Completion {
if let Some(completion) = self.completions.borrow().get(artifact) {
return *completion;
}
let views = self.views_of(artifact);
let busy = views
.iter()
.filter_map(|view| match view {
EvalView::InProgress(Activity::Running) => Some(Busy::Running),
EvalView::InProgress(Activity::Human(_)) => Some(Busy::Human),
EvalView::InProgress(Activity::Queued(_)) => Some(Busy::Queued),
_ => None,
})
.min();
let completion = if views
.iter()
.any(|view| matches!(view, EvalView::Failed { .. }))
{
Completion::Failed {
verdict: !views.contains(&EvalView::Failed { verdict: false }),
}
} else if let Some(busy) = busy {
Completion::InProgress(busy)
} else if views.iter().any(|view| !matches!(view, EvalView::Done(_))) || !self.met(artifact)
{
Completion::Waiting {
blocked: self.blocks(artifact)
|| views
.iter()
.any(|view| matches!(view, EvalView::BlockedBy(_))),
}
} else {
Completion::Complete
};
if let Some(id) = self.key(artifact) {
self.completions.borrow_mut().insert(id, completion);
}
completion
}
fn blocks(&self, artifact: &str) -> bool {
self.evals(artifact)
.iter()
.any(|eval| matches!(self.effective(eval), EvalStatus::Red | EvalStatus::Blocked))
}
fn held(&self, artifact: &str, blocked: bool) -> Vec<String> {
let mut x: Vec<String> = Vec::new();
for &eval in self.evals(artifact) {
let wanted = if blocked {
EvalStatus::Blocked
} else {
EvalStatus::Wait
};
if matches!(self.view(eval), EvalView::Done(_)) && self.effective(eval) == wanted {
for id in self.unmet(self.upstream(eval)) {
if (!blocked || self.blocks(&id)) && !x.contains(&id) {
x.push(id);
}
}
}
}
x
}
pub fn waits_of(&self, artifact: &str) -> Vec<String> {
if let Some(x) = self.waits.borrow().get(artifact) {
return x.clone();
}
let mut x = self.held(artifact, false);
for view in self.views_of(artifact) {
if let EvalView::WaitingOn(waits) | EvalView::NotRun(NotRun::Dependency(waits)) = view {
for id in waits.x {
if !x.contains(&id) {
x.push(id);
}
}
}
}
x.sort_by_key(|id| (self.completion(id).rank(), self.position(id)));
if let Some(id) = self.key(artifact) {
self.waits.borrow_mut().insert(id, x.clone());
}
x
}
pub fn blockers_of(&self, artifact: &str) -> Vec<String> {
let mut x = self.held(artifact, true);
for view in self.views_of(artifact) {
if let EvalView::BlockedBy(blockers) = view {
for id in blockers {
if !x.contains(&id) {
x.push(id);
}
}
}
}
x
}
fn root(&self, x: &[String]) -> Option<(String, String)> {
if x.iter()
.any(|id| !matches!(self.completion(id), Completion::Waiting { blocked: false }))
{
return None;
}
let via = x.first()?.clone();
let cause = self.waits_of(&via).into_iter().next()?;
Some((via, cause))
}
pub fn upstream_states(&self, eval: &str) -> Vec<Upstream> {
let mut upstream: Vec<_> = self
.upstream(eval)
.iter()
.map(|artifact| Upstream {
artifact: (*artifact).to_owned(),
completion: self.completion(artifact),
})
.collect();
upstream.sort_by_key(|upstream| {
(
upstream.completion.rank(),
self.position(&upstream.artifact),
)
});
upstream
}
}