Skip to main content

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}