dag/
verlink.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8use std::cmp;
9use std::fmt;
10use std::sync::atomic;
11use std::sync::atomic::AtomicU32;
12use std::sync::Arc;
13
14/// A linked list tracking a logic "version" with compatibility rules:
15/// - Append-only changes bump the version, the new version is backwards
16///   compatible.
17/// - Non-append-only changes create a new version that is incompatible with
18///   all other versions (in the current process).
19/// - Clones (cheaply) preserve the version.
20///
21/// Supported operations:
22/// - `new() -> x`: Create a new version that is not comparable (compatible)
23///   with other versions.
24/// - `clone(x) -> y`: Clone `x` to `y`. `x == y`. `x` and `y` are compatible.
25/// - `bump(x) -> y`: Bump `x` to `y`. `y > x`. `y` is backwards-compatible with
26///   `x`. Note: `y` is not comparable (compatible) with other `bump(x)`.
27/// - `x > y`: `true` if `x` is backwards compatible with `y`.
28///
29/// The linked list can be shared in other linked lists. So they form a tree
30/// effectively. Comparing to a DAG, there is no "merge" operation.
31/// Compatibility questions become reachability questions.
32#[derive(Clone)]
33pub struct VerLink {
34    inner: Arc<Inner>,
35}
36
37#[derive(Clone)]
38struct Inner {
39    parent: Option<VerLink>,
40
41    /// The base number. Two `VerLink`s with different base numbers are incompatible.
42    /// Used as an optimization to exit `compatible` early.
43    base: u32,
44
45    /// The "generation number", distance to root (the parentless `VerLink`).
46    /// If `x.compatible(y)` returns `x`, then `x.gen` must be >= `y.gen`.
47    /// Used as an optimization to exit `compatible` early.
48    gen: u32,
49}
50
51impl VerLink {
52    /// Creates a new `VerLink` that is incompatible with all other `VerLink`s
53    /// in the process.
54    pub fn new() -> Self {
55        let inner = Inner {
56            parent: None,
57            base: next_id(),
58            gen: 0,
59        };
60        Self {
61            inner: Arc::new(inner),
62        }
63    }
64
65    /// Bumps the `VerLink` for backward-compatible (ex. append-only) changes.
66    pub fn bump(&mut self) {
67        match Arc::get_mut(&mut self.inner) {
68            // This is an optimization to avoid increasing the length of the
69            // linked list (which slows down "compatible" calculation) if
70            // possible.
71            Some(_inner) => {
72                // Increasing gen is not necessary for correctness.
73                // _inner.gen += 1;
74            }
75            None => {
76                let next_inner = Inner {
77                    parent: Some(self.clone()),
78                    base: self.inner.base,
79                    gen: self.inner.gen + 1,
80                };
81                let next = Self {
82                    inner: Arc::new(next_inner),
83                };
84                *self = next;
85            }
86        }
87    }
88}
89
90impl PartialOrd for VerLink {
91    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
92        if self.inner.base != other.inner.base {
93            // Fast path: self and other have different root nodes and are not
94            // reachable from each other.
95            return None;
96        }
97        if self.inner.gen < other.inner.gen {
98            other.partial_cmp(self).map(|o| o.reverse())
99        } else {
100            debug_assert!(self.inner.gen >= other.inner.gen);
101            if Arc::ptr_eq(&self.inner, &other.inner) {
102                return Some(cmp::Ordering::Equal);
103            }
104            let mut cur = self.inner.parent.as_ref();
105            while let Some(this) = cur {
106                if this.inner.gen < other.inner.gen {
107                    // Fast path: not possible to reach other from here.
108                    return None;
109                }
110                if Arc::ptr_eq(&this.inner, &other.inner) {
111                    return Some(cmp::Ordering::Greater);
112                }
113                cur = this.inner.parent.as_ref();
114            }
115            None
116        }
117    }
118}
119
120impl PartialEq for VerLink {
121    fn eq(&self, other: &Self) -> bool {
122        Arc::ptr_eq(&self.inner, &other.inner)
123    }
124}
125
126impl fmt::Debug for VerLink {
127    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
128        let mut cur = Some(self);
129        while let Some(this) = cur {
130            write!(f, "{:p}", this.inner.as_ref())?;
131            cur = this.inner.parent.as_ref();
132            f.write_str("->")?;
133            if cur.is_none() {
134                write!(f, "{}", this.inner.base)?;
135            }
136        }
137        Ok(())
138    }
139}
140
141fn next_id() -> u32 {
142    static ID: AtomicU32 = AtomicU32::new(0);
143    ID.fetch_add(1, atomic::Ordering::AcqRel)
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_individual_news_are_incompatible() {
152        let a = VerLink::new();
153        let b = VerLink::new();
154        assert!(compatible(&a, &b).is_none());
155    }
156
157    #[test]
158    fn test_clone_compatible() {
159        let a = VerLink::new();
160        let b = a.clone();
161        assert_eq!(&a, &b);
162        assert_eq!(compatible(&a, &b), Some(&b));
163    }
164
165    #[test]
166    fn test_bump_is_different_and_backwards_compatible() {
167        let a = VerLink::new();
168        let mut b = a.clone();
169        b.bump();
170        assert_ne!(&b, &a);
171        assert_eq!(compatible(&a, &b), Some(&b));
172
173        b.bump();
174        b.bump();
175        assert_eq!(compatible(&a, &b), Some(&b));
176    }
177
178    #[test]
179    fn test_clone_bump_twice() {
180        let a = VerLink::new();
181        let mut b = a.clone();
182        b.bump();
183        let mut c = b.clone();
184        c.bump();
185        assert_eq!(compatible(&a, &c), Some(&c));
186        assert_eq!(compatible(&b, &c), Some(&c));
187    }
188
189    #[test]
190    fn test_bump_independently_become_incompatible() {
191        let mut a = VerLink::new();
192        let mut b = a.clone();
193        b.bump();
194        a.bump();
195        assert_eq!(compatible(&a, &b), None);
196    }
197
198    #[test]
199    fn test_bump_avoid_increase_len_if_possible() {
200        let a = VerLink::new();
201        let mut b = a.clone();
202        assert_eq!(a.chain_len(), 1);
203        assert_eq!(b.chain_len(), 1);
204        b.bump(); // Increases chain_len by 1.
205        assert_eq!(b.chain_len(), 2);
206        b.bump(); // Does not change chain len.
207        b.bump();
208        assert_eq!(b.chain_len(), 2);
209    }
210
211    /// Find the more compatible version.
212    #[allow(clippy::neg_cmp_op_on_partial_ord)]
213    fn compatible<'a>(a: &'a VerLink, b: &'a VerLink) -> Option<&'a VerLink> {
214        if a == b {
215            assert!(!(a != b));
216            assert!(!(a < b));
217            assert!(!(b < a));
218            assert!(!(a > b));
219            assert!(!(b > a));
220            Some(a)
221        } else if a < b {
222            assert!(!(a == b));
223            assert!(a != b);
224            assert!(!(b < a));
225            assert!(!(a > b));
226            assert!(b > a);
227            Some(b)
228        } else if a > b {
229            assert!(!(a == b));
230            assert!(a != b);
231            assert!(!(a < b));
232            assert!(b < a);
233            assert!(!(b > a));
234            Some(a)
235        } else {
236            assert!(!(a == b));
237            assert!(a != b);
238            assert!(!(a < b));
239            assert!(!(a > b));
240            assert!(!(b < a));
241            assert!(!(b > a));
242            None
243        }
244    }
245
246    impl VerLink {
247        /// Length of the linked list.
248        fn chain_len(&self) -> usize {
249            let mut len = 0;
250            let mut cur = Some(self);
251            while let Some(this) = cur {
252                len += 1;
253                cur = this.inner.parent.as_ref();
254            }
255            len
256        }
257    }
258}