timely/progress/stamp.rs
1//! The stamp of a message: the multiset of timestamps affixed to it.
2//!
3//! Each message in timely dataflow carries a stamp: a multiset of timestamps,
4//! such that the message may only result in downstream work at times greater
5//! or equal to some element. Like postage, the stamp records the capabilities
6//! under which the message travels. Multiplicities are significant, as the
7//! message is accounted once per element in progress tracking; the order of
8//! elements is not, and is maintained sorted so that equal stamps are
9//! structurally equal. The common case is a singleton stamp,
10//! corresponding to the classic "one capability per message" design, and is
11//! represented inline without allocation. Stamps may contain multiple elements
12//! (e.g. a batch of updates stamped with its lower antichain, as in differential
13//! dataflow) or no elements at all (data that makes no progress claims, and which
14//! may be delivered after the frontier has passed all times in its payload).
15//!
16//! `Stamp` maintains its elements sorted by `Ord`, so that equal stamps are
17//! structurally equal and stamps can be used as grouping and sorting keys.
18//! Stamps built by insertion are minimal (an antichain), but stamps that have
19//! crossed a scope boundary may contain comparable or duplicate elements: the
20//! stamp is the multiset of pointstamps at which the message is accounted, and
21//! boundary maps must preserve counts element-wise (see [`Stamp::map_pointwise`]).
22
23use serde::{Deserialize, Serialize};
24use smallvec::SmallVec;
25
26use crate::order::{PartialOrder, TotalOrder};
27
28/// A multiset of timestamps affixed to a message, stored sorted.
29///
30/// The elements are stored in increasing order under `Ord` so that `Eq`, `Ord`,
31/// and `Hash` are structural. Stamps built by insertion are minimal antichains;
32/// stamps mapped across scope boundaries may contain comparable or duplicate
33/// elements, whose multiplicities are significant for progress accounting.
34///
35/// Equality compares multisets, not the sets of times they contain: `{a}` and
36/// `{a, a}` are different stamps, as they are accounted differently.
37///
38/// As with [`Antichain`](crate::progress::Antichain), the storage holds a single
39/// element inline, so the overwhelmingly common zero- and one-element stamps
40/// require no allocation.
41#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, columnar::Columnar)]
42pub struct Stamp<T> {
43 elements: SmallVec<[T; 1]>,
44}
45
46/// Deserialization sorts the elements, as the bytes need not come from a valid `Stamp`.
47impl<'de, T: Deserialize<'de> + Ord> Deserialize<'de> for Stamp<T> {
48 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
49 // Mirrors the serialized form of `Stamp`.
50 #[derive(Deserialize)]
51 #[serde(rename = "Stamp")]
52 struct Raw<T> { elements: SmallVec<[T; 1]> }
53 let mut elements = Raw::deserialize(deserializer)?.elements;
54 elements.sort();
55 Ok(Stamp { elements })
56 }
57}
58
59impl<T> Stamp<T> {
60 /// An empty stamp, making no progress claims.
61 pub fn new() -> Self {
62 Stamp { elements: SmallVec::new() }
63 }
64
65 /// A stamp containing a single element.
66 pub fn from_elem(element: T) -> Self {
67 Stamp { elements: SmallVec::from_buf([element]) }
68 }
69
70 /// The elements of the stamp, in increasing `Ord` order.
71 #[inline]
72 pub fn elements(&self) -> &[T] {
73 &self.elements[..]
74 }
75
76 /// An iterator over the elements of the stamp.
77 #[inline]
78 pub fn iter(&self) -> std::slice::Iter<'_, T> {
79 self.elements.iter()
80 }
81
82 /// True iff the stamp contains no elements.
83 #[inline]
84 pub fn is_empty(&self) -> bool {
85 self.elements.is_empty()
86 }
87
88 /// The number of elements in the stamp.
89 #[inline]
90 pub fn len(&self) -> usize {
91 self.elements.len()
92 }
93
94 /// The elements of the stamp, by value, in increasing `Ord` order.
95 #[inline]
96 pub fn into_elements(self) -> Vec<T> {
97 self.elements.into_vec()
98 }
99
100 /// The sole element of the stamp, if the stamp is a singleton.
101 #[inline]
102 pub fn as_singleton(&self) -> Option<&T> {
103 if self.elements.len() == 1 { Some(&self.elements[0]) } else { None }
104 }
105
106 /// The sole element of the stamp; panics if the stamp is not a singleton.
107 ///
108 /// This supports pre-stamp interfaces that insist on a single timestamp per
109 /// message; such interfaces cannot be used with multi- or zero-element stamps.
110 #[inline]
111 pub fn expect_singleton(&self) -> &T where T: std::fmt::Debug {
112 self.as_singleton().unwrap_or_else(|| {
113 panic!("expected a singleton stamp; found {:?} elements: {:?}", self.elements.len(), self.elements())
114 })
115 }
116}
117
118impl<T: TotalOrder> Stamp<T> {
119 /// The least element of the stamp, if the stamp is non-empty.
120 ///
121 /// Only totally ordered stamps have a least element. A non-empty stamp over a
122 /// total order has one however many elements it contains, and it is the time
123 /// at which the message may first result in downstream work.
124 ///
125 /// Elements are stored in `Ord` order, but `TotalOrder` speaks only of `PartialOrder`
126 /// and need not agree with `Ord`, so this is a reduction rather than `first()`.
127 #[inline]
128 pub fn least(&self) -> Option<&T> {
129 self.elements.iter().reduce(|a, b| if b.less_equal(a) { b } else { a })
130 }
131}
132
133impl<T: PartialOrder + Ord> Stamp<T> {
134 /// Inserts `element` unless it is redundant, removing elements it dominates.
135 ///
136 /// Returns true iff the element was inserted.
137 pub fn insert(&mut self, element: T) -> bool {
138 if self.elements.iter().any(|x| x.less_equal(&element)) {
139 false
140 } else {
141 self.elements.retain(|x| !element.less_equal(x));
142 let position = self.elements.partition_point(|x| x < &element);
143 self.elements.insert(position, element);
144 true
145 }
146 }
147
148 /// True iff some element of the stamp is less or equal to `time`.
149 #[inline]
150 pub fn less_equal(&self, time: &T) -> bool {
151 self.elements.iter().any(|x| x.less_equal(time))
152 }
153
154 /// Maps each element through `logic`, discarding `None` results and
155 /// restoring minimality and canonical order.
156 ///
157 /// This is the shape required when timestamps traverse path summaries
158 /// (feedback edges), where elements may fail to traverse, or may become
159 /// comparable after mapping. It may only be used by operators that account
160 /// for their own consumed and produced messages, as it changes the number
161 /// of pointstamps a message is accounted at.
162 pub fn map_into<T2: PartialOrder + Ord>(&self, mut logic: impl FnMut(&T) -> Option<T2>) -> Stamp<T2> {
163 let mut result = Stamp::new();
164 for element in self.elements.iter() {
165 if let Some(mapped) = logic(element) {
166 result.insert(mapped);
167 }
168 }
169 result
170 }
171
172 /// Maps each element through `logic`, preserving the number of elements and
173 /// restoring sorted order, but *not* minimality.
174 ///
175 /// This is the shape required at scope boundaries (enter and leave), whose
176 /// produced and consumed accounting is inferred independently at either end
177 /// of the channel from the stamp itself: the counts must agree element-wise,
178 /// so the map must not collapse elements that become comparable or equal
179 /// after mapping (as when leaving a scope projects away a timestamp
180 /// coordinate). The resulting stamp may contain comparable or duplicate
181 /// elements; this weakens no guarantee, as the stamp promises only that
182 /// message contents are greater or equal to *some* element.
183 pub fn map_pointwise<T2: Ord>(&self, logic: impl FnMut(&T) -> T2) -> Stamp<T2> {
184 let mut elements: SmallVec<[T2; 1]> = self.elements.iter().map(logic).collect();
185 elements.sort();
186 Stamp { elements }
187 }
188}
189
190impl<T> Default for Stamp<T> {
191 fn default() -> Self { Self::new() }
192}
193
194impl<T: PartialOrder + Ord> FromIterator<T> for Stamp<T> {
195 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
196 let mut result = Stamp::new();
197 for element in iter {
198 result.insert(element);
199 }
200 result
201 }
202}
203
204impl<'a, T> IntoIterator for &'a Stamp<T> {
205 type Item = &'a T;
206 type IntoIter = std::slice::Iter<'a, T>;
207 fn into_iter(self) -> Self::IntoIter { self.iter() }
208}
209
210impl<T: PartialOrder + Ord + Clone> From<crate::progress::Antichain<T>> for Stamp<T> {
211 fn from(antichain: crate::progress::Antichain<T>) -> Self {
212 let mut elements: SmallVec<[T; 1]> = antichain.into();
213 elements.sort();
214 Stamp { elements }
215 }
216}
217
218impl<T: PartialOrder + Ord + Clone> From<Stamp<T>> for crate::progress::Antichain<T> {
219 fn from(stamp: Stamp<T>) -> Self {
220 stamp.into_elements().into()
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::Stamp;
227
228 #[test]
229 fn deserialize_sorts() {
230 // A stamp serializes as its sequence of elements.
231 let bytes = bincode::serialize(&vec![3u64, 1, 2, 1]).unwrap();
232 let stamp: Stamp<u64> = bincode::deserialize(&bytes).unwrap();
233 assert_eq!(stamp.elements(), &[1, 1, 2, 3]);
234 assert_eq!(bincode::serialize(&stamp).unwrap(), bincode::serialize(&vec![1u64, 1, 2, 3]).unwrap());
235 }
236}