Skip to main content

timely/dataflow/operators/generic/
builder_raw.rs

1//! Types to build operators with general shapes.
2//!
3//! These types expose some raw timely interfaces, and while public so that others can build on them,
4//! they require some sophistication to use correctly. I recommend checking out `builder_rc.rs` for
5//! an interface that is intentionally harder to mis-use.
6
7use std::default::Default;
8use std::rc::Rc;
9use std::cell::RefCell;
10
11use crate::scheduling::{Schedule, Activations};
12
13use crate::progress::{Source, Target};
14use crate::progress::{Timestamp, Operate, operate::SharedProgress, Antichain};
15use crate::progress::operate::{FrontierInterest, Connectivity, PortConnectivityBuilder};
16use crate::Container;
17use crate::dataflow::{Stream, Scope, OperatorSlot};
18use crate::dataflow::channels::pushers::Tee;
19use crate::dataflow::channels::pact::ParallelizationContract;
20use crate::dataflow::operators::generic::operator_info::OperatorInfo;
21
22/// Contains type-free information about the operator properties.
23#[derive(Debug)]
24pub struct OperatorShape {
25    name: String,   // A meaningful name for the operator.
26    notify: Vec<FrontierInterest>,   // Per-input frontier interest.
27    peers: usize,   // The total number of workers in the computation. Needed to initialize pointstamp counts with the correct magnitude.
28    inputs: usize,  // The number of input ports.
29    outputs: usize, // The number of output ports.
30}
31
32/// Core data for the structure of an operator, minus scope and logic.
33impl OperatorShape {
34    fn new(name: String, peers: usize) -> Self {
35        OperatorShape {
36            name,
37            notify: Vec::new(),
38            peers,
39            inputs: 0,
40            outputs: 0,
41        }
42    }
43
44    /// The number of inputs of this operator
45    pub fn inputs(&self) -> usize { self.inputs }
46
47    /// The number of outputs of this operator
48    pub fn outputs(&self) -> usize { self.outputs }
49}
50
51/// Builds operators with generic shape.
52#[derive(Debug)]
53pub struct OperatorBuilder<'scope, T: Timestamp> {
54    scope: Scope<'scope, T>,
55    slot: OperatorSlot<'scope, T>,
56    address: Rc<[usize]>,    // path to the operator (ending with index).
57    shape: OperatorShape,
58    summary: Vec<PortConnectivityBuilder<<T as Timestamp>::Summary>>,
59}
60
61impl<'scope, T: Timestamp> OperatorBuilder<'scope, T> {
62
63    /// Allocates a new generic operator builder from its containing scope.
64    pub fn new(name: String, scope: Scope<'scope, T>) -> Self {
65
66        let slot = scope.reserve_operator();
67        let address = slot.addr();
68        let peers = scope.peers();
69
70        OperatorBuilder {
71            scope,
72            slot,
73            address,
74            shape: OperatorShape::new(name, peers),
75            summary: vec![],
76        }
77    }
78
79    /// The operator's scope-local index.
80    pub fn index(&self) -> usize { self.slot.index() }
81
82    /// The operator's worker-unique identifier.
83    pub fn global(&self) -> usize { self.slot.identifier() }
84
85    /// Return a reference to the operator's shape
86    pub fn shape(&self) -> &OperatorShape { &self.shape }
87
88    /// Sets frontier interest for a specific input.
89    pub fn set_notify_for(&mut self, input: usize, notify: FrontierInterest) {
90        self.shape.notify[input] = notify;
91    }
92
93    /// Adds a new input to a generic operator builder, returning the `Pull` implementor to use.
94    pub fn new_input<C: Container, P>(&mut self, stream: Stream<'scope, T, C>, pact: P) -> P::Puller
95    where
96        P: ParallelizationContract<T, C>
97    {
98        let connection = (0 .. self.shape.outputs).map(|o| (o, Antichain::from_elem(Default::default())));
99        self.new_input_connection(stream, pact, connection)
100    }
101
102    /// Adds a new input to a generic operator builder, returning the `Pull` implementor to use.
103    pub fn new_input_connection<C: Container, P, I>(&mut self, stream: Stream<'scope, T, C>, pact: P, connection: I) -> P::Puller
104    where
105        P: ParallelizationContract<T, C>,
106        I: IntoIterator<Item = (usize, Antichain<<T as Timestamp>::Summary>)>,
107    {
108        let channel_id = self.scope.worker().new_identifier();
109        let logging = self.scope.worker().logging();
110        let (sender, receiver) = pact.connect(self.scope.worker(), channel_id, Rc::clone(&self.address), logging);
111        let target = Target::new(self.slot.index(), self.shape.inputs);
112        stream.connect_to(target, sender, channel_id);
113
114        self.shape.inputs += 1;
115        self.shape.notify.push(FrontierInterest::Always);
116        let connectivity: PortConnectivityBuilder<_> = connection.into_iter()
117            .inspect(|(o,_)| assert!(*o < self.shape.outputs))
118            .collect();
119        self.summary.push(connectivity);
120
121        receiver
122    }
123
124    /// Adds a new output to a generic operator builder, returning the `Push` implementor to use.
125    pub fn new_output<C: Container>(&mut self) -> (Tee<T, C>, Stream<'scope, T, C>) {
126        let connection = (0 .. self.shape.inputs).map(|i| (i, Antichain::from_elem(Default::default())));
127        self.new_output_connection(connection)
128    }
129
130    /// Adds a new output to a generic operator builder, returning the `Push` implementor to use.
131    pub fn new_output_connection<C: Container, I>(&mut self, connection: I) -> (Tee<T, C>, Stream<'scope, T, C>)
132    where
133        I: IntoIterator<Item = (usize, Antichain<<T as Timestamp>::Summary>)>,
134    {
135        let new_output = self.shape.outputs;
136        self.shape.outputs += 1;
137        let (target, registrar) = Tee::new();
138        let source = Source::new(self.slot.index(), new_output);
139        let stream = Stream::new(source, registrar, self.scope);
140
141        for (input, entry) in connection {
142            self.summary[input].add_port(new_output, entry);
143        }
144
145        (target, stream)
146    }
147
148    /// Creates an operator implementation from supplied logic constructor.
149    ///
150    /// Boxes the closure to avoid per-closure monomorphization based on `L`.
151    /// For the fully generic (non-boxing) path, see [`build_typed`].
152    pub fn build<L>(self, logic: L)
153    where
154        L: FnMut(&mut SharedProgress<T>)->bool+'static
155    {
156        self.build_boxed(Box::new(logic));
157    }
158
159    /// Creates an operator implementation from pre-boxed logic.
160    ///
161    /// This method exists primarily to force the `Box<dyn ...>` coercion, which
162    /// can otherwise easily be `Box<L>` for specialized `L` instead.
163    pub fn build_boxed(self, logic: Box<dyn FnMut(&mut SharedProgress<T>)->bool>) {
164        self.build_typed(logic);
165    }
166
167    /// Like `build_reschedule`, but specialized to the closure type `L`.
168    ///
169    /// This method is instantiated once per distinct `L`, and one should be
170    /// mindful of monomorphization bloat. Callers with many distinct closures
171    /// should consider erasing their variation, for example via `Box<dyn ...>`,
172    /// as demonstrated in [`build`].
173    pub fn build_typed<L>(self, logic: L)
174    where
175        L: FnMut(&mut SharedProgress<T>)->bool+'static
176    {
177        let inputs = self.shape.inputs;
178        let outputs = self.shape.outputs;
179
180        let operator = OperatorCore {
181            shape: self.shape,
182            address: self.address,
183            activations: self.scope.activations(),
184            logic,
185            shared_progress: Rc::new(RefCell::new(SharedProgress::new(inputs, outputs))),
186            summary: self.summary.into_iter().map(|b| b.freeze()).collect(),
187        };
188
189        self.slot.install(Box::new(operator));
190    }
191
192    /// Information describing the operator.
193    pub fn operator_info(&self) -> OperatorInfo {
194        OperatorInfo::new(self.index(), self.global(), Rc::clone(&self.address))
195    }
196}
197
198struct OperatorCore<T, L>
199where
200    T: Timestamp,
201    L: FnMut(&mut SharedProgress<T>)->bool+'static,
202{
203    shape: OperatorShape,
204    address: Rc<[usize]>,
205    logic: L,
206    shared_progress: Rc<RefCell<SharedProgress<T>>>,
207    activations: Rc<RefCell<Activations>>,
208    summary: Connectivity<T::Summary>,
209}
210
211impl<T, L> Schedule for OperatorCore<T, L>
212where
213    T: Timestamp,
214    L: FnMut(&mut SharedProgress<T>)->bool+'static,
215{
216    fn name(&self) -> &str { &self.shape.name }
217    fn path(&self) -> &[usize] { &self.address[..] }
218    fn schedule(&mut self) -> bool {
219        let shared_progress = &mut *self.shared_progress.borrow_mut();
220        (self.logic)(shared_progress)
221    }
222}
223
224impl<T, L> Operate<T> for OperatorCore<T, L>
225where
226    T: Timestamp,
227    L: FnMut(&mut SharedProgress<T>)->bool+'static,
228{
229    fn inputs(&self) -> usize { self.shape.inputs }
230    fn outputs(&self) -> usize { self.shape.outputs }
231
232    // announce internal topology as fully connected, and hold all default capabilities.
233    fn initialize(mut self: Box<Self>) -> (Connectivity<T::Summary>, Rc<RefCell<SharedProgress<T>>>, Box<dyn Schedule>) {
234
235        // Request the operator to be scheduled at least once.
236        self.activations.borrow_mut().activate(&self.address[..]);
237
238        // by default, we reserve a capability for each output port at `Default::default()`.
239        self.shared_progress
240            .borrow_mut()
241            .internals
242            .iter_mut()
243            .for_each(|output| output.update(T::minimum(), self.shape.peers as i64));
244
245        // The summary is not read again; move it out rather than clone it.
246        (::std::mem::take(&mut self.summary), Rc::clone(&self.shared_progress), self)
247    }
248
249    fn notify_me(&self) -> &[FrontierInterest] { &self.shape.notify }
250}