Skip to main content

timely/progress/
reachability.rs

1//! Manages pointstamp reachability within a timely dataflow graph.
2//!
3//! Timely dataflow is concerned with understanding and communicating the potential
4//! for capabilities to reach nodes in a directed graph, by following paths through
5//! the graph (along edges and through nodes). This module contains one abstraction
6//! for managing this information.
7//!
8//! # Examples
9//!
10//! ```rust
11//! use timely::progress::{Location, Port};
12//! use timely::progress::frontier::Antichain;
13//! use timely::progress::{Source, Target};
14//! use timely::progress::reachability::{Builder, Tracker};
15//!
16//! // allocate a new empty topology builder.
17//! let mut builder = Builder::<usize>::new();
18//!
19//! // Each node with one input connected to one output.
20//! builder.add_node(0, 1, 1, vec![[(0, Antichain::from_elem(0))].into_iter().collect()]);
21//! builder.add_node(1, 1, 1, vec![[(0, Antichain::from_elem(0))].into_iter().collect()]);
22//! builder.add_node(2, 1, 1, vec![[(0, Antichain::from_elem(1))].into_iter().collect()]);
23//!
24//! // Connect nodes in sequence, looping around to the first from the last.
25//! builder.add_edge(Source::new(0, 0), Target::new(1, 0));
26//! builder.add_edge(Source::new(1, 0), Target::new(2, 0));
27//! builder.add_edge(Source::new(2, 0), Target::new(0, 0));
28//!
29//! // Construct a reachability tracker.
30//! let (mut tracker, _) = builder.build(None);
31//!
32//! // Introduce a pointstamp at the output of the first node.
33//! tracker.update_source(Source::new(0, 0), 17, 1);
34//!
35//! // Propagate changes; until this call updates are simply buffered.
36//! tracker.propagate_all();
37//!
38//! let mut results =
39//! tracker
40//!     .pushed()
41//!     .0
42//!     .drain()
43//!     .filter(|((location, time), delta)| location.is_target())
44//!     .collect::<Vec<_>>();
45//!
46//! results.sort();
47//!
48//! println!("{:?}", results);
49//!
50//! assert_eq!(results.len(), 3);
51//! assert_eq!(results[0], ((Location::new_target(0, 0), 18), 1));
52//! assert_eq!(results[1], ((Location::new_target(1, 0), 17), 1));
53//! assert_eq!(results[2], ((Location::new_target(2, 0), 17), 1));
54//!
55//! // Introduce a pointstamp at the output of the first node.
56//! tracker.update_source(Source::new(0, 0), 17, -1);
57//!
58//! // Propagate changes; until this call updates are simply buffered.
59//! tracker.propagate_all();
60//!
61//! let mut results =
62//! tracker
63//!     .pushed()
64//!     .0
65//!     .drain()
66//!     .filter(|((location, time), delta)| location.is_target())
67//!     .collect::<Vec<_>>();
68//!
69//! results.sort();
70//!
71//! assert_eq!(results.len(), 3);
72//! assert_eq!(results[0], ((Location::new_target(0, 0), 18), -1));
73//! assert_eq!(results[1], ((Location::new_target(1, 0), 17), -1));
74//! assert_eq!(results[2], ((Location::new_target(2, 0), 17), -1));
75//! ```
76
77use std::collections::BinaryHeap;
78use std::cmp::Reverse;
79
80use columnar::{Vecs, Index as ColumnarIndex};
81
82use crate::progress::Timestamp;
83use crate::progress::{Source, Target};
84use crate::progress::ChangeBatch;
85use crate::progress::{Location, Port};
86use crate::progress::operate::{Connectivity, PortConnectivity, PortConnectivityBuilder};
87use crate::progress::frontier::{Antichain, MutableAntichain};
88use crate::progress::timestamp::PathSummary;
89
90/// Build a `Vecs<Vecs<Vec<S>>>` from nested iterators.
91///
92/// The outer iterator yields nodes, each node yields ports, each port yields data items.
93fn build_nested_vecs<S>(nodes: impl Iterator<Item = impl Iterator<Item = impl Iterator<Item = S>>>) -> Vecs<Vecs<Vec<S>>> {
94    let mut result: Vecs<Vecs<Vec<S>>> = Default::default();
95    for node in nodes {
96        for port in node {
97            result.values.push_iter(port);
98        }
99        result.bounds.push(result.values.bounds.len() as u64);
100    }
101    result
102}
103
104/// A topology builder, which can summarize reachability along paths.
105///
106/// A `Builder` takes descriptions of the nodes and edges in a graph, and compiles
107/// a static summary of the minimal actions a timestamp must endure going from any
108/// input or output port to a destination input port.
109///
110/// A graph is provides as (i) several indexed nodes, each with some number of input
111/// and output ports, and each with a summary of the internal paths connecting each
112/// input to each output, and (ii) a set of edges connecting output ports to input
113/// ports. Edges do not adjust timestamps; only nodes do this.
114///
115/// The resulting summary describes, for each origin port in the graph and destination
116/// input port, a set of incomparable path summaries, each describing what happens to
117/// a timestamp as it moves along the path. There may be multiple summaries for each
118/// part of origin and destination due to the fact that the actions on timestamps may
119/// not be totally ordered (e.g., "increment the timestamp" and "take the maximum of
120/// the timestamp and seven").
121///
122/// # Examples
123///
124/// ```rust
125/// use timely::progress::frontier::Antichain;
126/// use timely::progress::{Source, Target};
127/// use timely::progress::reachability::Builder;
128///
129/// // allocate a new empty topology builder.
130/// let mut builder = Builder::<usize>::new();
131///
132/// // Each node with one input connected to one output.
133/// builder.add_node(0, 1, 1, vec![[(0, Antichain::from_elem(0))].into_iter().collect()]);
134/// builder.add_node(1, 1, 1, vec![[(0, Antichain::from_elem(0))].into_iter().collect()]);
135/// builder.add_node(2, 1, 1, vec![[(0, Antichain::from_elem(1))].into_iter().collect()]);
136///
137/// // Connect nodes in sequence, looping around to the first from the last.
138/// builder.add_edge(Source::new(0, 0), Target::new(1, 0));
139/// builder.add_edge(Source::new(1, 0), Target::new(2, 0));
140/// builder.add_edge(Source::new(2, 0), Target::new(0, 0));
141///
142/// // Summarize reachability information.
143/// let (tracker, _) = builder.build(None);
144/// ```
145#[derive(Clone, Debug)]
146pub struct Builder<T: Timestamp> {
147    /// Internal connections within hosted operators.
148    ///
149    /// Indexed by operator index, then input port, then output port. This is the
150    /// same format returned by `initialize`, as if we simply appended
151    /// all of the summaries for the hosted nodes.
152    pub nodes: Vec<Connectivity<T::Summary>>,
153    /// Direct connections from sources to targets.
154    ///
155    /// Edges do not affect timestamps, so we only need to know the connectivity.
156    /// Indexed by operator index then output port.
157    pub edges: Vec<Vec<Vec<Target>>>,
158    /// Numbers of inputs and outputs for each node.
159    pub shape: Vec<(usize, usize)>,
160}
161
162impl<T: Timestamp> Builder<T> {
163
164    /// Create a new empty topology builder.
165    pub fn new() -> Self {
166        Builder {
167            nodes: Vec::new(),
168            edges: Vec::new(),
169            shape: Vec::new(),
170        }
171    }
172
173    /// Add links internal to operators.
174    ///
175    /// This method overwrites any existing summary, instead of anything more sophisticated.
176    pub fn add_node(&mut self, index: usize, inputs: usize, outputs: usize, summary: Connectivity<T::Summary>) {
177
178        // Assert that all summaries exist.
179        debug_assert_eq!(inputs, summary.len());
180        debug_assert!(summary.iter().all(|os| os.iter_ports().all(|(o,_)| o < outputs)));
181
182        while self.nodes.len() <= index {
183            self.nodes.push(Vec::new());
184            self.edges.push(Vec::new());
185            self.shape.push((0, 0));
186        }
187
188        self.nodes[index] = summary;
189        if self.edges[index].len() != outputs {
190            self.edges[index] = vec![Vec::new(); outputs];
191        }
192        self.shape[index] = (inputs, outputs);
193    }
194
195    /// Add links between operators.
196    ///
197    /// This method does not check that the associated nodes and ports exist. References to
198    /// missing nodes or ports are discovered in `build`.
199    pub fn add_edge(&mut self, source: Source, target: Target) {
200
201        // Assert that the edge is between existing ports.
202        debug_assert!(source.port < self.shape[source.node].1);
203        debug_assert!(target.port < self.shape[target.node].0);
204
205        self.edges[source.node][source.port].push(target);
206    }
207
208    /// Compiles the current nodes and edges into immutable path summaries.
209    ///
210    /// This method has the opportunity to perform some error checking that the path summaries
211    /// are valid, including references to undefined nodes and ports, as well as self-loops with
212    /// default summaries (a serious liveness issue).
213    ///
214    /// The optional logger information is baked into the resulting tracker.
215    pub fn build(self, logger: Option<logging::TrackerLogger<T>>) -> (Tracker<T>, Connectivity<T::Summary>) {
216
217        if !self.is_acyclic() {
218            println!("Cycle detected without timestamp increment");
219            println!("{:?}", self);
220        }
221
222        Tracker::allocate_from(self, logger)
223    }
224
225    /// Tests whether the graph includes a cycle of default path summaries.
226    ///
227    /// Graphs containing cycles of default path summaries will most likely
228    /// not work well with progress tracking, as a timestamp can result in
229    /// itself. Such computations can still *run*, but one should not block
230    /// on frontier information before yielding results, as you many never
231    /// unblock.
232    ///
233    /// # Examples
234    ///
235    /// ```rust
236    /// use timely::progress::frontier::Antichain;
237    /// use timely::progress::{Source, Target};
238    /// use timely::progress::reachability::Builder;
239    ///
240    /// // allocate a new empty topology builder.
241    /// let mut builder = Builder::<usize>::new();
242    ///
243    /// // Each node with one input connected to one output.
244    /// builder.add_node(0, 1, 1, vec![[(0, Antichain::from_elem(0))].into_iter().collect()]);
245    /// builder.add_node(1, 1, 1, vec![[(0, Antichain::from_elem(0))].into_iter().collect()]);
246    /// builder.add_node(2, 1, 1, vec![[(0, Antichain::from_elem(0))].into_iter().collect()]);
247    ///
248    /// // Connect nodes in sequence, looping around to the first from the last.
249    /// builder.add_edge(Source::new(0, 0), Target::new(1, 0));
250    /// builder.add_edge(Source::new(1, 0), Target::new(2, 0));
251    ///
252    /// assert!(builder.is_acyclic());
253    ///
254    /// builder.add_edge(Source::new(2, 0), Target::new(0, 0));
255    ///
256    /// assert!(!builder.is_acyclic());
257    /// ```
258    ///
259    /// This test exists because it is possible to describe dataflow graphs that
260    /// do not contain non-incrementing cycles, but without feedback nodes that
261    /// strictly increment timestamps. For example,
262    ///
263    /// ```rust
264    /// use timely::progress::frontier::Antichain;
265    /// use timely::progress::{Source, Target};
266    /// use timely::progress::reachability::Builder;
267    ///
268    /// // allocate a new empty topology builder.
269    /// let mut builder = Builder::<usize>::new();
270    ///
271    /// // Two inputs and outputs, only one of which advances.
272    /// builder.add_node(0, 2, 2, vec![
273    ///     [(0,Antichain::from_elem(0)),(1,Antichain::new())].into_iter().collect(),
274    ///     [(0,Antichain::new()),(1,Antichain::from_elem(1))].into_iter().collect(),
275    /// ]);
276    ///
277    /// // Connect each output to the opposite input.
278    /// builder.add_edge(Source::new(0, 0), Target::new(0, 1));
279    /// builder.add_edge(Source::new(0, 1), Target::new(0, 0));
280    ///
281    /// assert!(builder.is_acyclic());
282    /// ```
283    pub fn is_acyclic(&self) -> bool {
284
285        // Dense per-location in-degree counts, with each node's targets and
286        // then sources laid out contiguously at a per-node offset.
287        let mut offsets = Vec::with_capacity(self.shape.len());
288        let mut locations = 0;
289        for (targets, sources) in self.shape.iter() {
290            offsets.push(locations);
291            locations += targets + sources;
292        }
293        let index_of = |location: &Location| {
294            let (targets, _) = self.shape[location.node];
295            match location.port {
296                Port::Target(port) => offsets[location.node] + port,
297                Port::Source(port) => offsets[location.node] + targets + port,
298            }
299        };
300        let mut in_degree = vec![0usize; locations];
301
302        // Load edges as default summaries.
303        for ports in self.edges.iter() {
304            for targets in ports.iter() {
305                for &target in targets.iter() {
306                    in_degree[index_of(&Location::from(target))] += 1;
307                }
308            }
309        }
310
311        // Load default intra-node summaries.
312        for (index, summary) in self.nodes.iter().enumerate() {
313            for outputs in summary.iter() {
314                for (output, summaries) in outputs.iter_ports() {
315                    let source = Location::new_source(index, output);
316                    for summary in summaries.elements().iter() {
317                        if summary == &Default::default() {
318                            in_degree[index_of(&source)] += 1;
319                        }
320                    }
321                }
322            }
323        }
324
325        // A worklist of nodes that cannot be reached from the whole graph.
326        // Initially this list contains locations with no incoming edges, but
327        // as the algorithm develops we add to it any locations that can only
328        // be reached by nodes that have been on this list.
329        let mut remaining = in_degree.iter().filter(|count| **count > 0).count();
330        let mut worklist = Vec::with_capacity(locations);
331        for (node, &(targets, sources)) in self.shape.iter().enumerate() {
332            for port in 0 .. targets {
333                let location = Location::new_target(node, port);
334                if in_degree[index_of(&location)] == 0 { worklist.push(location); }
335            }
336            for port in 0 .. sources {
337                let location = Location::new_source(node, port);
338                if in_degree[index_of(&location)] == 0 { worklist.push(location); }
339            }
340        }
341
342        // Repeatedly remove nodes and update adjacent in-edges.
343        while let Some(Location { node, port }) = worklist.pop() {
344            match port {
345                Port::Source(port) => {
346                    for target in self.edges[node][port].iter() {
347                        let target = Location::from(*target);
348                        let index = index_of(&target);
349                        in_degree[index] -= 1;
350                        if in_degree[index] == 0 {
351                            remaining -= 1;
352                            worklist.push(target);
353                        }
354                    }
355                },
356                Port::Target(port) => {
357                    for (output, summaries) in self.nodes[node][port].iter_ports() {
358                        let source = Location::new_source(node, output);
359                        let index = index_of(&source);
360                        for summary in summaries.elements().iter() {
361                            if summary == &Default::default() {
362                                in_degree[index] -= 1;
363                                if in_degree[index] == 0 {
364                                    remaining -= 1;
365                                    worklist.push(source);
366                                }
367                            }
368                        }
369                    }
370                },
371            }
372        }
373
374        // Acyclic graphs should drain every positive in-degree to zero.
375        remaining == 0
376    }
377}
378
379impl<T: Timestamp> Default for Builder<T> {
380    fn default() -> Self {
381        Self::new()
382    }
383}
384
385/// An interactive tracker of propagated reachability information.
386///
387/// A `Tracker` tracks, for a fixed graph topology, the implications of
388/// pointstamp changes at various node input and output ports. These changes may
389/// alter the potential pointstamps that could arrive at downstream input ports.
390pub struct Tracker<T:Timestamp> {
391
392    /// Internal operator connectivity, columnar form of `Vec<Vec<PortConnectivity<T::Summary>>>`.
393    /// Indexed by `(node, input_port)` to yield `(output_port, summary)` pairs.
394    nodes: Vecs<Vecs<Vec<(usize, T::Summary)>>>,
395    /// Edge connectivity, columnar form of `Vec<Vec<Vec<Target>>>`.
396    /// Indexed by `(node, output_port)` to yield target slices.
397    edges: Vecs<Vecs<Vec<Target>>>,
398
399    /// Summaries from each target (operator input) to scope outputs.
400    /// Indexed by `(node, target_port)` to yield `(scope_output, summary)` pairs.
401    target_summaries: Vecs<Vecs<Vec<(usize, T::Summary)>>>,
402    /// Summaries from each source (operator output) to scope outputs.
403    /// Indexed by `(node, source_port)` to yield `(scope_output, summary)` pairs.
404    source_summaries: Vecs<Vecs<Vec<(usize, T::Summary)>>>,
405
406    /// Each source and target has a mutable antichain to ensure that we track their discrete frontiers,
407    /// rather than their multiplicities. We separately track the frontiers resulting from propagated
408    /// frontiers, to protect them from transient negativity in inbound target updates.
409    per_operator: Vec<PerOperator<T>>,
410
411    /// Source and target changes are buffered, which allows us to delay processing until propagation,
412    /// and so consolidate updates, but to leap directly to those frontiers that may have changed.
413    target_changes: ChangeBatch<(Target, T)>,
414    source_changes: ChangeBatch<(Source, T)>,
415
416    /// Worklist of updates to perform, ordered by increasing timestamp and target.
417    worklist: BinaryHeap<Reverse<(T, Location, i64)>>,
418
419    /// Buffer of consequent changes.
420    pushed_changes: ChangeBatch<(Location, T)>,
421
422    /// Compiled summaries from each internal location (not scope inputs) to each scope output.
423    output_changes: Vec<ChangeBatch<T>>,
424
425    /// A non-negative sum of post-filtration input changes.
426    ///
427    /// This sum should be zero exactly when the accumulated input changes are zero,
428    /// indicating that the progress tracker is currently tracking nothing. It should
429    /// always be exactly equal to the sum across all operators of the frontier sizes
430    /// of the target and source `pointstamps` member.
431    total_counts: i64,
432
433    /// Optionally, a unique logging identifier and logging for tracking events.
434    logger: Option<logging::TrackerLogger<T>>,
435}
436
437/// Target and source information for each operator.
438pub struct PerOperator<T: Timestamp> {
439    /// Port information for each target.
440    pub targets: Vec<PortInformation<T>>,
441    /// Port information for each source.
442    pub sources: Vec<PortInformation<T>>,
443    /// Sum across outputs of capabilities.
444    pub cap_counts: i64,
445}
446
447impl<T: Timestamp> PerOperator<T> {
448    /// A new PerOperator bundle from numbers of input and output ports.
449    pub fn new(inputs: usize, outputs: usize) -> Self {
450        PerOperator {
451            targets: vec![PortInformation::new(); inputs],
452            sources: vec![PortInformation::new(); outputs],
453            cap_counts: 0,
454        }
455    }
456}
457
458/// Per-port progress-tracking information.
459#[derive(Clone)]
460pub struct PortInformation<T: Timestamp> {
461    /// Current counts of active pointstamps.
462    pub pointstamps: MutableAntichain<T>,
463    /// Current implications of active pointstamps across the dataflow.
464    pub implications: MutableAntichain<T>,
465}
466
467impl<T: Timestamp> PortInformation<T> {
468    /// Creates empty port information.
469    pub fn new() -> Self {
470        PortInformation {
471            pointstamps: MutableAntichain::new(),
472            implications: MutableAntichain::new(),
473        }
474    }
475
476    /// Returns `true` if updates at this pointstamp uniquely block progress.
477    ///
478    /// This method returns `true` if the currently maintained pointstamp
479    /// counts are such that zeroing out outstanding updates at *this*
480    /// pointstamp would change the frontiers at this operator. When the
481    /// method returns `false` it means that, temporarily at least, there
482    /// are outstanding pointstamp updates that are strictly less than
483    /// this pointstamp.
484    #[inline]
485    pub fn is_global(&self, time: &T) -> bool {
486        let dominated = self.implications.frontier().iter().any(|t| t.less_than(time));
487        let redundant = self.implications.count_for(time) > 1;
488        !dominated && !redundant
489    }
490}
491
492impl<T: Timestamp> Default for PortInformation<T> {
493    fn default() -> Self {
494        Self::new()
495    }
496}
497
498impl<T:Timestamp> Tracker<T> {
499
500    /// Updates the count for a time at a location.
501    #[inline]
502    pub fn update(&mut self, location: Location, time: T, value: i64) {
503        match location.port {
504            Port::Target(port) => self.update_target(Target::new(location.node, port), time, value),
505            Port::Source(port) => self.update_source(Source::new(location.node, port), time, value),
506        };
507    }
508
509    /// Updates the count for a time at a target (operator input, scope output).
510    #[inline]
511    pub fn update_target(&mut self, target: Target, time: T, value: i64) {
512        self.target_changes.update((target, time), value);
513    }
514    /// Updates the count for a time at a source (operator output, scope input).
515    #[inline]
516    pub fn update_source(&mut self, source: Source, time: T, value: i64) {
517        self.source_changes.update((source, time), value);
518    }
519
520    /// Indicates if any pointstamps have positive count.
521    pub fn tracking_anything(&mut self) -> bool {
522        !self.source_changes.is_empty() ||
523        !self.target_changes.is_empty() ||
524        self.total_counts > 0
525    }
526
527    /// Allocate a new `Tracker` using the shape from `summaries`.
528    ///
529    /// The result is a pair of tracker, and the summaries from each input port to each
530    /// output port.
531    ///
532    /// If the optional logger is provided, it will be used to log various tracker events.
533    pub fn allocate_from(builder: Builder<T>, logger: Option<logging::TrackerLogger<T>>) -> (Self, Connectivity<T::Summary>) {
534
535        // Allocate buffer space for each input and input port.
536        let per_operator =
537        builder
538            .shape
539            .iter()
540            .map(|&(inputs, outputs)| PerOperator::new(inputs, outputs))
541            .collect::<Vec<_>>();
542
543        // Summary of scope inputs to scope outputs.
544        let mut builder_summary = vec![PortConnectivity::default(); builder.shape[0].1];
545
546        // Compile summaries from each location to each scope output.
547        // Collect into per-node, per-port buckets for flattening.
548        let output_summaries = summarize_outputs::<T>(&builder.nodes, &builder.edges);
549
550        // Temporary storage: target_sum[node][port] and source_sum[node][port].
551        let mut target_sum: Vec<Vec<PortConnectivity<T::Summary>>> = builder.shape.iter()
552            .map(|&(inputs, _)| vec![PortConnectivity::default(); inputs])
553            .collect();
554        let mut source_sum: Vec<Vec<PortConnectivity<T::Summary>>> = builder.shape.iter()
555            .map(|&(_, outputs)| vec![PortConnectivity::default(); outputs])
556            .collect();
557
558        for (location, summaries) in output_summaries.into_iter() {
559            // Summaries from scope inputs are useful in summarizing the scope.
560            if location.node == 0 {
561                if let Port::Source(port) = location.port {
562                    builder_summary[port] = summaries;
563                }
564                else {
565                    // Ignore (ideally trivial) output to output summaries.
566                }
567            }
568            // Summaries from internal nodes are important for projecting capabilities.
569            else {
570                match location.port {
571                    Port::Target(port) => {
572                        target_sum[location.node][port] = summaries;
573                    },
574                    Port::Source(port) => {
575                        source_sum[location.node][port] = summaries;
576                    },
577                }
578            }
579        }
580
581        // Build columnar nodes: Vecs<Vecs<Vec<(usize, T::Summary)>>>.
582        let nodes = build_nested_vecs(builder.nodes.into_iter().map(|connectivity| {
583            connectivity.into_iter().map(|port_conn| {
584                port_conn.into_iter().flat_map(|(port, antichain)| {
585                    antichain.into_iter().map(move |s| (port, s))
586                })
587            })
588        }));
589
590        // Build columnar edges: Vecs<Vecs<Vec<Target>>>.
591        let edges = build_nested_vecs(builder.edges.iter().map(|node_edges| {
592            node_edges.iter().map(|port_edges| port_edges.iter().cloned())
593        }));
594
595        // Build columnar target and source summaries.
596        let target_summaries = build_nested_vecs(target_sum.into_iter().map(|ports| {
597            ports.into_iter().map(|port_conn| {
598                port_conn.into_iter().flat_map(|(port, antichain)| {
599                    antichain.into_iter().map(move |s| (port, s))
600                })
601            })
602        }));
603        let source_summaries = build_nested_vecs(source_sum.into_iter().map(|ports| {
604            ports.into_iter().map(|port_conn| {
605                port_conn.into_iter().flat_map(|(port, antichain)| {
606                    antichain.into_iter().map(move |s| (port, s))
607                })
608            })
609        }));
610
611        let scope_outputs = builder.shape[0].0;
612        let output_changes = vec![ChangeBatch::new(); scope_outputs];
613
614        let tracker =
615        Tracker {
616            nodes,
617            edges,
618            target_summaries,
619            source_summaries,
620            per_operator,
621            target_changes: ChangeBatch::new(),
622            source_changes: ChangeBatch::new(),
623            worklist: BinaryHeap::new(),
624            pushed_changes: ChangeBatch::new(),
625            output_changes,
626            total_counts: 0,
627            logger,
628        };
629
630        (tracker, builder_summary)
631    }
632
633    /// Propagates all pending updates.
634    ///
635    /// The method drains `self.input_changes` and circulates their implications
636    /// until we cease deriving new implications.
637    pub fn propagate_all(&mut self) {
638
639        // Step 0: If logging is enabled, construct and log inbound changes.
640        if let Some(logger) = &mut self.logger {
641
642            let target_changes =
643            self.target_changes
644                .iter()
645                .map(|((target, time), diff)| (target.node, target.port, time, *diff));
646
647            logger.log_target_updates(target_changes);
648
649            let source_changes =
650            self.source_changes
651                .iter()
652                .map(|((source, time), diff)| (source.node, source.port, time, *diff));
653
654            logger.log_source_updates(source_changes);
655        }
656
657        // Step 1: Drain `self.input_changes` and determine actual frontier changes.
658        //
659        // Not all changes in `self.input_changes` may alter the frontier at a location.
660        // By filtering the changes through `self.pointstamps` we react only to discrete
661        // changes in the frontier, rather than changes in the pointstamp counts that
662        // witness that frontier.
663        use itertools::Itertools;
664        let mut target_changes = self.target_changes.drain().peekable();
665        while let Some(((target, _), _)) = target_changes.peek() {
666
667            let target = *target;
668            let operator = &mut self.per_operator[target.node].targets[target.port];
669            let target_updates = target_changes.peeking_take_while(|((t, _),_)| t == &target).map(|((_,time),diff)| (time,diff));
670            let changes = operator.pointstamps.update_iter(target_updates);
671
672            for (time, diff) in changes {
673                self.total_counts += diff;
674                for &(output, ref summary) in (&self.target_summaries).get(target.node).get(target.port).into_index_iter() {
675                    if let Some(out_time) = summary.results_in(&time) {
676                        self.output_changes[output].update(out_time, diff);
677                    }
678                }
679                self.worklist.push(Reverse((time, Location::from(target), diff)));
680            }
681        }
682
683        let mut source_changes = self.source_changes.drain().peekable();
684        while let Some(((source, _), _)) = source_changes.peek() {
685
686            let source = *source;
687            let operator = &mut self.per_operator[source.node];
688            let op_source = &mut operator.sources[source.port];
689            let source_updates = source_changes.peeking_take_while(|((s, _),_)| s == &source).map(|((_,time),diff)| (time,diff));
690            let changes = op_source.pointstamps.update_iter(source_updates);
691
692            for (time, diff) in changes {
693                self.total_counts += diff;
694                operator.cap_counts += diff;
695                for &(output, ref summary) in (&self.source_summaries).get(source.node).get(source.port).into_index_iter() {
696                    if let Some(out_time) = summary.results_in(&time) {
697                        self.output_changes[output].update(out_time, diff);
698                    }
699                }
700                self.worklist.push(Reverse((time, Location::from(source), diff)));
701            }
702        }
703
704        // Step 2: Circulate implications of changes to `self.pointstamps`.
705        //
706        // TODO: The argument that this always terminates is subtle, and should be made.
707        //       The intent is that that by moving forward in layers through `time`, we
708        //       will discover zero-change times when we first visit them, as no further
709        //       changes can be made to them once we complete them.
710        while let Some(Reverse((time, location, mut diff))) = self.worklist.pop() {
711
712            // Drain and accumulate all updates that have the same time and location.
713            while self.worklist.peek().map(|x| ((x.0).0 == time) && ((x.0).1 == location)).unwrap_or(false) {
714                diff += (self.worklist.pop().unwrap().0).2;
715            }
716
717            // Only act if there is a net change, positive or negative.
718            if diff != 0 {
719
720                match location.port {
721                    // Update to an operator input.
722                    // Propagate any changes forward across the operator.
723                    Port::Target(port_index) => {
724
725                        let changes =
726                        self.per_operator[location.node]
727                            .targets[port_index]
728                            .implications
729                            .update_iter(Some((time, diff)));
730
731                        for (time, diff) in changes {
732                            for &(output_port, ref summary) in (&self.nodes).get(location.node).get(port_index).into_index_iter() {
733                                if let Some(new_time) = summary.results_in(&time) {
734                                    let source = Location { node: location.node, port: Port::Source(output_port) };
735                                    self.worklist.push(Reverse((new_time, source, diff)));
736                                }
737                            }
738                            self.pushed_changes.update((location, time), diff);
739                        }
740                    }
741                    // Update to an operator output.
742                    // Propagate any changes forward along outgoing edges.
743                    Port::Source(port_index) => {
744
745                        let changes =
746                        self.per_operator[location.node]
747                            .sources[port_index]
748                            .implications
749                            .update_iter(Some((time, diff)));
750
751                        for (time, diff) in changes {
752                            for new_target in (&self.edges).get(location.node).get(port_index).into_index_iter() {
753                                self.worklist.push(Reverse((
754                                    time.clone(),
755                                    Location::from(*new_target),
756                                    diff,
757                                )));
758                            }
759                            self.pushed_changes.update((location, time), diff);
760                        }
761                    },
762                };
763            }
764        }
765    }
766
767    /// Implications of maintained capabilities projected to each output.
768    pub fn pushed_output(&mut self) -> &mut [ChangeBatch<T>] {
769        &mut self.output_changes[..]
770    }
771
772    /// A mutable reference to the pushed results of changes.
773    pub fn pushed(&mut self) -> (&mut ChangeBatch<(Location, T)>, &[PerOperator<T>]) {
774        (&mut self.pushed_changes, &self.per_operator)
775    }
776
777    /// Reveals per-operator frontier state.
778    pub fn node_state(&self, index: usize) -> &PerOperator<T> {
779        &self.per_operator[index]
780    }
781
782    /// Indicates if pointstamp is in the scope-wide frontier.
783    ///
784    /// Such a pointstamp would, if removed from `self.pointstamps`, cause a change
785    /// to `self.implications`, which is what we track for per operator input frontiers.
786    /// If the above do not hold, then its removal either 1. shouldn't be possible,
787    /// or 2. will not affect the output of `self.implications`.
788    pub fn is_global(&self, location: Location, time: &T) -> bool {
789        match location.port {
790            Port::Target(port) => self.per_operator[location.node].targets[port].is_global(time),
791            Port::Source(port) => self.per_operator[location.node].sources[port].is_global(time),
792        }
793    }
794}
795
796/// A sorted map maintained as a single vector of power-of-two sorted runs.
797///
798/// The vector's length always reveals the run structure: the binary
799/// representation of the length, read from the high bit down, gives the
800/// sizes of the sorted runs in order. Adjacent runs may in fact be parts
801/// of larger sorted runs, but we make no attempt to claim those wins.
802///
803/// Keys are distinct across all runs. Novel keys are introduced by
804/// re-sorting the suffix whose run structure their addition changes, as
805/// in binary addition. Each element is re-sorted at most logarithmically
806/// often (so `O(n log^2 n)` comparisons in total, a log factor more than
807/// merging would cost, for much less code), and lookups visit at most
808/// logarithmically many runs.
809struct BinaryRuns<K, V> { entries: Vec<(K, V)> }
810
811impl<K: Ord, V> Default for BinaryRuns<K, V> {
812    fn default() -> Self { Self { entries: Vec::new() } }
813}
814
815impl<K: Ord, V> BinaryRuns<K, V> {
816    /// A mutable reference to the value at `key`, if present.
817    fn get_mut(&mut self, key: &K) -> Option<&mut V> {
818        let mut position = None;
819        let mut offset = 0;
820        for bit in (0..usize::BITS).rev() {
821            let size = 1usize << bit;
822            if self.entries.len() & size != 0 {
823                let run = &self.entries[offset .. offset + size];
824                if let Ok(index) = run.binary_search_by(|(k, _)| k.cmp(key)) {
825                    position = Some(offset + index);
826                    break;
827                }
828                offset += size;
829            }
830        }
831        position.map(|index| &mut self.entries[index].1)
832    }
833
834    /// Introduces a batch of keys distinct from each other and from those present.
835    fn insert_batch(&mut self, batch: Vec<(K, V)>) {
836        if batch.is_empty() { return; }
837        let total = self.entries.len() + batch.len();
838        // Runs at the leading bits on which the lengths agree are unaffected; mask
839        // away the highest differing bit (the xor is non-zero) and below.
840        let stable = total & !(usize::MAX >> (self.entries.len() ^ total).leading_zeros());
841        self.entries.extend(batch);
842        self.entries[stable..].sort_unstable_by(|x, y| x.0.cmp(&y.0));
843    }
844
845    /// Merges all runs into one sorted vector.
846    fn into_sorted(mut self) -> Vec<(K, V)> {
847        self.entries.sort_unstable_by(|x, y| x.0.cmp(&y.0));
848        self.entries
849    }
850}
851
852/// Determines summaries from locations to scope outputs.
853///
854/// Specifically, for each location whose node identifier is non-zero, we compile
855/// the summaries along which they can reach each output.
856///
857/// Graph locations may be missing from the output, in which case they have no
858/// paths to scope outputs. The result is sorted by location.
859fn summarize_outputs<T: Timestamp>(
860    nodes: &[Connectivity<T::Summary>],
861    edges: &[Vec<Vec<Target>>],
862    ) -> Vec<(Location, PortConnectivity<T::Summary>)>
863{
864    // A reverse edge map, to allow us to walk back up the dataflow graph.
865    // Sorted by target location; each target should have at most one source.
866    let mut reverse_edges = Vec::new();
867    for (node, outputs) in edges.iter().enumerate() {
868        for (output, targets) in outputs.iter().enumerate() {
869            for target in targets.iter() {
870                reverse_edges.push((
871                    Location::from(*target),
872                    Location { node, port: Port::Source(output) }
873                ));
874            }
875        }
876    }
877    reverse_edges.sort_unstable();
878    reverse_edges.dedup();
879
880    // A reverse map from operator outputs to inputs, along their internal summaries.
881    // Sorted by source location, so that the entries for a location are contiguous.
882    let mut reverse_internal = Vec::new();
883    for (node, connectivity) in nodes.iter().enumerate() {
884        for (input, outputs) in connectivity.iter().enumerate() {
885            for (output, summary) in outputs.iter_ports() {
886                reverse_internal.push((Location::new_source(node, output), input, summary));
887            }
888        }
889    }
890    reverse_internal.sort_unstable_by(|x, y| (x.0, x.1).cmp(&(y.0, y.1)));
891
892    // Accumulated summaries to scope outputs, keyed by `(location, output)`.
893    let mut accumulated: BinaryRuns<(Location, usize), Antichain<T::Summary>> = BinaryRuns::default();
894
895    // Round-based (semi-naive) fixed point. Each round walks reverse edges and reverse
896    // internal summaries from the triples that changed last round, and the proposals
897    // that improve the accumulated antichains form the next round's work.
898    // The scope may have no outputs, in which case we can do no work.
899    let mut todo: Vec<(Location, usize, T::Summary)> =
900    edges
901        .iter()
902        .flat_map(|x| x.iter())
903        .flat_map(|x| x.iter())
904        .filter(|target| target.node == 0)
905        .map(|target| (Location::from(*target), target.port, Default::default()))
906        .collect();
907
908    let mut proposals: Vec<((Location, usize), T::Summary)> = Vec::new();
909
910    // Loop until we stop discovering novel reachability paths.
911    while !todo.is_empty() {
912
913        // Collect proposed summaries from the triples changed last round.
914        for (location, output, summary) in todo.drain(..) {
915            match location.port {
916
917                // This is an output port of an operator, or a scope input.
918                // We want to crawl up the operator, to its inputs.
919                Port::Source(_output_port) => {
920                    let start = reverse_internal.partition_point(|(source, _, _)| *source < location);
921                    let inputs = reverse_internal[start..].iter().take_while(|(source, _, _)| *source == location);
922                    for (_, input_port, operator_summary) in inputs {
923                        let new_location = Location::new_target(location.node, *input_port);
924                        for op_summary in operator_summary.elements().iter() {
925                            if let Some(combined) = op_summary.followed_by(&summary) {
926                                proposals.push(((new_location, output), combined));
927                            }
928                        }
929                    }
930                }
931
932                // This is an input port of an operator, or a scope output.
933                // We want to walk back the (unique) edge leading to it.
934                Port::Target(_port) => {
935                    if let Ok(index) = reverse_edges.binary_search_by_key(&location, |(target, _)| *target) {
936                        proposals.push(((reverse_edges[index].1, output), summary));
937                    }
938                }
939            }
940        }
941
942        // Merge the batch of proposals into the accumulated summaries. Proposals are
943        // first collapsed per key into an antichain, so that only elements novel to
944        // the accumulated antichain (in an order-independent sense) seed the next round.
945        proposals.sort_unstable_by(|x, y| x.0.cmp(&y.0));
946        let mut fresh: Vec<((Location, usize), Antichain<T::Summary>)> = Vec::new();
947        let mut batch: Antichain<T::Summary> = Antichain::new();
948        let mut iter = proposals.drain(..).peekable();
949        while let Some(((location, output), summary)) = iter.next() {
950            // Collapse this round's proposals for the key into one antichain.
951            batch.insert(summary);
952            while iter.peek().map(|(key, _)| *key == (location, output)).unwrap_or(false) {
953                batch.insert(iter.next().unwrap().1);
954            }
955            if let Some(antichain) = accumulated.get_mut(&(location, output)) {
956                for summary in batch.drain() {
957                    if antichain.insert_ref(&summary) {
958                        todo.push((location, output, summary));
959                    }
960                }
961            }
962            else {
963                todo.extend(batch.elements().iter().map(|summary| (location, output, summary.clone())));
964                fresh.push(((location, output), std::mem::take(&mut batch)));
965            }
966        }
967
968        // Introduce the novel keys.
969        accumulated.insert_batch(fresh);
970    }
971
972    // Merge all runs into one sorted list, and group it by location.
973    let mut results: Vec<(Location, PortConnectivityBuilder<T::Summary>)> = Vec::new();
974    for ((location, output), antichain) in accumulated.into_sorted() {
975        match results.last_mut() {
976            Some((last, connectivity)) if *last == location => { connectivity.add_port(output, antichain); }
977            _ => {
978                let mut connectivity = PortConnectivityBuilder::default();
979                connectivity.add_port(output, antichain);
980                results.push((location, connectivity));
981            }
982        }
983    }
984    results.into_iter().map(|(location, builder)| (location, builder.freeze())).collect()
985}
986
987/// Logging types for reachability tracking events.
988pub mod logging {
989    use std::time::Duration;
990
991    use timely_container::CapacityContainerBuilder;
992    use timely_logging::TypedLogger;
993    use crate::logging_core::Logger;
994
995    /// A container builder for tracker events.
996    pub type TrackerEventBuilder<T> = CapacityContainerBuilder<Vec<(Duration, TrackerEvent<T>)>>;
997
998    /// A logger with additional identifying information about the tracker.
999    pub struct TrackerLogger<T: Clone + 'static> {
1000        identifier: usize,
1001        logger: TypedLogger<TrackerEventBuilder<T>, TrackerEvent<T>>,
1002    }
1003
1004    impl<T: Clone + 'static> TrackerLogger<T> {
1005        /// Create a new tracker logger from its fields.
1006        pub fn new(identifier: usize, logger: Logger<TrackerEventBuilder<T>>) -> Self {
1007            Self { identifier, logger: logger.into() }
1008        }
1009
1010        /// Log source update events with additional identifying information.
1011        pub fn log_source_updates<'a, I>(&mut self, updates: I)
1012        where
1013            I: IntoIterator<Item = (usize, usize, &'a T, i64)>
1014        {
1015            let updates: Vec<_> = updates.into_iter().map(|(a,b,c,d)| (a,b,c.clone(),d)).collect();
1016            if !updates.is_empty() {
1017                self.logger.log({
1018                    SourceUpdate {
1019                        tracker_id: self.identifier,
1020                        updates
1021                    }
1022                });
1023            }
1024        }
1025        /// Log target update events with additional identifying information.
1026        pub fn log_target_updates<'a, I>(&mut self, updates: I)
1027        where
1028            I: IntoIterator<Item = (usize, usize, &'a T, i64)>
1029        {
1030            let updates: Vec<_> = updates.into_iter().map(|(a,b,c,d)| (a,b,c.clone(),d)).collect();
1031            if !updates.is_empty() {
1032                self.logger.log({
1033                    TargetUpdate {
1034                        tracker_id: self.identifier,
1035                        updates
1036                    }
1037                });
1038            }
1039        }
1040    }
1041
1042    /// Events that the tracker may record.
1043    #[derive(Debug, Clone)]
1044    pub enum TrackerEvent<T> {
1045        /// Updates made at a source of data.
1046        SourceUpdate(SourceUpdate<T>),
1047        /// Updates made at a target of data.
1048        TargetUpdate(TargetUpdate<T>),
1049    }
1050
1051    /// An update made at a source of data.
1052    #[derive(Debug, Clone)]
1053    pub struct SourceUpdate<T> {
1054        /// An identifier for the tracker.
1055        pub tracker_id: usize,
1056        /// Updates themselves, as `(node, port, time, diff)`.
1057        pub updates: Vec<(usize, usize, T, i64)>,
1058    }
1059
1060    /// An update made at a target of data.
1061    #[derive(Debug, Clone)]
1062    pub struct TargetUpdate<T> {
1063        /// An identifier for the tracker.
1064        pub tracker_id: usize,
1065        /// Updates themselves, as `(node, port, time, diff)`.
1066        pub updates: Vec<(usize, usize, T, i64)>,
1067    }
1068
1069    impl<T> From<SourceUpdate<T>> for TrackerEvent<T> {
1070        fn from(v: SourceUpdate<T>) -> TrackerEvent<T> { TrackerEvent::SourceUpdate(v) }
1071    }
1072
1073    impl<T> From<TargetUpdate<T>> for TrackerEvent<T> {
1074        fn from(v: TargetUpdate<T>) -> TrackerEvent<T> { TrackerEvent::TargetUpdate(v) }
1075    }
1076}
1077
1078// The Drop implementation for `Tracker` makes sure that reachability logging is correct for
1079// prematurely dropped dataflows. At the moment, this is only possible through `drop_dataflow`,
1080// because in all other cases the tracker stays alive while it has outstanding work, leaving no
1081// remaining work for this Drop implementation.
1082impl<T: Timestamp> Drop for Tracker<T> {
1083    fn drop(&mut self) {
1084        let logger = if let Some(logger) = &mut self.logger {
1085            logger
1086        } else {
1087            // No cleanup necessary when there is no logger.
1088            return;
1089        };
1090
1091        // Retract pending data that `propagate_all` would normally log.
1092        for (index, per_operator) in self.per_operator.iter_mut().enumerate() {
1093            let target_changes = per_operator.targets
1094                .iter_mut()
1095                .enumerate()
1096                .flat_map(|(port, target)| {
1097                    target.pointstamps
1098                        .updates()
1099                        .map(move |(time, diff)| (index, port, time, -diff))
1100                });
1101
1102            logger.log_target_updates(target_changes);
1103
1104            let source_changes = per_operator.sources
1105                .iter_mut()
1106                .enumerate()
1107                .flat_map(|(port, source)| {
1108                    source.pointstamps
1109                        .updates()
1110                        .map(move |(time, diff)| (index, port, time, -diff))
1111                });
1112
1113            logger.log_source_updates(source_changes);
1114        }
1115    }
1116}