Skip to main content

timely/dataflow/operators/generic/
builder_rc.rs

1//! Types to build operators with general shapes.
2
3use std::rc::Rc;
4use std::cell::{OnceCell, RefCell};
5use std::default::Default;
6
7use crate::progress::{ChangeBatch, Timestamp};
8use crate::progress::operate::SharedProgress;
9use crate::progress::frontier::{Antichain, MutableAntichain};
10
11use crate::Container;
12use crate::dataflow::{Scope, Stream};
13use crate::dataflow::channels::pushers::Counter as PushCounter;
14use crate::dataflow::channels::pushers;
15use crate::dataflow::channels::pact::ParallelizationContract;
16use crate::dataflow::channels::pullers::Counter as PullCounter;
17use crate::dataflow::operators::capability::Capability;
18use crate::dataflow::operators::generic::handles::{InputHandleCore, new_input_handle};
19use crate::dataflow::operators::generic::operator_info::OperatorInfo;
20use crate::dataflow::operators::generic::builder_raw::OperatorShape;
21use crate::progress::operate::{FrontierInterest, PortConnectivity, PortConnectivityBuilder};
22
23use super::builder_raw::OperatorBuilder as OperatorBuilderRaw;
24
25/// Builds operators with generic shape.
26#[derive(Debug)]
27pub struct OperatorBuilder<'scope, T: Timestamp> {
28    builder: OperatorBuilderRaw<'scope, T>,
29    frontier: Vec<MutableAntichain<T>>,
30    consumed: Vec<Rc<RefCell<ChangeBatch<T>>>>,
31    internal: Rc<RefCell<Vec<Rc<RefCell<ChangeBatch<T>>>>>>,
32    /// For each input, a shared cell from which input handles and capabilities
33    /// read the summaries to each output at runtime, and the builder in which
34    /// the summaries accumulate during construction. The cell is set once, from
35    /// the builder, when the operator is built.
36    summaries: Vec<(Rc<OnceCell<PortConnectivity<<T as Timestamp>::Summary>>>, PortConnectivityBuilder<<T as Timestamp>::Summary>)>,
37    produced: Vec<Rc<RefCell<ChangeBatch<T>>>>,
38}
39
40impl<'scope, T: Timestamp> OperatorBuilder<'scope, T> {
41
42    /// Allocates a new generic operator builder from its containing scope.
43    pub fn new(name: String, scope: Scope<'scope, T>) -> Self {
44        OperatorBuilder {
45            builder: OperatorBuilderRaw::new(name, scope),
46            frontier: Vec::new(),
47            consumed: Vec::new(),
48            internal: Rc::new(RefCell::new(Vec::new())),
49            summaries: Vec::new(),
50            produced: Vec::new(),
51        }
52    }
53
54    /// Sets frontier interest for a specific input.
55    pub fn set_notify_for(&mut self, input: usize, notify: FrontierInterest) {
56        self.builder.set_notify_for(input, notify);
57    }
58
59    /// Adds a new input to a generic operator builder, returning the `Pull` implementor to use.
60    pub fn new_input<C: Container, P>(&mut self, stream: Stream<'scope, T, C>, pact: P) -> InputHandleCore<T, C, P::Puller>
61    where
62        P: ParallelizationContract<T, C> {
63
64        let connection = (0..self.builder.shape().outputs()).map(|o| (o, Antichain::from_elem(Default::default())));
65        self.new_input_connection(stream, pact, connection)
66    }
67
68    /// Adds a new input with connection information to a generic operator builder, returning the `Pull` implementor to use.
69    ///
70    /// The `connection` parameter contains promises made by the operator for each of the existing *outputs*, that any timestamp
71    /// appearing at the input, any output timestamp will be greater than or equal to the input timestamp subjected to a `Summary`
72    /// greater or equal to some element of the corresponding antichain in `connection`.
73    ///
74    /// Commonly the connections are either the unit summary, indicating the same timestamp might be produced as output, or an empty
75    /// antichain indicating that there is no connection from the input to the output.
76    pub fn new_input_connection<C: Container, P, I>(&mut self, stream: Stream<'scope, T, C>, pact: P, connection: I) -> InputHandleCore<T, C, P::Puller>
77    where
78        P: ParallelizationContract<T, C>,
79        I: IntoIterator<Item = (usize, Antichain<<T as Timestamp>::Summary>)> + Clone,
80    {
81        let puller = self.builder.new_input_connection(stream, pact, connection.clone());
82
83        let input = PullCounter::new(puller);
84        self.frontier.push(MutableAntichain::new());
85        self.consumed.push(Rc::clone(input.consumed()));
86
87        let shared_summary = Rc::new(OnceCell::new());
88        self.summaries.push((Rc::clone(&shared_summary), connection.into_iter().collect()));
89
90        new_input_handle(input, Rc::clone(&self.internal), shared_summary)
91    }
92
93    /// Adds a new output to a generic operator builder, returning the `Push` implementor to use.
94    pub fn new_output<C: Container>(&mut self) -> (pushers::Output<T, C>, Stream<'scope, T, C>) {
95        let connection = (0..self.builder.shape().inputs()).map(|i| (i, Antichain::from_elem(Default::default())));
96        self.new_output_connection(connection)
97    }
98
99    /// Adds a new output with connection information to a generic operator builder, returning the `Push` implementor to use.
100    ///
101    /// The `connection` parameter contains promises made by the operator for each of the existing *inputs*, that any timestamp
102    /// appearing at the input, any output timestamp will be greater than or equal to the input timestamp subjected to a `Summary`
103    /// greater or equal to some element of the corresponding antichain in `connection`.
104    ///
105    /// Commonly the connections are either the unit summary, indicating the same timestamp might be produced as output, or an empty
106    /// antichain indicating that there is no connection from the input to the output.
107    pub fn new_output_connection<C: Container, I>(&mut self, connection: I) -> (pushers::Output<T, C>, Stream<'scope, T, C>)
108    where
109        I: IntoIterator<Item = (usize, Antichain<<T as Timestamp>::Summary>)> + Clone,
110    {
111        let new_output = self.shape().outputs();
112        let (tee, stream) = self.builder.new_output_connection(connection.clone());
113
114        let internal = Rc::new(RefCell::new(ChangeBatch::new()));
115        self.internal.borrow_mut().push(Rc::clone(&internal));
116
117        let counter = PushCounter::new(tee);
118        self.produced.push(Rc::clone(counter.produced()));
119
120        for (input, entry) in connection {
121            self.summaries[input].1.add_port(new_output, entry);
122        }
123
124        (pushers::Output::new(counter, internal, new_output), stream)
125    }
126
127    /// Creates an operator implementation from supplied logic constructor.
128    pub fn build<B, L>(self, constructor: B)
129    where
130        B: FnOnce(Vec<Capability<T>>) -> L,
131        L: FnMut(&[MutableAntichain<T>])+'static
132    {
133        self.build_reschedule(|caps| {
134            let mut logic = constructor(caps);
135            move |frontier| { logic(frontier); false }
136        })
137    }
138
139    /// Creates an operator implementation from supplied logic constructor.
140    ///
141    /// Unlike `build`, the supplied closure can indicate if the operator
142    /// should be considered incomplete. A not-incomplete operator will be
143    /// shut down if it has empty input frontiers and holds no capabilities.
144    /// Flagging oneself as incomplete is most commonly used by operators
145    /// that manage external resources like file writes or transactions that
146    /// must complete before the operator should be shut down.
147    ///
148    /// This method boxes `B` and `L` and delegates to [`build_reschedule_boxed`].
149    /// For the fully generic (non-boxing) path, see [`build_reschedule_typed`].
150    pub fn build_reschedule<B, L>(self, constructor: B)
151    where
152        B: FnOnce(Vec<Capability<T>>) -> L,
153        L: FnMut(&[MutableAntichain<T>])->bool+'static
154    {
155        self.build_reschedule_boxed(Box::new(|caps| -> Box<dyn FnMut(&[MutableAntichain<T>])->bool> { Box::new(constructor(caps)) }));
156    }
157
158    /// Like `build_reschedule`, but with a pre-boxed constructor.
159    ///
160    /// This method exists primarily to force the `Box<dyn ...>` coercions, which
161    /// can otherwise easily be `Box<B>` or `Box<L>` for specialized `B` and `L` instead.
162    pub fn build_reschedule_boxed<'a>(self, constructor: Box<dyn FnOnce(Vec<Capability<T>>) -> Box<dyn FnMut(&[MutableAntichain<T>])->bool> + 'a>) {
163        self.build_reschedule_typed(constructor);
164    }
165
166    /// Like `build_reschedule`, but specialized to the closure types `B` and `L`.
167    ///
168    /// This method is instantiated once per distinct `(B, L)` pair, and one
169    /// should be mindful of monomorphization bloat. Callers with many closures
170    /// should consider erasing their variation, for example via `Box<dyn ...>`.
171    ///
172    /// This method calls `build_typed` directly using a new closure, mirroring
173    /// the variation in `L`, rather than forcing it to be reboxed via `build`.
174    pub fn build_reschedule_typed<B, L>(mut self, constructor: B)
175    where
176        B: FnOnce(Vec<Capability<T>>) -> L,
177        L: FnMut(&[MutableAntichain<T>])->bool+'static
178    {
179        // Freeze the per-input connectivity, now complete, for runtime readers.
180        for (cell, builder) in std::mem::take(&mut self.summaries) {
181            cell.set(builder.freeze()).expect("connectivity already frozen");
182        }
183
184        let mut logic = constructor(self.mint_capabilities());
185
186        // These vectors grew by `push` from empty, reserving capacity four; ports
187        // are few and the vectors live as long as the operator, so trim the excess.
188        // The trade is one reallocation per vector at build time for memory
189        // proportional to ports, rather than capacity, thereafter.
190        self.frontier.shrink_to_fit();
191        self.consumed.shrink_to_fit();
192        self.produced.shrink_to_fit();
193        self.internal.borrow_mut().shrink_to_fit();
194
195        let mut bookkeeping = ProgressBookkeeping {
196            frontier: self.frontier,
197            consumed: self.consumed,
198            internal: self.internal,
199            produced: self.produced,
200        };
201
202        let raw_logic =
203        move |progress: &mut SharedProgress<T>| {
204            bookkeeping.drain_frontiers(progress);
205            let result = logic(bookkeeping.frontiers());
206            bookkeeping.publish_progress(progress);
207            result
208        };
209
210        self.builder.build_typed(raw_logic);
211    }
212
213    /// Create initial capabilities, one per output, and clear their creation evidence.
214    ///
215    /// This method is specifically outlined from `Self::build_reschedule_typed` to avoid
216    /// monomorphization bloat, as it depends only on `T`, not on the closures.
217    fn mint_capabilities(&self) -> Vec<Capability<T>> {
218        let mut capabilities = Vec::with_capacity(self.internal.borrow().len());
219        for batch in self.internal.borrow().iter() {
220            capabilities.push(Capability::new(T::minimum(), Rc::clone(batch)));
221            // Discard evidence of creation, as we are assumed to start with one.
222            batch.borrow_mut().clear();
223        }
224        capabilities
225    }
226
227    /// Get the identifier assigned to the operator being constructed
228    pub fn index(&self) -> usize { self.builder.index() }
229
230    /// The operator's worker-unique identifier.
231    pub fn global(&self) -> usize { self.builder.global() }
232
233    /// Return a reference to the operator's shape
234    pub fn shape(&self) -> &OperatorShape { self.builder.shape() }
235
236    /// Creates operator info for the operator.
237    pub fn operator_info(&self) -> OperatorInfo { self.builder.operator_info() }
238}
239
240
241/// Progress-tracking state that is independent of operator logic.
242///
243/// Extracted so that `drain_frontiers` and `publish_progress` are monomorphized
244/// once per timestamp type `T`, rather than once per closure type passed to
245/// `build_reschedule`.
246struct ProgressBookkeeping<T: Timestamp> {
247    frontier: Vec<MutableAntichain<T>>,
248    consumed: Vec<Rc<RefCell<ChangeBatch<T>>>>,
249    internal: Rc<RefCell<Vec<Rc<RefCell<ChangeBatch<T>>>>>>,
250    produced: Vec<Rc<RefCell<ChangeBatch<T>>>>,
251}
252
253impl<T: Timestamp> ProgressBookkeeping<T> {
254    /// The current input frontiers, for passing to operator logic.
255    #[inline(always)] fn frontiers(&self) -> &[MutableAntichain<T>] { &self.frontier[..] }
256
257    /// Drain incoming frontier changes from `SharedProgress` into our local antichains.
258    fn drain_frontiers(&mut self, progress: &mut SharedProgress<T>) {
259        for (progress, frontier) in progress.frontiers.iter_mut().zip(self.frontier.iter_mut()) {
260            frontier.update_iter(progress.drain());
261        }
262    }
263
264    /// Publish consumed, internal, and produced changes back to `SharedProgress`.
265    fn publish_progress(&self, progress: &mut SharedProgress<T>) {
266        // move batches of consumed changes.
267        for (progress, consumed) in progress.consumeds.iter_mut().zip(self.consumed.iter()) {
268            consumed.borrow_mut().drain_into(progress);
269        }
270
271        // move batches of internal changes.
272        let self_internal_borrow = self.internal.borrow_mut();
273        for index in 0 .. self_internal_borrow.len() {
274            let mut borrow = self_internal_borrow[index].borrow_mut();
275            progress.internals[index].extend(borrow.drain());
276        }
277
278        // move batches of produced changes.
279        for (progress, produced) in progress.produceds.iter_mut().zip(self.produced.iter()) {
280            produced.borrow_mut().drain_into(progress);
281        }
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use crate::dataflow::operators::generic::OutputBuilder;
288
289    #[test]
290    #[should_panic]
291    fn incorrect_capabilities() {
292
293        // This tests that if we attempt to use a capability associated with the
294        // wrong output, there is a run-time assertion.
295
296        use crate::dataflow::operators::generic::builder_rc::OperatorBuilder;
297
298        crate::example(|scope| {
299
300            let mut builder = OperatorBuilder::new("Failure".to_owned(), scope.clone());
301
302            let (output1, _stream1) = builder.new_output::<Vec<()>>();
303            let (output2, _stream2) = builder.new_output::<Vec<()>>();
304            let mut output1 = OutputBuilder::from(output1);
305            let mut output2 = OutputBuilder::from(output2);
306
307            builder.build(move |capabilities| {
308                move |_frontiers| {
309
310                    let mut output_handle1 = output1.activate();
311                    let mut output_handle2 = output2.activate();
312
313                    // NOTE: Using incorrect capabilities here.
314                    output_handle2.session(&capabilities[0]);
315                    output_handle1.session(&capabilities[1]);
316                }
317            });
318        })
319    }
320
321    #[test]
322    fn correct_capabilities() {
323
324        // This tests that if we attempt to use capabilities with the correct outputs
325        // there is no runtime assertion
326
327        use crate::dataflow::operators::generic::builder_rc::OperatorBuilder;
328
329        crate::example(|scope| {
330
331            let mut builder = OperatorBuilder::new("Failure".to_owned(), scope.clone());
332
333            let (output1, _stream1) = builder.new_output::<Vec<()>>();
334            let (output2, _stream2) = builder.new_output::<Vec<()>>();
335            let mut output1 = OutputBuilder::from(output1);
336            let mut output2 = OutputBuilder::from(output2);
337
338            builder.build(move |mut capabilities| {
339                move |_frontiers| {
340
341                    let mut output_handle1 = output1.activate();
342                    let mut output_handle2 = output2.activate();
343
344                    // Avoid second call.
345                    if !capabilities.is_empty() {
346
347                        // NOTE: Using correct capabilities here.
348                        output_handle1.session(&capabilities[0]);
349                        output_handle2.session(&capabilities[1]);
350
351                        capabilities.clear();
352                    }
353                }
354            });
355
356            "Hello".to_owned()
357        });
358    }
359}