Skip to main content

somatize_core/
execution.rs

1//! The engine: walking a [`Plan`] and executing what it says.
2//!
3//! Walking is domain logic, so it lives here and not in the bindings; Python
4//! only supplies the implementations. The engine never looks at the graph —
5//! every plan step carries where its input comes from.
6//!
7//! When one thing reaches a node it receives that thing; when several do, a
8//! [`Value::Map`] keyed by whoever produced each. Aggregating them is the
9//! receiving node's job.
10//!
11//! Keys travel in a table **beside** `produced`, never inside a [`Value`], so
12//! the [`Node`](crate::Node) contract does not change. Nothing is named without
13//! both [`Executor::remembering`] (declared, travels) and [`Executor::keeping`]
14//! (injected, does not).
15
16use crate::{
17    Cargo, Catalog, Ctx, Device, Fact, Host, Keeper, Kept, Key, Keys, Memory, NodeError, NodeId,
18    Outcome, Placement, Plan, Transport, TransportError, Value, Watcher,
19};
20use std::collections::{HashMap, HashSet};
21use std::fmt;
22use std::time::Instant;
23
24/// What the engine writes beside a value it keeps. Public because a store
25/// outlives the process that wrote to it and readers need these strings.
26pub const NODE: &str = "node";
27
28/// Which version of the code produced it, written beside the value rather than
29/// mixed into the name.
30pub const FINGERPRINT: &str = "fingerprint";
31
32/// What the graph was fed, by the name its content has. Only a
33/// [`Keeper`](crate::Keeper) can hash a [`Value`], and a key does not run
34/// backwards, so it is written now or never. Set on [`run`](Executor::run) and
35/// never on [`resume`](Executor::resume): a slice is not handed the graph's
36/// input.
37pub const INPUT: &str = "input";
38
39/// The words the engine writes itself, so a layer that refuses a stamp can ask
40/// which ones are taken.
41pub const OURS: [&str; 3] = [NODE, FINGERPRINT, INPUT];
42
43/// Executes plans. A type and not a bare function because executing needs
44/// context: the store, the placement and the transports.
45pub struct Executor<'a> {
46    catalog: &'a Catalog,
47    placement: Option<&'a Placement>,
48    /// What is remembered about each node. **Declared**, like the placement: it
49    /// belongs to whoever wrote the graph, and it travels.
50    memory: Option<&'a Memory>,
51    /// Who hashes and where what is named ends up. **Injected**, like the
52    /// transports: it belongs to whoever runs, and it does not travel.
53    keeper: Option<&'a dyn Keeper>,
54    /// Which host it knows how to reach, and by what route. A list because
55    /// there are two or three of them.
56    transports: Vec<(Host, &'a dyn Transport)>,
57    /// Who is told what happened. Injected, so it does not travel.
58    watcher: Option<&'a dyn Watcher>,
59    /// Where this walk's timeline starts, so a fact can say **when** and not
60    /// only how long. One number for the whole walk, hence a field.
61    since: Option<Instant>,
62    /// What else to write beside everything this run keeps. **Injected**, like
63    /// the keeper, and opaque: see [`stamping`](Self::stamping).
64    stamp: Vec<(String, String)>,
65    /// The name of what the graph was fed, worked out once per
66    /// [`run`](Self::run) rather than at every kept node.
67    input: Option<Key>,
68}
69
70impl<'a> Executor<'a> {
71    /// An executor over this catalog, with nothing else said yet.
72    pub fn new(catalog: &'a Catalog) -> Self {
73        Self {
74            catalog,
75            placement: None,
76            memory: None,
77            keeper: None,
78            transports: Vec::new(),
79            watcher: None,
80            since: None,
81            stamp: Vec::new(),
82            input: None,
83        }
84    }
85
86    /// The same executor, knowing where each node runs. Without this every
87    /// `ctx.device` is `None`, which means "wherever it lands".
88    pub fn placed(mut self, placement: &'a Placement) -> Self {
89        self.placement = Some(placement);
90        self
91    }
92
93    /// The same executor, knowing what is remembered about each node. Declared,
94    /// so it **travels** with a slice — its own call and not half of
95    /// [`keeping`](Self::keeping), which does not.
96    pub fn remembering(mut self, memory: &'a Memory) -> Self {
97        self.memory = Some(memory);
98        self
99    }
100
101    /// The same executor, with somewhere to keep what it names. Injected.
102    /// Without it, or without [`remembering`](Self::remembering), no key is
103    /// even computed.
104    pub fn keeping(mut self, keeper: &'a dyn Keeper) -> Self {
105        self.keeper = Some(keeper);
106        self
107    }
108
109    /// The same executor, knowing how to reach a host. Called once per host; a
110    /// name nobody resolves is [`RunError::NoTransport`], not a slice executed
111    /// here just in case.
112    pub fn reaching(mut self, host: impl Into<Host>, transport: &'a dyn Transport) -> Self {
113        self.transports.push((host.into(), transport));
114        self
115    }
116
117    /// The same executor, telling this one what it sees. Injected, so a slice
118    /// sent away is watched over there and comes back attributed.
119    pub fn watching(mut self, watcher: &'a dyn Watcher) -> Self {
120        self.watcher = Some(watcher);
121        self
122    }
123
124    /// The same executor, writing this beside everything it keeps.
125    ///
126    /// Opaque text the core passes through untouched: an environment, a commit,
127    /// a run are facts about the world outside a graph. Injected, so a slice
128    /// sent away is stamped by the engine over there.
129    pub fn stamping(mut self, stamp: impl IntoIterator<Item = (String, String)>) -> Self {
130        self.stamp = stamp.into_iter().collect();
131        self
132    }
133
134    /// Hands over one fact, if anybody is listening. The closure is why a run
135    /// nobody watches pays a branch and not an allocation.
136    fn saw(&self, fact: impl FnOnce() -> Fact) {
137        if let Some(watcher) = self.watcher {
138            watcher.saw(&fact());
139        }
140    }
141
142    /// Executes the plan and returns what it produced; the first failure stops
143    /// it. The only place a run is said to end — [`resume`](Self::resume) says
144    /// nothing of the sort, because a slice is not a `forward`.
145    pub fn run(&self, plan: &Plan, input: Value) -> Result<Value, RunError> {
146        let began = Instant::now();
147        // `None` when there is no keeper, or when the input cannot leave this
148        // process — the same absence that leaves everything under it nameless.
149        let named = self.keeper.and_then(|keeper| keeper.key_of(&input));
150        let walking = self.since(began).fed(named);
151        let answer = walking.running(plan, input);
152        match &answer {
153            Ok(_) => walking.saw(|| Fact::Finished {
154                took: began.elapsed(),
155            }),
156            Err(why) => walking.saw(|| Fact::Broke {
157                why: why.to_string(),
158            }),
159        }
160        answer
161    }
162
163    /// The same executor with a timeline of its own, for one walk. A copy
164    /// because [`run`](Self::run) takes `&self`: an executor is shared.
165    fn since(&self, began: Instant) -> Self {
166        Self {
167            catalog: self.catalog,
168            placement: self.placement,
169            memory: self.memory,
170            keeper: self.keeper,
171            transports: self.transports.clone(),
172            watcher: self.watcher,
173            since: Some(began),
174            stamp: self.stamp.clone(),
175            // Not carried over: `run` works it out and sets it, and `resume`
176            // deliberately leaves it empty. A slice's input is not a graph's.
177            input: None,
178        }
179    }
180
181    /// The same walk, knowing what the graph was fed.
182    fn fed(mut self, input: Option<Key>) -> Self {
183        self.input = input;
184        self
185    }
186
187    /// How long this walk has been going, or zero if nobody started a clock.
188    fn so_far(&self) -> std::time::Duration {
189        self.since.map(|began| began.elapsed()).unwrap_or_default()
190    }
191
192    /// The walk itself, so that the two terminal facts wrap one thing and not
193    /// every `return` in it.
194    fn running(&self, plan: &Plan, input: Value) -> Result<Value, RunError> {
195        let mut produced: HashMap<NodeId, Value> = HashMap::new();
196        // The names first, and then what will not be needed because of them.
197        let (mut keys, unneeded) = self.foreseen(plan, &input);
198        let last = self.walk(plan, &input, &mut produced, &mut keys, &unneeded)?;
199
200        // A graph's output is that of its leaves: one leaf gives that value,
201        // several a map keyed by each, so a diamond comes back round.
202        let leaves = terminals(plan);
203        Ok(match leaves.as_slice() {
204            [] | [_] => last,
205            many => Value::map(
206                many.iter()
207                    .map(|id| {
208                        let value = produced
209                            .get(id)
210                            .cloned()
211                            .expect("the walk executed every step of the plan");
212                        (id.to_string(), value)
213                    })
214                    .collect::<Vec<_>>(),
215            ),
216        })
217    }
218
219    /// Executes a slice that already knows what came before: what a worker does
220    /// on receiving one. `known` and `named` are fed in as if this run had
221    /// produced them; neither comes back, and both are ordered by id because
222    /// this crosses a process boundary.
223    pub fn resume(
224        &self,
225        plan: &Plan,
226        input: Value,
227        known: Vec<(NodeId, Value)>,
228        named: Vec<(NodeId, Keys)>,
229    ) -> Result<Outcome, RunError> {
230        // A slice counts from its own start: an offset into a slice is a fact
231        // about the slice, and two wall clocks would not have composed.
232        let walking = self.since(Instant::now());
233        let mut produced: HashMap<NodeId, Value> = known.into_iter().collect();
234        let mut keys: HashMap<NodeId, Keys> = named.into_iter().collect();
235        let brought: Vec<NodeId> = produced.keys().cloned().collect();
236        let named: Vec<NodeId> = keys.keys().cloned().collect();
237
238        // A slice is not pruned: only the sender knows which of what it
239        // produces is read, and it already left out what it did not want.
240        let last = walking.walk(plan, &input, &mut produced, &mut keys, &HashSet::new())?;
241
242        produced.retain(|id, _| !brought.contains(id));
243        keys.retain(|id, _| !named.contains(id));
244        Ok(Outcome {
245            last,
246            produced: sorted(produced),
247            keys: sorted(keys),
248        })
249    }
250
251    /// Executes a plan, noting what each node produces, and returns the output
252    /// of its last step.
253    fn walk(
254        &self,
255        plan: &Plan,
256        graph_input: &Value,
257        produced: &mut HashMap<NodeId, Value>,
258        keys: &mut HashMap<NodeId, Keys>,
259        unneeded: &HashSet<NodeId>,
260    ) -> Result<Value, RunError> {
261        match plan {
262            Plan::Empty => Ok(graph_input.clone()),
263            // Nothing reads what this makes that is not already kept. Its name
264            // is in `keys` regardless, so whoever hit downstream still hits.
265            Plan::Execute { node, .. } if unneeded.contains(node) => {
266                self.saw(|| Fact::Spared { node: node.clone() });
267                Ok(Value::Null)
268            }
269            Plan::Execute { node, from } if self.maps(node) => {
270                self.over_items(node, from, graph_input, produced, keys)
271            }
272            Plan::Execute { node, from } => {
273                // Naming the root is the one place a value is hashed by
274                // content; twice would cost exactly what asking early saves.
275                let key = match keys.get(node) {
276                    Some(Keys::One(named)) => Some(named.clone()),
277                    _ => self.key_for(node, from, graph_input, keys),
278                };
279                if let Some(key) = &key {
280                    keys.insert(node.clone(), Keys::One(key.clone()));
281                }
282                // A hit is the whole point: the node is not advanced, and its
283                // input is not even assembled.
284                let output = match self.recalled(node, key.as_ref()) {
285                    Some(kept) => kept,
286                    // Kept when the store was asked, gone when it was read.
287                    // What feeds this was skipped *because* the answer was
288                    // there, so there is nothing left to run.
289                    None if from.iter().any(|id| unneeded.contains(id)) => {
290                        return Err(RunError::Vanished { node: node.clone() });
291                    }
292                    None => {
293                        let input = gather(node, from, graph_input, produced)?;
294                        let output = self.advance(node, input)?;
295                        self.keep(node, key.as_ref(), &output);
296                        output
297                    }
298                };
299                produced.insert(node.clone(), output.clone());
300                Ok(output)
301            }
302            Plan::Sequence(plans) => {
303                let mut last = graph_input.clone();
304                for plan in plans {
305                    last = self.walk(plan, graph_input, produced, keys, unneeded)?;
306                }
307                Ok(last)
308            }
309            Plan::Wave(branches) => self.at_once(branches, graph_input, produced, keys, unneeded),
310            // A slice nobody needs is a message that is not sent: the whole
311            // round trip goes, not just the work at the far end.
312            Plan::Remote { inner, .. }
313                if inner.steps().all(|step| unneeded.contains(step.node)) =>
314            {
315                for step in inner.steps() {
316                    self.saw(|| Fact::Spared {
317                        node: step.node.clone(),
318                    });
319                }
320                Ok(Value::Null)
321            }
322            Plan::Remote { host, inner } => {
323                self.elsewhere(host, inner, graph_input, produced, keys)
324            }
325        }
326    }
327
328    /// Whether this node was declared to map over the items of its input.
329    fn maps(&self, node: &NodeId) -> bool {
330        self.memory.is_some_and(|memory| memory.is_mapped(node))
331    }
332
333    /// One step of a node that maps: the items it is missing, and no more. Its
334    /// input is assembled first and there is no way around it — the names of
335    /// these items are made out of the items.
336    fn over_items(
337        &self,
338        node: &NodeId,
339        from: &[NodeId],
340        graph_input: &Value,
341        produced: &mut HashMap<NodeId, Value>,
342        keys: &mut HashMap<NodeId, Keys>,
343    ) -> Result<Value, RunError> {
344        let input = gather(node, from, graph_input, produced)?;
345        let Value::List(items) = &input else {
346            return Err(RunError::NotItems {
347                node: node.clone(),
348                given: input.type_name().to_string(),
349            });
350        };
351
352        let mine = self.keys_for_items(node, from, items, keys);
353        let kept: Vec<Option<Value>> = match &mine {
354            Some(mine) => self.recalled_items(node, mine),
355            None => vec![None; items.len()],
356        };
357        let missing: Vec<usize> = (0..items.len()).filter(|i| kept[*i].is_none()).collect();
358        self.saw(|| Fact::Items {
359            node: node.clone(),
360            of: items.len(),
361            recalled: items.len() - missing.len(),
362        });
363
364        // Nothing missing is the point of all this: the node is not advanced at
365        // all, exactly as an ordinary hit does not advance it.
366        let mut answers = Vec::new();
367        if !missing.is_empty() {
368            let asked = Value::list(
369                missing
370                    .iter()
371                    .map(|i| items[*i].clone())
372                    .collect::<Vec<_>>(),
373            );
374            let output = self.advance(node, asked)?;
375            let Value::List(back) = &output else {
376                return Err(RunError::NotItems {
377                    node: node.clone(),
378                    given: output.type_name().to_string(),
379                });
380            };
381            if back.len() != missing.len() {
382                return Err(RunError::Uncounted {
383                    node: node.clone(),
384                    asked: missing.len(),
385                    answered: back.len(),
386                });
387            }
388            answers = back.to_vec();
389        }
390
391        let mut out = Vec::with_capacity(items.len());
392        let mut answered = answers.into_iter();
393        for (i, was) in kept.into_iter().enumerate() {
394            match was {
395                Some(value) => out.push(value),
396                None => {
397                    let value = answered.next().expect("one answer per item asked for");
398                    if let Some(mine) = &mine {
399                        self.keep(node, Some(&mine[i]), &value);
400                    }
401                    out.push(value);
402                }
403            }
404        }
405
406        let output = Value::list(out);
407        if let Some(mine) = mine {
408            keys.insert(node.clone(), Keys::PerItem(mine));
409        }
410        produced.insert(node.clone(), output.clone());
411        Ok(output)
412    }
413
414    /// One name per item, or `None` when nothing is being remembered.
415    ///
416    /// If what is above already names each item these are built out of those;
417    /// if not, each item is hashed by **its own content** — its position would
418    /// not make the same document in another list the same item.
419    fn keys_for_items(
420        &self,
421        node: &NodeId,
422        from: &[NodeId],
423        items: &[Value],
424        keys: &HashMap<NodeId, Keys>,
425    ) -> Option<Vec<Key>> {
426        let (keeper, memory) = (self.keeper?, self.memory?);
427        let identity = memory.identity_of(node)?;
428        let above: Vec<Key> = match from {
429            [one] => match keys.get(one) {
430                Some(Keys::PerItem(each)) if each.len() == items.len() => each.clone(),
431                _ => items
432                    .iter()
433                    .map(|item| keeper.key_of(item))
434                    .collect::<Option<Vec<_>>>()?,
435            },
436            _ => items
437                .iter()
438                .map(|item| keeper.key_of(item))
439                .collect::<Option<Vec<_>>>()?,
440        };
441        Some(
442            above
443                .iter()
444                .map(|one| {
445                    keeper.combine(&[
446                        identity,
447                        memory.state_of(node).unwrap_or(""),
448                        memory.salt_of(node).unwrap_or(""),
449                        one.as_str(),
450                    ])
451                })
452                .collect(),
453        )
454    }
455
456    /// What is kept for each of these, asked in one call: a thousand items
457    /// against a remote store is a thousand round trips unless it is one.
458    fn recalled_items(&self, node: &NodeId, mine: &[Key]) -> Vec<Option<Value>> {
459        let nothing = vec![None; mine.len()];
460        let Some((keeper, memory)) = self.keeper.zip(self.memory) else {
461            return nothing;
462        };
463        if !memory.is_cached(node) {
464            return nothing;
465        }
466        match keeper.recall(&mine.iter().collect::<Vec<_>>()) {
467            Ok(answers) => answers
468                .into_iter()
469                .map(|kept| kept.map(|kept| kept.value))
470                .collect(),
471            Err(why) => {
472                eprintln!("what `{node}` produced could not be looked up: {why}");
473                nothing
474            }
475        }
476    }
477
478    /// Launches a wave's branches at once and merges what they produced. Each
479    /// gets a copy of `produced` and returns only its own; being connected
480    /// components they are disjoint, so merging clobbers nothing and there is
481    /// no lock.
482    fn at_once(
483        &self,
484        branches: &[Plan],
485        graph_input: &Value,
486        produced: &mut HashMap<NodeId, Value>,
487        keys: &mut HashMap<NodeId, Keys>,
488        unneeded: &HashSet<NodeId>,
489    ) -> Result<Value, RunError> {
490        let earlier: &HashMap<NodeId, Value> = produced;
491        let named: &HashMap<NodeId, Keys> = keys;
492        let outcomes = std::thread::scope(|scope| {
493            let running: Vec<_> = branches
494                .iter()
495                .map(|branch| {
496                    scope.spawn(move || {
497                        let mut mine = earlier.clone();
498                        let mut mine_keys = named.clone();
499                        let last =
500                            self.walk(branch, graph_input, &mut mine, &mut mine_keys, unneeded)?;
501                        mine.retain(|id, _| !earlier.contains_key(id));
502                        mine_keys.retain(|id, _| !named.contains_key(id));
503                        Ok::<_, RunError>((last, mine, mine_keys))
504                    })
505                })
506                .collect();
507            running
508                .into_iter()
509                .map(|handle| match handle.join() {
510                    Ok(outcome) => outcome,
511                    // Not swallowed: `scope` has already waited on the others.
512                    Err(panic) => std::panic::resume_unwind(panic),
513                })
514                .collect::<Vec<_>>()
515        });
516
517        for outcome in outcomes {
518            // The first to fail **in declaration order**, not in time.
519            let (_, mine, mine_keys) = outcome?;
520            produced.extend(mine);
521            keys.extend(mine_keys);
522        }
523
524        // A wave has no single output: its branches end in several places.
525        Ok(Value::Null)
526    }
527
528    /// Sends a slice elsewhere and merges whatever comes back, given only what
529    /// it reads and does not produce.
530    fn elsewhere(
531        &self,
532        host: &Host,
533        inner: &Plan,
534        graph_input: &Value,
535        produced: &mut HashMap<NodeId, Value>,
536        keys: &mut HashMap<NodeId, Keys>,
537    ) -> Result<Value, RunError> {
538        let transport = self
539            .transports
540            .iter()
541            .find(|(known, _)| known == host)
542            .map(|(_, transport)| *transport)
543            .ok_or_else(|| RunError::NoTransport(host.clone()))?;
544
545        let reads = needs(inner);
546        let known: Vec<(NodeId, Value)> = reads
547            .iter()
548            .filter_map(|id| produced.get(id).map(|value| (id.clone(), value.clone())))
549            .collect();
550        // The keys of the same set: what it reads is what it has to be able to
551        // name, and what it produces it names from those.
552        let named: Vec<(NodeId, Keys)> = reads
553            .iter()
554            .filter_map(|id| keys.get(id).map(|key| (id.clone(), key.clone())))
555            .collect();
556
557        let nowhere = Placement::new();
558        let nothing = Memory::new();
559        let cargo = Cargo {
560            input: graph_input,
561            known: &known,
562            keys: &named,
563            placement: self.placement.unwrap_or(&nowhere),
564            // Travels whether or not there is a keeper here: what is
565            // remembered is the graph's, and the far side may be the keeper.
566            memory: self.memory.unwrap_or(&nothing),
567        };
568        // The far side emits exactly what it would emit at home; attributing
569        // it happens here, because here is where the host has a name.
570        let attributed = self.watcher.map(|to| Attributed {
571            host: host.clone(),
572            to,
573        });
574        let at = self.so_far();
575        let began = Instant::now();
576        let outcome = transport
577            .dispatch(
578                inner,
579                &cargo,
580                attributed.as_ref().map(|one| one as &dyn Watcher),
581            )
582            .map_err(|source| RunError::Transport {
583                host: host.clone(),
584                source,
585            })?;
586        self.saw(|| Fact::Left {
587            host: host.clone(),
588            began: at,
589            took: began.elapsed(),
590        });
591
592        produced.extend(outcome.produced);
593        keys.extend(outcome.keys);
594        Ok(outcome.last)
595    }
596
597    /// Run it, and attribute whatever it says to it. Whatever the node takes to
598    /// answer happens inside it: the engine cannot tell a loop that will not end
599    /// from work that is slow, so it neither counts nor bounds.
600    fn advance(&self, node: &NodeId, input: Value) -> Result<Value, RunError> {
601        let ctx = Ctx {
602            device: self.device(node),
603        };
604        // Around the `forward` and nothing else, so the number means the same
605        // whether or not the node is cached, mapped or on another machine.
606        let at = self.so_far();
607        let began = Instant::now();
608        let answer = self.implementation(node)?.forward(&input, &ctx);
609        let took = began.elapsed();
610        match answer {
611            Ok(output) => {
612                self.saw(|| Fact::Ran {
613                    node: node.clone(),
614                    began: at,
615                    took,
616                    device: self.device(node).cloned(),
617                });
618                Ok(output)
619            }
620            Err(source) => {
621                // Said before the error is returned: by the time the caller
622                // sees it the run is over, and a watcher wanted the node now.
623                self.saw(|| Fact::Failed {
624                    node: node.clone(),
625                    why: source.to_string(),
626                });
627                Err(RunError::Node {
628                    node: node.clone(),
629                    source,
630                })
631            }
632        }
633    }
634
635    /// The name this node's output will have, **before** it has one.
636    ///
637    /// `None` whenever anything the recipe is made of is missing. Not a
638    /// failure: it means neither this output nor anything below it can be kept.
639    /// One of the two seams of the cache — the one that sees every edge.
640    fn key_for(
641        &self,
642        node: &NodeId,
643        from: &[NodeId],
644        graph_input: &Value,
645        keys: &HashMap<NodeId, Keys>,
646    ) -> Option<Key> {
647        let (keeper, memory) = (self.keeper?, self.memory?);
648        let identity = memory.identity_of(node)?;
649        let keeper: &dyn Keeper = keeper;
650        // A root reads the graph's input, the one thing hashed by content; from
651        // here down it is hashes of hashes, which is what makes a key foreseen.
652        //
653        // And that one hash is [`run`](Self::run)'s: it names the input for the
654        // record before the walk begins, and a root's name is built on the very
655        // same digest. Hashing it again costs exactly what asking early saves —
656        // a 19 MB batch weighed twice is 245 ms where CU24 measured 121. A
657        // slice has no graph input of its own, so `resume` leaves this empty
658        // and the root there is named the long way.
659        let above: Vec<Key> = match from {
660            [] => vec![match &self.input {
661                Some(named) => named.clone(),
662                None => keeper.key_of(graph_input)?,
663            }],
664            many => many
665                .iter()
666                .map(|id| keys.get(id).map(|keys| whole(keeper, keys)))
667                .collect::<Option<Vec<_>>>()?,
668        };
669
670        // The state of a node that is not frozen is empty, and it does not
671        // matter: nothing unfrozen is kept, nor is anything under it.
672        let mut parts = vec![
673            identity,
674            memory.declaration_of(node).unwrap_or(""),
675            memory.state_of(node).unwrap_or(""),
676            memory.salt_of(node).unwrap_or(""),
677        ];
678        parts.extend(above.iter().map(Key::as_str));
679        Some(keeper.combine(&parts))
680    }
681
682    /// The names the whole plan will produce, and the nodes that will not have
683    /// to produce them.
684    ///
685    /// Names first, since a key needs nothing to have run;
686    /// then [`present`](Keeper::present) says which answers are already there;
687    /// then backwards from the leaves, because a node whose answer is kept does
688    /// not need its inputs. Gives up towards **keeping** a node in two places: a
689    /// mapped node, named by its items' content, and a node with no key.
690    ///
691    /// Public because the answer is worth having without the run: two versions
692    /// of a graph name a node differently exactly when its recipe changed.
693    /// Names nothing without a keeper and a memory.
694    pub fn foreseen(
695        &self,
696        plan: &Plan,
697        graph_input: &Value,
698    ) -> (HashMap<NodeId, Keys>, HashSet<NodeId>) {
699        let nothing = (HashMap::new(), HashSet::new());
700        let (Some(keeper), Some(memory)) = (self.keeper, self.memory) else {
701            return nothing;
702        };
703
704        // Plan order is topological, so a predecessor's name is always in hand.
705        let mut named: HashMap<NodeId, Keys> = HashMap::new();
706        let mut asked: Vec<(NodeId, Key)> = Vec::new();
707        for step in plan.steps() {
708            if self.maps(step.node) {
709                continue;
710            }
711            let Some(key) = self.key_for(step.node, step.from, graph_input, &named) else {
712                continue;
713            };
714            if memory.is_cached(step.node) {
715                asked.push((step.node.clone(), key.clone()));
716            }
717            named.insert(step.node.clone(), Keys::One(key));
718        }
719        if asked.is_empty() {
720            return (named, HashSet::new());
721        }
722
723        let keys: Vec<&Key> = asked.iter().map(|(_, key)| key).collect();
724        let there = match keeper.present(&keys) {
725            Ok(there) => there,
726            // A keeper that cannot answer is not the end of the run: nothing
727            // is skipped and everything is computed.
728            Err(why) => {
729                eprintln!("what is already kept could not be looked up: {why}");
730                return (named, HashSet::new());
731            }
732        };
733        let kept: HashSet<&NodeId> = asked
734            .iter()
735            .zip(&there)
736            .filter(|(_, there)| **there)
737            .map(|((node, _), _)| node)
738            .collect();
739
740        let mut needed: HashSet<NodeId> = HashSet::new();
741        let mut asking: Vec<NodeId> = terminals(plan);
742        while let Some(node) = asking.pop() {
743            if !needed.insert(node.clone()) || kept.contains(&node) {
744                continue;
745            }
746            for step in plan.steps().filter(|step| *step.node == node) {
747                asking.extend(step.from.iter().cloned());
748            }
749        }
750        let unneeded = plan
751            .steps()
752            .map(|step| step.node)
753            .filter(|node| !needed.contains(*node))
754            .cloned()
755            .collect();
756        (named, unneeded)
757    }
758
759    /// What is kept under this node's name, if it is kept at all. A keeper that
760    /// cannot answer recomputes and says so — an optimization that can kill a
761    /// run at hour three is not one.
762    fn recalled(&self, node: &NodeId, key: Option<&Key>) -> Option<Value> {
763        let (keeper, memory) = (self.keeper?, self.memory?);
764        let key = key?;
765        if !memory.is_cached(node) {
766            return None;
767        }
768        let kept = match keeper.recall(&[key]) {
769            Ok(answers) => answers.into_iter().next().flatten()?,
770            Err(why) => {
771                eprintln!("what `{node}` produced could not be looked up: {why}");
772                return None;
773            }
774        };
775
776        // The fingerprint is deliberately not in the key, so this is where the
777        // two are put side by side. It is said, and what was kept is used.
778        if let (Some(declared), Some(written)) = (memory.fingerprint_of(node), fingerprint(&kept))
779            && declared != written
780        {
781            eprintln!(
782                "`{node}` was kept by code fingerprinted `{written}` and this graph declares \
783                 `{declared}`: using what is kept, since the fingerprint is not part of the key"
784            );
785        }
786        self.saw(|| Fact::Recalled {
787            node: node.clone(),
788            key: key.clone(),
789        });
790        Some(kept.value)
791    }
792
793    /// Keeps what this node produced, if it was said to be worth keeping. A
794    /// node with no `.cached()` still got a key and still passed it on.
795    fn keep(&self, node: &NodeId, key: Option<&Key>, output: &Value) {
796        let (Some(keeper), Some(memory), Some(key)) = (self.keeper, self.memory, key) else {
797            return;
798        };
799        if !memory.is_cached(node) {
800            return;
801        }
802        let mut meta = vec![(NODE, node.as_str())];
803        if let Some(written) = memory.fingerprint_of(node) {
804            meta.push((FINGERPRINT, written));
805        }
806        if let Some(fed) = &self.input {
807            meta.push((INPUT, fed.as_str()));
808        }
809        // What the engine knows is not shadowed by a caller who picked one of
810        // its words. Dropped and not put last: the obvious way to read a list
811        // of pairs takes the last. Silent here because the layer a person types
812        // in already refused it out loud.
813        meta.extend(
814            self.stamp
815                .iter()
816                .filter(|(what, _)| !OURS.contains(&what.as_str()))
817                .map(|(what, said)| (what.as_str(), said.as_str())),
818        );
819        match keeper.keep(key, output, &meta) {
820            Ok(()) => self.saw(|| Fact::Kept {
821                node: node.clone(),
822                key: key.clone(),
823            }),
824            Err(why) => eprintln!("what `{node}` produced could not be kept: {why}"),
825        }
826    }
827
828    /// Where this node was said to run. Without a placement, nowhere.
829    fn device(&self, node: &NodeId) -> Option<&'a Device> {
830        self.placement.and_then(|placement| placement.of(node))
831    }
832
833    /// What the catalog has registered for this node.
834    fn implementation(&self, node: &NodeId) -> Result<&std::sync::Arc<dyn crate::Node>, RunError> {
835        self.catalog
836            .get(node)
837            .ok_or_else(|| RunError::NoImplementation(node.clone()))
838    }
839}
840
841/// A watcher that says where what it is told happened, and passes it on. One
842/// wrapper per dispatch, so a slice that carried on to a third machine comes out
843/// of [`Fact::flattened`] with its route in order.
844struct Attributed<'a> {
845    host: Host,
846    to: &'a dyn Watcher,
847}
848
849impl Watcher for Attributed<'_> {
850    fn saw(&self, fact: &Fact) {
851        self.to.saw(&Fact::Elsewhere {
852            host: self.host.clone(),
853            saw: Box::new(fact.clone()),
854        });
855    }
856}
857
858/// Why the execution could not be finished. The structural things were ruled
859/// out in [`compile`](crate::compile); these are the implementations' failures.
860#[derive(Debug, Clone, PartialEq, Eq)]
861pub enum RunError {
862    /// The plan names a node this catalog does not know.
863    NoImplementation(NodeId),
864    /// The node failed.
865    Node {
866        /// Where it happened.
867        node: NodeId,
868        /// What it said.
869        source: NodeError,
870    },
871    /// The plan sends a slice to a host nobody knows how to reach.
872    NoTransport(Host),
873    /// The transport could not carry the slice, or what ran there failed.
874    Transport {
875        /// Which host it was bound for.
876        host: Host,
877        /// What the transport said.
878        source: TransportError,
879    },
880    /// A node that maps was handed something that is not a list of items, or
881    /// answered with something that is not one.
882    NotItems {
883        /// Which node.
884        node: NodeId,
885        /// And what arrived instead.
886        given: String,
887    },
888    /// A node that maps answered with a different number of items than it was
889    /// asked for, so nobody knows which answer goes with which item.
890    Uncounted {
891        /// Which node.
892        node: NodeId,
893        /// How many items it was handed.
894        asked: usize,
895        /// And how many came back.
896        answered: usize,
897    },
898    /// What was kept when the store was asked and gone when it was read, after
899    /// what feeds this node had already been skipped because of the answer.
900    Vanished {
901        /// The one with nothing left to run.
902        node: NodeId,
903    },
904    /// A step reads what another produced, and what it produced never came back
905    /// from wherever it ran.
906    Lost {
907        /// The one that cannot be assembled.
908        node: NodeId,
909        /// What it was reading.
910        from: NodeId,
911    },
912}
913
914impl fmt::Display for RunError {
915    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
916        match self {
917            Self::NoImplementation(id) => {
918                write!(f, "node `{id}` has no registered implementation")
919            }
920            Self::Node { node, source } => write!(f, "node `{node}` failed: {source}"),
921            Self::NotItems { node, given } => write!(
922                f,
923                "`{node}` maps over the items of its input, so what reaches it and \
924                 what it answers with are lists; a `{given}` is one thing and has \
925                 no items. Either it does not map, or whoever feeds it should be \
926                 handing it a list"
927            ),
928            Self::Uncounted {
929                node,
930                asked,
931                answered,
932            } => write!(
933                f,
934                "`{node}` was handed {asked} items and answered with {answered}: a \
935                 node that maps gives back one for each, in order, or nobody can \
936                 tell which answer belongs to which item"
937            ),
938            Self::NoTransport(host) => write!(
939                f,
940                "there is a slice placed on `{host}` and this executor cannot reach it"
941            ),
942            Self::Transport { host, source } => write!(f, "carrying a slice to `{host}`: {source}"),
943            Self::Vanished { node } => write!(
944                f,
945                "what was kept for `{node}` was there when the store was asked and gone when \
946                 it was read, and what feeds it was not run because of that answer. Nothing \
947                 was lost — run it again"
948            ),
949            Self::Lost { node, from } => write!(
950                f,
951                "`{node}` reads what `{from}` produced, and that stayed where it ran: \
952                 only what can leave a process comes back from one"
953            ),
954        }
955    }
956}
957
958impl std::error::Error for RunError {}
959
960/// What a node receives: nothing → the graph's input, one thing → that thing,
961/// several → a map keyed by whoever produced each, in edge declaration order.
962fn gather(
963    node: &NodeId,
964    from: &[NodeId],
965    graph_input: &Value,
966    produced: &HashMap<NodeId, Value>,
967) -> Result<Value, RunError> {
968    // Topological order already ran the predecessors, so what is missing stayed
969    // in the process that made it, unable to leave one.
970    let recall = |id: &NodeId| {
971        produced.get(id).cloned().ok_or_else(|| RunError::Lost {
972            node: node.clone(),
973            from: id.clone(),
974        })
975    };
976    match from {
977        [] => Ok(graph_input.clone()),
978        [single] => recall(single),
979        many => Ok(Value::map(
980            many.iter()
981                .map(|id| Ok((id.to_string(), recall(id)?)))
982                .collect::<Result<Vec<_>, RunError>>()?,
983        )),
984    }
985}
986
987/// One name for what a node produced, whether it has one or a thousand.
988/// [`Keeper::combine`] decides how; what matters is that it is deterministic and
989/// depends on all of them.
990fn whole(keeper: &dyn Keeper, keys: &Keys) -> Key {
991    match keys {
992        Keys::One(key) => key.clone(),
993        Keys::PerItem(each) => keeper.combine(&each.iter().map(Key::as_str).collect::<Vec<_>>()),
994    }
995}
996
997/// A table in the order a wire wants it: by id, so two runs of the same thing
998/// answer with the same bytes.
999fn sorted<T>(table: HashMap<NodeId, T>) -> Vec<(NodeId, T)> {
1000    let mut out: Vec<(NodeId, T)> = table.into_iter().collect();
1001    out.sort_by(|(a, _), (b, _)| a.cmp(b));
1002    out
1003}
1004
1005/// What was written beside a kept value about the code that produced it.
1006fn fingerprint(kept: &Kept) -> Option<&str> {
1007    kept.meta
1008        .iter()
1009        .find(|(what, _)| what == FINGERPRINT)
1010        .map(|(_, written)| written.as_str())
1011}
1012
1013/// What this plan reads and does not produce: what has to travel with it.
1014fn needs(plan: &Plan) -> Vec<NodeId> {
1015    let produced: Vec<&NodeId> = plan.steps().map(|step| step.node).collect();
1016    let mut out: Vec<NodeId> = Vec::new();
1017    for id in plan.steps().flat_map(|step| step.from) {
1018        if !produced.contains(&id) && !out.contains(id) {
1019            out.push(id.clone());
1020        }
1021    }
1022    out
1023}
1024
1025/// The plan's nodes whose output no other node reads: the leaves.
1026fn terminals(plan: &Plan) -> Vec<NodeId> {
1027    let consumed: Vec<&NodeId> = plan.steps().flat_map(|step| step.from).collect();
1028    plan.steps()
1029        .map(|step| step.node)
1030        .filter(|node| !consumed.contains(node))
1031        .cloned()
1032        .collect()
1033}