Skip to main content

timely/progress/
subgraph.rs

1//! A dataflow subgraph
2//!
3//! Timely dataflow graphs can be nested hierarchically, where some region of
4//! graph is grouped, and presents upwards as an operator. This grouping needs
5//! some care, to make sure that the presented operator reflects the behavior
6//! of the grouped operators.
7
8use std::rc::Rc;
9use std::cell::RefCell;
10use std::collections::BinaryHeap;
11use std::cmp::Reverse;
12
13use crate::logging::TimelyLogger as Logger;
14use crate::logging::TimelySummaryLogger as SummaryLogger;
15
16use crate::scheduling::Schedule;
17use crate::scheduling::activate::Activations;
18
19use crate::progress::frontier::{MutableAntichain, MutableAntichainFilter};
20use crate::progress::{Timestamp, Operate, operate::SharedProgress};
21use crate::progress::{Location, Port, Source, Target};
22use crate::progress::operate::{FrontierInterest, Connectivity, PortConnectivity, PortConnectivityBuilder};
23use crate::progress::ChangeBatch;
24use crate::progress::broadcast::Progcaster;
25use crate::progress::reachability;
26use crate::progress::timestamp::Refines;
27
28use crate::worker::ProgressMode;
29
30// IMPORTANT : by convention, a child identifier of zero is used to indicate inputs and outputs of
31// the Subgraph itself. An identifier greater than zero corresponds to an actual child, which can
32// be found at position (id - 1) in the `children` field of the Subgraph.
33
34/// A builder for interactively initializing a `Subgraph`.
35///
36/// This collects all the information necessary to get a `Subgraph` up and
37/// running, and is important largely through its `build` method which
38/// actually creates a `Subgraph`.
39pub struct SubgraphBuilder<TInner>
40where
41    TInner: Timestamp,
42{
43    /// The name of this subgraph.
44    pub name: String,
45
46    /// A sequence of integers uniquely identifying the subgraph.
47    pub path: Rc<[usize]>,
48
49    /// The index assigned to the subgraph by its parent.
50    index: usize,
51
52    /// A global identifier for this subgraph.
53    identifier: usize,
54
55    // Deferred children: (operator, index, identifier). Built into PerOperatorState at build time.
56    children: Vec<(Box<dyn Operate<TInner>>, usize, usize)>,
57    child_count: usize,
58
59    edge_stash: Vec<(Source, Target)>,
60
61    // shared state written to by the datapath, counting records entering this subgraph instance.
62    input_messages: Vec<Rc<RefCell<ChangeBatch<TInner>>>>,
63
64    // expressed capabilities, used to filter changes against.
65    outputs: usize,
66
67}
68
69impl<TInner> SubgraphBuilder<TInner>
70where
71    TInner: Timestamp,
72{
73    /// Allocates a new input to the subgraph and returns the target to that input in the outer graph.
74    pub fn new_input(&mut self, shared_counts: Rc<RefCell<ChangeBatch<TInner>>>) -> Target {
75        self.input_messages.push(shared_counts);
76        Target::new(self.index, self.input_messages.len() - 1)
77    }
78
79    /// Allocates a new output from the subgraph and returns the source of that output in the outer graph.
80    pub fn new_output(&mut self) -> Source {
81        self.outputs += 1;
82        Source::new(self.index, self.outputs - 1)
83    }
84
85    /// Introduces a dependence from the source to the target.
86    ///
87    /// This method does not effect data movement, but rather reveals to the progress tracking infrastructure
88    /// that messages produced by `source` should be expected to be consumed at `target`.
89    pub fn connect(&mut self, source: Source, target: Target) {
90        self.edge_stash.push((source, target));
91    }
92
93    /// Creates a `SubgraphBuilder` from a path of indexes from the dataflow root to the subgraph,
94    /// terminating with the local index of the new subgraph itself.
95    pub fn new_from(
96        path: Rc<[usize]>,
97        identifier: usize,
98        name: &str,
99    )
100        -> SubgraphBuilder<TInner>
101    {
102        let index = path[path.len() - 1];
103
104        SubgraphBuilder {
105            name: name.to_owned(),
106            path,
107            index,
108            identifier,
109            children: Vec::new(),
110            child_count: 1,
111            edge_stash: Vec::new(),
112            input_messages: Vec::new(),
113            outputs: 0,
114        }
115    }
116
117    /// Allocates a new child identifier, for later use.
118    pub fn allocate_child_id(&mut self) -> usize {
119        self.child_count += 1;
120        self.child_count - 1
121    }
122
123    /// Adds a new child to the subgraph.
124    ///
125    /// The child will be initialized and logged when [`build`] is called.
126    pub fn add_child(&mut self, child: Box<dyn Operate<TInner>>, index: usize, identifier: usize) {
127        self.children.push((child, index, identifier));
128    }
129
130    /// Now that initialization is complete, actually build a subgraph.
131    pub fn build<TOuter: Timestamp>(mut self, worker: &crate::worker::Worker) -> Subgraph<TOuter, TInner> {
132        // at this point, the subgraph is frozen. we should initialize any internal state which
133        // may have been determined after construction (e.g. the numbers of inputs and outputs).
134        // we also need to determine what to return as a summary and initial capabilities, which
135        // will depend on child summaries and capabilities, as well as edges in the subgraph.
136
137        let inputs = self.input_messages.len();
138        let outputs = self.outputs;
139
140        let type_name = std::any::type_name::<TInner>();
141        let mut logging = worker.logging();
142        let mut summary_logging = worker.logger_for(&format!("timely/summary/{type_name}"));
143
144        // Sort stashed children by index, and preface with a child zero mirroring the subgraph shape.
145        self.children.sort_unstable_by_key(|&(_, index, _)| index);
146        let mut children: Vec<_> = [PerOperatorState::empty(outputs, inputs)]
147            .into_iter()
148            .chain(self.children.into_iter().map(|(operator, index, identifier)| {
149                let child = PerOperatorState::new(operator, index, identifier, logging.clone(), &mut summary_logging);
150                if let Some(l) = &mut logging {
151                    let mut child_path = Vec::with_capacity(self.path.len() + 1);
152                    child_path.extend_from_slice(&self.path[..]);
153                    child_path.push(index);
154                    l.log(crate::logging::OperatesEvent {
155                        id: identifier,
156                        addr: child_path,
157                        name: child.name.to_owned(),
158                    });
159                }
160                child
161            }))
162            .collect();
163        assert!(children.iter().enumerate().all(|(i,x)| i == x.index));
164
165        let mut builder = reachability::Builder::new();
166
167        // Child 0 has `inputs` outputs and `outputs` inputs, not yet connected.
168        let summary = (0..outputs).map(|_| PortConnectivity::default()).collect();
169        builder.add_node(0, outputs, inputs, summary);
170        for (index, child) in children.iter_mut().enumerate().skip(1) {
171            let summary = std::mem::take(&mut child.internal_summary);
172            builder.add_node(index, child.inputs, child.outputs, summary);
173        }
174
175        for (source, target) in self.edge_stash {
176            children[source.node].edges[source.port].push(target);
177            builder.add_edge(source, target);
178        }
179
180        let reachability_logging =
181        worker.logger_for(&format!("timely/reachability/{type_name}"))
182              .map(|logger| reachability::logging::TrackerLogger::new(self.identifier, logger));
183        let progress_logging = worker.logger_for(&format!("timely/progress/{type_name}"));
184        let (tracker, scope_summary) = builder.build(reachability_logging);
185
186        let progcaster = Progcaster::new(worker, Rc::clone(&self.path), self.identifier, logging, progress_logging);
187
188        let mut incomplete = vec![true; children.len()];
189        incomplete[0] = false;
190        let incomplete_count = incomplete.len() - 1;
191
192        let activations = worker.activations();
193
194        activations.borrow_mut().activate(&self.path[..]);
195
196        // The subgraph's per-input interest is conservatively the max across all children's inputs.
197        let max_interest = children.iter()
198            .flat_map(|c| c.notify.iter().copied())
199            .max()
200            .unwrap_or(FrontierInterest::Never);
201        let notify_me: Vec<FrontierInterest> = vec![max_interest; inputs];
202
203        Subgraph {
204            name: self.name,
205            path: self.path,
206            inputs,
207            outputs,
208            incomplete,
209            incomplete_count,
210            activations,
211            temp_active: BinaryHeap::new(),
212            maybe_shutdown: Vec::new(),
213            children,
214            input_messages: self.input_messages,
215            output_capabilities: vec![MutableAntichain::new(); self.outputs],
216
217            local_pointstamp: ChangeBatch::new(),
218            final_pointstamp: ChangeBatch::new(),
219            progcaster,
220            pointstamp_tracker: tracker,
221
222            shared_progress: Rc::new(RefCell::new(SharedProgress::new(inputs, outputs))),
223            scope_summary,
224
225            progress_mode: worker.config().progress_mode,
226            notify_me,
227        }
228    }
229}
230
231
232/// A dataflow subgraph.
233///
234/// The subgraph type contains the infrastructure required to describe the topology of and track
235/// progress within a dataflow subgraph.
236pub struct Subgraph<TOuter, TInner>
237where
238    TOuter: Timestamp,
239    TInner: Timestamp,
240{
241    name: String,           // an informative name.
242    /// Path of identifiers from the root.
243    pub path: Rc<[usize]>,
244    inputs: usize,          // number of inputs.
245    outputs: usize,         // number of outputs.
246
247    // handles to the children of the scope. index i corresponds to entry i-1, unless things change.
248    children: Vec<PerOperatorState<TInner>>,
249
250    incomplete: Vec<bool>,   // the incompletion status of each child.
251    incomplete_count: usize, // the number of incomplete children.
252
253    // shared activations (including children).
254    activations: Rc<RefCell<Activations>>,
255    temp_active: BinaryHeap<Reverse<usize>>,
256    maybe_shutdown: Vec<usize>,
257
258    // shared state written to by the datapath, counting records entering this subgraph instance.
259    input_messages: Vec<Rc<RefCell<ChangeBatch<TInner>>>>,
260
261    // expressed capabilities, used to filter changes against.
262    output_capabilities: Vec<MutableAntichain<TOuter>>,
263
264    // pointstamp messages to exchange. ultimately destined for `messages` or `internal`.
265    local_pointstamp: ChangeBatch<(Location, TInner)>,
266    final_pointstamp: ChangeBatch<(Location, TInner)>,
267
268    // Graph structure and pointstamp tracker.
269    // pointstamp_builder: reachability::Builder<TInner>,
270    pointstamp_tracker: reachability::Tracker<TInner>,
271
272    // channel / whatever used to communicate pointstamp updates to peers.
273    progcaster: Progcaster<TInner>,
274
275    shared_progress: Rc<RefCell<SharedProgress<TOuter>>>,
276    scope_summary: Connectivity<TInner::Summary>,
277
278    progress_mode: ProgressMode,
279
280    notify_me: Vec<FrontierInterest>,
281}
282
283impl<TOuter, TInner> Schedule for Subgraph<TOuter, TInner>
284where
285    TOuter: Timestamp,
286    TInner: Timestamp+Refines<TOuter>,
287{
288    fn name(&self) -> &str { &self.name }
289
290    fn path(&self) -> &[usize] { &self.path }
291
292    fn schedule(&mut self) -> bool {
293
294        // This method performs several actions related to progress tracking
295        // and child operator scheduling. The actions have been broken apart
296        // into atomic actions that should be able to be safely executed in
297        // isolation, by a potentially clueless user (yours truly).
298
299        self.accept_frontier();         // Accept supplied frontier changes.
300        self.harvest_inputs();          // Count records entering the scope.
301
302        // Receive post-exchange progress updates.
303        self.progcaster.recv(&mut self.final_pointstamp);
304
305        // Commit and propagate final pointstamps.
306        self.propagate_pointstamps();
307
308        {   // Enqueue active children; scoped to let borrow drop.
309            let temp_active = &mut self.temp_active;
310            self.activations
311                .borrow_mut()
312                .for_extensions(&self.path[..], |index| temp_active.push(Reverse(index)));
313        }
314
315        // Schedule child operators.
316        //
317        // We should be able to schedule arbitrary subsets of children, as
318        // long as we eventually schedule all children that need to do work.
319        let mut previous = 0;
320        while let Some(Reverse(index)) = self.temp_active.pop() {
321            // De-duplicate, and don't revisit.
322            if index > previous {
323                // TODO: This is a moment where a scheduling decision happens.
324                self.activate_child(index);
325                previous = index;
326            }
327        }
328
329        // Transmit produced progress updates.
330        self.send_progress();
331
332        // If child scopes surface more final pointstamp updates we must re-execute.
333        if !self.final_pointstamp.is_empty() {
334            self.activations.borrow_mut().activate(&self.path[..]);
335        }
336
337        // A subgraph is incomplete if any child is incomplete, or there are outstanding messages.
338        let incomplete = self.incomplete_count > 0;
339        let tracking = self.pointstamp_tracker.tracking_anything();
340
341        incomplete || tracking
342    }
343}
344
345
346impl<TOuter, TInner> Subgraph<TOuter, TInner>
347where
348    TOuter: Timestamp,
349    TInner: Timestamp+Refines<TOuter>,
350{
351    /// Schedules a child operator and collects progress statements.
352    ///
353    /// The return value indicates that the child task cannot yet shut down.
354    fn activate_child(&mut self, child_index: usize) -> bool {
355
356        let child = &mut self.children[child_index];
357
358        let incomplete = child.schedule();
359
360        if incomplete != self.incomplete[child_index] {
361            if incomplete { self.incomplete_count += 1; }
362            else          { self.incomplete_count -= 1; }
363            self.incomplete[child_index] = incomplete;
364        }
365
366        if !incomplete {
367            // Consider shutting down the child, if neither capabilities nor input frontier.
368            let child_state = self.pointstamp_tracker.node_state(child_index);
369            let frontiers_empty = child_state.targets.iter().all(|x| x.implications.is_empty());
370            let no_capabilities = child_state.sources.iter().all(|x| x.pointstamps.is_empty());
371            if frontiers_empty && no_capabilities {
372                child.shut_down();
373            }
374        }
375        else {
376            // In debug mode, check that the progress statements do not violate invariants.
377            #[cfg(debug_assertions)] {
378                child.validate_progress(self.pointstamp_tracker.node_state(child_index));
379            }
380        }
381
382        // Extract progress statements into either pre- or post-exchange buffers.
383        if child.local {
384            child.extract_progress(&mut self.local_pointstamp, &mut self.temp_active);
385        }
386        else {
387            child.extract_progress(&mut self.final_pointstamp, &mut self.temp_active);
388        }
389
390        incomplete
391    }
392
393    /// Move frontier changes from parent into progress statements.
394    fn accept_frontier(&mut self) {
395        for (port, changes) in self.shared_progress.borrow_mut().frontiers.iter_mut().enumerate() {
396            let source = Source::new(0, port);
397            for (time, value) in changes.drain() {
398                self.pointstamp_tracker.update_source(
399                    source,
400                    TInner::to_inner(time),
401                    value
402                );
403            }
404        }
405    }
406
407    /// Collects counts of records entering the scope.
408    ///
409    /// This method moves message counts from the output of child zero to the inputs to
410    /// attached operators. This is a bit of a hack, because normally one finds capabilities
411    /// at an operator output, rather than message counts. These counts are used only at
412    /// mark [XXX] where they are reported upwards to the parent scope.
413    fn harvest_inputs(&mut self) {
414        for input in 0 .. self.inputs {
415            let source = Location::new_source(0, input);
416            let mut borrowed = self.input_messages[input].borrow_mut();
417            for (time, delta) in borrowed.drain() {
418                for target in &self.children[0].edges[input] {
419                    self.local_pointstamp.update((Location::from(*target), time.clone()), delta);
420                }
421                self.local_pointstamp.update((source, time), -delta);
422            }
423        }
424    }
425
426    /// Commits pointstamps in `self.final_pointstamp`.
427    ///
428    /// This method performs several steps that for reasons of correctness must
429    /// be performed atomically, before control is returned. These are:
430    ///
431    /// 1. Changes to child zero's outputs are reported as consumed messages.
432    /// 2. Changes to child zero's inputs are reported as produced messages.
433    /// 3. Frontiers for child zero's inputs are reported as internal capabilities.
434    ///
435    /// Perhaps importantly, the frontiers for child zero are determined *without*
436    /// the messages that are produced for child zero inputs, as we only want to
437    /// report retained internal capabilities, and not now-external messages.
438    ///
439    /// In the course of propagating progress changes, we also propagate progress
440    /// changes for all of the managed child operators.
441    fn propagate_pointstamps(&mut self) {
442
443        // Process exchanged pointstamps. Handle child 0 statements carefully.
444        for ((location, timestamp), delta) in self.final_pointstamp.drain() {
445
446            // Child 0 corresponds to the parent scope and has special handling.
447            if location.node == 0 {
448                match location.port {
449                    // [XXX] Report child 0's capabilities as consumed messages.
450                    //       Note the re-negation of delta, to make counts positive.
451                    Port::Source(scope_input) => {
452                        self.shared_progress
453                            .borrow_mut()
454                            .consumeds[scope_input]
455                            .update(timestamp.to_outer(), -delta);
456                    },
457                    // [YYY] Report child 0's input messages as produced messages.
458                    //       Do not otherwise record, as we will not see subtractions,
459                    //       and we do not want to present their implications upward.
460                    Port::Target(scope_output) => {
461                        self.shared_progress
462                            .borrow_mut()
463                            .produceds[scope_output]
464                            .update(timestamp.to_outer(), delta);
465                    },
466                }
467            }
468            else {
469                self.pointstamp_tracker.update(location, timestamp, delta);
470            }
471        }
472
473        // Propagate implications of progress changes.
474        self.pointstamp_tracker.propagate_all();
475
476        // Drain propagated information into shared progress structure.
477        let (pushed, operators) = self.pointstamp_tracker.pushed();
478        for ((location, time), diff) in pushed.drain() {
479            self.maybe_shutdown.push(location.node);
480            // Targets are actionable, sources are not.
481            if let crate::progress::Port::Target(port) = location.port {
482                // Activate based on expressed frontier interest for this input.
483                let activate = match self.children[location.node].notify[port] {
484                    FrontierInterest::Always => true,
485                    FrontierInterest::IfCapability => { operators[location.node].cap_counts > 0 }
486                    FrontierInterest::Never => false,
487                };
488                if activate { self.temp_active.push(Reverse(location.node)); }
489
490                // Keep this current independent of the interest.
491                self.children[location.node]
492                    .shared_progress
493                    .borrow_mut()
494                    .frontiers[port]
495                    .update(time, diff);
496            }
497        }
498
499        // Consider scheduling each recipient of progress information to shut down.
500        self.maybe_shutdown.sort_unstable();
501        self.maybe_shutdown.dedup();
502        for child_index in self.maybe_shutdown.drain(..) {
503            let child_state = self.pointstamp_tracker.node_state(child_index);
504            let frontiers_empty = child_state.targets.iter().all(|x| x.implications.is_empty());
505            let no_capabilities = child_state.cap_counts == 0;
506            if frontiers_empty && no_capabilities {
507                self.temp_active.push(Reverse(child_index));
508            }
509        }
510
511        // Extract child zero frontier changes and report as internal capability changes.
512        for (output, internal) in self.shared_progress.borrow_mut().internals.iter_mut().enumerate() {
513            self.pointstamp_tracker
514                .pushed_output()[output]
515                .drain()
516                .map(|(time, diff)| (time.to_outer(), diff))
517                .filter_through(&mut self.output_capabilities[output])
518                .for_each(|(time, diff)| internal.update(time, diff));
519        }
520    }
521
522    /// Sends local progress updates to all workers.
523    ///
524    /// This method does not guarantee that all of `self.local_pointstamps` are
525    /// sent, but that no blocking pointstamps remain
526    fn send_progress(&mut self) {
527
528        // If we are requested to eagerly send progress updates, or if there are
529        // updates visible in the scope-wide frontier, we must send all updates.
530        let must_send = self.progress_mode == ProgressMode::Eager || {
531            let tracker = &mut self.pointstamp_tracker;
532            self.local_pointstamp
533                .iter()
534                .any(|((location, time), diff)|
535                    // Must publish scope-wide visible subtractions.
536                    tracker.is_global(*location, time) && *diff < 0 ||
537                    // Must confirm the receipt of inbound messages.
538                    location.node == 0
539                )
540        };
541
542        if must_send {
543            self.progcaster.send(&mut self.local_pointstamp);
544        }
545    }
546}
547
548
549impl<TOuter, TInner> Operate<TOuter> for Subgraph<TOuter, TInner>
550where
551    TOuter: Timestamp,
552    TInner: Timestamp+Refines<TOuter>,
553{
554    fn local(&self) -> bool { false }
555    fn inputs(&self)  -> usize { self.inputs }
556    fn outputs(&self) -> usize { self.outputs }
557
558    // produces connectivity summaries from inputs to outputs, and reports initial internal
559    // capabilities on each of the outputs (projecting capabilities from contained scopes).
560    fn initialize(mut self: Box<Self>) -> (Connectivity<TOuter::Summary>, Rc<RefCell<SharedProgress<TOuter>>>, Box<dyn Schedule>) {
561
562        // double-check that child 0 (the outside world) is correctly shaped.
563        assert_eq!(self.children[0].outputs, self.inputs());
564        assert_eq!(self.children[0].inputs, self.outputs());
565
566        // Note that we need to have `self.inputs()` elements in the summary
567        // with each element containing `self.outputs()` antichains regardless
568        // of how long `self.scope_summary` is
569        let mut internal_summary = vec![PortConnectivityBuilder::default(); self.inputs()];
570        for (input_idx, input) in std::mem::take(&mut self.scope_summary).into_iter().enumerate() {
571            for (output_idx, output) in input {
572                for outer in output.into_iter().map(TInner::summarize) {
573                    internal_summary[input_idx].insert(output_idx, outer);
574                }
575            }
576        }
577        let internal_summary: Connectivity<_> = internal_summary.into_iter().map(|b| b.freeze()).collect();
578
579        debug_assert_eq!(
580            internal_summary.len(),
581            self.inputs(),
582            "the internal summary should have as many elements as there are inputs",
583        );
584        debug_assert!(
585            internal_summary.iter().all(|os| os.iter_ports().all(|(o,_)| o < self.outputs())),
586            "each element of the internal summary should only reference valid outputs",
587        );
588
589        // Each child has expressed initial capabilities (their `shared_progress.internals`).
590        // We introduce these into the progress tracker to determine the scope's initial
591        // internal capabilities.
592        for child in self.children.iter_mut() {
593            child.extract_progress(&mut self.final_pointstamp, &mut self.temp_active);
594        }
595
596        self.propagate_pointstamps();  // Propagate expressed capabilities to output frontiers.
597
598        // Return summaries and shared progress information.
599        (internal_summary, Rc::clone(&self.shared_progress), self)
600    }
601
602    fn notify_me(&self) -> &[FrontierInterest] { &self.notify_me }
603}
604
605struct PerOperatorState<T: Timestamp> {
606
607    name: String,       // name of the operator
608    index: usize,       // index of the operator within its parent scope
609    id: usize,          // worker-unique identifier
610
611    local: bool,        // indicates whether the operator will exchange data or not
612    notify: Vec<FrontierInterest>,
613    inputs: usize,      // number of inputs to the operator
614    outputs: usize,     // number of outputs from the operator
615
616    operator: Option<Box<dyn Schedule>>,
617
618    edges: Vec<Vec<Target>>,    // edges from the outputs of the operator
619
620    shared_progress: Rc<RefCell<SharedProgress<T>>>,
621
622    internal_summary: Connectivity<T::Summary>,   // from initialize; moved into the reachability builder.
623
624    logging: Option<Logger>,
625}
626
627impl<T: Timestamp> PerOperatorState<T> {
628
629    fn empty(inputs: usize, outputs: usize) -> PerOperatorState<T> {
630        PerOperatorState {
631            name:       "External".to_owned(),
632            operator:   None,
633            index:      0,
634            id:         usize::MAX,
635            local:      false,
636            notify:     vec![FrontierInterest::IfCapability; inputs],
637            inputs,
638            outputs,
639
640            edges: vec![Vec::new(); outputs],
641
642            logging: None,
643
644            shared_progress: Rc::new(RefCell::new(SharedProgress::new(inputs,outputs))),
645            internal_summary: Vec::new(),
646        }
647    }
648
649    pub fn new(
650        scope: Box<dyn Operate<T>>,
651        index: usize,
652        identifier: usize,
653        logging: Option<Logger>,
654        summary_logging: &mut Option<SummaryLogger<T::Summary>>,
655    ) -> PerOperatorState<T>
656    {
657        let local = scope.local();
658        let inputs = scope.inputs();
659        let outputs = scope.outputs();
660        let notify = scope.notify_me().to_vec();
661
662        let (internal_summary, shared_progress, operator) = scope.initialize();
663
664        if let Some(l) = summary_logging {
665            l.log(crate::logging::OperatesSummaryEvent {
666                id: identifier,
667                summary: internal_summary.clone(),
668            })
669        }
670
671        assert_eq!(
672            internal_summary.len(),
673            inputs,
674            "operator summary has {} inputs when {} were expected",
675            internal_summary.len(),
676            inputs,
677        );
678        assert!(
679            internal_summary.iter().all(|os| os.iter_ports().all(|(o,_)| o < outputs)),
680            "operator summary references invalid output port",
681        );
682
683        PerOperatorState {
684            name:               operator.name().to_owned(),
685            operator:           Some(operator),
686            index,
687            id:                 identifier,
688            local,
689            notify,
690            inputs,
691            outputs,
692            edges:              vec![vec![]; outputs],
693
694            logging,
695
696            shared_progress,
697            internal_summary,
698        }
699    }
700
701    pub fn schedule(&mut self) -> bool {
702
703        if let Some(ref mut operator) = self.operator {
704
705            // Perhaps log information about the start of the schedule call.
706            if let Some(l) = self.logging.as_mut() {
707                // FIXME: There is no contract that the operator must consume frontier changes.
708                //        This report could be spurious.
709                // TODO:  Perhaps fold this in to `ScheduleEvent::start()` as a "reason"?
710                let frontiers = &mut self.shared_progress.borrow_mut().frontiers[..];
711                if frontiers.iter_mut().any(|buffer| !buffer.is_empty()) {
712                    l.log(crate::logging::PushProgressEvent { op_id: self.id })
713                }
714
715                l.log(crate::logging::ScheduleEvent::start(self.id));
716            }
717
718            let incomplete = operator.schedule();
719
720            // Perhaps log information about the stop of the schedule call.
721            if let Some(l) = self.logging.as_mut() {
722                l.log(crate::logging::ScheduleEvent::stop(self.id));
723            }
724
725            incomplete
726        }
727        else {
728
729            // If the operator is closed and we are reporting progress at it, something has surely gone wrong.
730            if self.shared_progress.borrow_mut().frontiers.iter_mut().any(|x| !x.is_empty()) {
731                println!("Operator prematurely shut down: {}", self.name);
732                println!("  {:?}", self.notify);
733                println!("  {:?}", self.shared_progress.borrow_mut().frontiers);
734                panic!();
735            }
736
737            // A closed operator shouldn't keep anything open.
738            false
739        }
740    }
741
742    fn shut_down(&mut self) {
743        if self.operator.is_some() {
744            if let Some(l) = self.logging.as_mut() {
745                l.log(crate::logging::ShutdownEvent{ id: self.id });
746            }
747            self.operator = None;
748        }
749    }
750
751    /// Extracts shared progress information and converts to pointstamp changes.
752    fn extract_progress(&self, pointstamps: &mut ChangeBatch<(Location, T)>, temp_active: &mut BinaryHeap<Reverse<usize>>) {
753
754        let shared_progress = &mut *self.shared_progress.borrow_mut();
755
756        // Migrate consumeds, internals, produceds into progress statements.
757        for (input, consumed) in shared_progress.consumeds.iter_mut().enumerate() {
758            let target = Location::new_target(self.index, input);
759            for (time, delta) in consumed.drain() {
760                pointstamps.update((target, time), -delta);
761            }
762        }
763        for (output, internal) in shared_progress.internals.iter_mut().enumerate() {
764            let source = Location::new_source(self.index, output);
765            for (time, delta) in internal.drain() {
766                pointstamps.update((source, time), delta);
767            }
768        }
769        for (output, produced) in shared_progress.produceds.iter_mut().enumerate() {
770            for (time, delta) in produced.drain() {
771                if let Some((last, rest)) = self.edges[output].split_last() {
772                    for target in rest {
773                        pointstamps.update((Location::from(*target), time.clone()), delta);
774                        temp_active.push(Reverse(target.node));
775                    }
776                    pointstamps.update((Location::from(*last), time), delta);
777                    temp_active.push(Reverse(last.node));
778                }
779            }
780        }
781    }
782
783    /// Test the validity of `self.shared_progress`.
784    ///
785    /// The validity of shared progress information depends on both the external frontiers and the
786    /// internal capabilities, as events can occur that cannot be explained locally otherwise.
787    #[allow(dead_code)]
788    fn validate_progress(&self, child_state: &reachability::PerOperator<T>) {
789
790        let shared_progress = &mut *self.shared_progress.borrow_mut();
791
792        // Increments to internal capabilities require a consumed input message, a
793        for (output, internal) in shared_progress.internals.iter_mut().enumerate() {
794            for (time, diff) in internal.iter() {
795                if *diff > 0 {
796                    let consumed = shared_progress.consumeds.iter_mut().any(|x| x.iter().any(|(t,d)| *d > 0 && t.less_equal(time)));
797                    let internal = child_state.sources[output].implications.less_equal(time);
798                    if !consumed && !internal {
799                        println!("Increment at {:?}, not supported by\n\tconsumed: {:?}\n\tinternal: {:?}", time, shared_progress.consumeds, child_state.sources[output].implications);
800                        panic!("Progress error; internal {:?}", self.name);
801                    }
802                }
803            }
804        }
805        for (output, produced) in shared_progress.produceds.iter_mut().enumerate() {
806            for (time, diff) in produced.iter() {
807                if *diff > 0 {
808                    let consumed = shared_progress.consumeds.iter_mut().any(|x| x.iter().any(|(t,d)| *d > 0 && t.less_equal(time)));
809                    let internal = child_state.sources[output].implications.less_equal(time);
810                    if !consumed && !internal {
811                        println!("Increment at {:?}, not supported by\n\tconsumed: {:?}\n\tinternal: {:?}", time, shared_progress.consumeds, child_state.sources[output].implications);
812                        panic!("Progress error; produced {:?}", self.name);
813                    }
814                }
815            }
816        }
817    }
818}
819
820// Explicitly shut down the operator to get logged information.
821impl<T: Timestamp> Drop for PerOperatorState<T> {
822    fn drop(&mut self) {
823        self.shut_down();
824    }
825}