Skip to main content

blockworx/log/
state.rs

1//! Document state: what the log folds into.
2//!
3//! Every element is a bag of registers plus a liveness flag, and each of those
4//! keeps the write with the greatest [`WriteOrder`] it has seen. Nothing here
5//! knows about time, actors, or hashes beyond that order — this is a value,
6//! and a snapshot of it is a cache the log can always rebuild.
7//!
8//! Collections are ordered maps throughout. Iteration order is part of the
9//! answer: it decides z-order, and two replicas must encode a state to the
10//! same bytes.
11
12use std::collections::BTreeMap;
13
14use serde::{Deserialize, Serialize};
15
16use crate::log::command::{ElementKind, PropTag, PropWrite};
17use crate::log::id::{ElementId, WriteOrder};
18
19/// Whether an element is present or tombstoned. A register like any other, so
20/// a delete racing a restore resolves by write order instead of by who arrived
21/// last.
22#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
23pub enum Liveness {
24    Live,
25    Deleted,
26}
27
28impl Liveness {
29    pub fn is_live(self) -> bool {
30        self == Liveness::Live
31    }
32}
33
34/// What happened to a write. A loss is not an error — it is the merge working
35/// — but it is what a collision review reports.
36#[derive(Clone, Copy, PartialEq, Eq, Debug)]
37pub enum Applied {
38    Won,
39    /// Dropped because the register already held a write ordered after this
40    /// one. Dropping it is what makes delivery order irrelevant.
41    LostToNewer,
42}
43
44/// One register. `value` is `None` when the register has been cleared — a
45/// tombstone for a property, which still carries an order so a concurrent
46/// earlier write cannot resurrect the old value.
47#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
48pub struct Register {
49    pub value: Option<PropWrite>,
50    pub order: WriteOrder,
51}
52
53#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
54pub struct Element {
55    kind: ElementKind,
56    /// The order of the create that made it. This is the element's place in
57    /// z-order, which is why no explicit ordering field exists anywhere.
58    created: WriteOrder,
59    liveness: Liveness,
60    liveness_order: WriteOrder,
61    registers: BTreeMap<PropTag, Register>,
62}
63
64impl Element {
65    fn new(kind: ElementKind, created: WriteOrder) -> Self {
66        Self {
67            kind,
68            created,
69            liveness: Liveness::Live,
70            liveness_order: created,
71            registers: BTreeMap::new(),
72        }
73    }
74
75    pub fn kind(&self) -> ElementKind {
76        self.kind
77    }
78
79    pub fn created(&self) -> WriteOrder {
80        self.created
81    }
82
83    pub fn liveness(&self) -> Liveness {
84        self.liveness
85    }
86
87    pub fn is_live(&self) -> bool {
88        self.liveness.is_live()
89    }
90
91    pub fn get(&self, tag: PropTag) -> Option<&PropWrite> {
92        self.registers.get(&tag).and_then(|r| r.value.as_ref())
93    }
94
95    pub fn register(&self, tag: PropTag) -> Option<&Register> {
96        self.registers.get(&tag)
97    }
98
99    /// Ordered by tag, so encoding a state is a function of its content.
100    pub fn registers(&self) -> impl Iterator<Item = (PropTag, &Register)> {
101        self.registers.iter().map(|(tag, reg)| (*tag, reg))
102    }
103
104    pub fn parent(&self) -> Option<ElementId> {
105        match self.get(PropTag::Parent) {
106            Some(PropWrite::Parent(id)) => Some(*id),
107            _ => None,
108        }
109    }
110
111    /// The entire merge rule. A write ordered before what the register already
112    /// holds is dropped, which is what makes `max` — and therefore the whole
113    /// engine — independent of the order writes arrive in.
114    ///
115    /// `value` is `None` to clear the register. Clearing is a write like any
116    /// other: it lands in the map with its order rather than removing the
117    /// entry, or an older write arriving later would win.
118    fn write(&mut self, tag: PropTag, value: Option<PropWrite>, order: WriteOrder) -> Applied {
119        match self.registers.get(&tag) {
120            Some(held) if held.order > order => Applied::LostToNewer,
121            _ => {
122                self.registers.insert(tag, Register { value, order });
123                Applied::Won
124            }
125        }
126    }
127
128    fn set_liveness(&mut self, liveness: Liveness, order: WriteOrder) -> Applied {
129        if self.liveness_order > order {
130            return Applied::LostToNewer;
131        }
132        self.liveness = liveness;
133        self.liveness_order = order;
134        Applied::Won
135    }
136}
137
138/// The folded document.
139#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
140pub struct DocState {
141    elements: BTreeMap<ElementId, Element>,
142}
143
144impl DocState {
145    pub fn new() -> Self {
146        Self::default()
147    }
148
149    pub fn len(&self) -> usize {
150        self.elements.len()
151    }
152
153    pub fn is_empty(&self) -> bool {
154        self.elements.is_empty()
155    }
156
157    pub fn contains(&self, id: ElementId) -> bool {
158        self.elements.contains_key(&id)
159    }
160
161    pub fn element(&self, id: ElementId) -> Option<&Element> {
162        self.elements.get(&id)
163    }
164
165    /// Live elements only. A tombstoned element is still present — it has to
166    /// be, or a restore would have nothing to bring back — but it is not part
167    /// of the document.
168    pub fn live(&self, id: ElementId) -> Option<&Element> {
169        self.elements.get(&id).filter(|e| e.is_live())
170    }
171
172    pub fn get(&self, id: ElementId, tag: PropTag) -> Option<&PropWrite> {
173        self.live(id)?.get(tag)
174    }
175
176    /// Every element including tombstones, in id order.
177    pub fn iter(&self) -> impl Iterator<Item = (ElementId, &Element)> {
178        self.elements.iter().map(|(id, e)| (*id, e))
179    }
180
181    /// A parent's live children, in z-order — which is creation order, since
182    /// that is what the create's [`WriteOrder`] records. No stored sequence,
183    /// so nothing to merge and nothing to renumber.
184    pub fn children(&self, parent: ElementId) -> Vec<ElementId> {
185        let mut found: Vec<(WriteOrder, ElementId)> = self
186            .elements
187            .iter()
188            .filter(|(_, e)| e.is_live() && e.parent() == Some(parent))
189            .map(|(id, e)| (e.created, *id))
190            .collect();
191        found.sort_unstable();
192        found.into_iter().map(|(_, id)| id).collect()
193    }
194
195    /// Walks parent links to the document root. `None` when the chain runs off
196    /// a tombstone or a missing element, or when it exceeds the number of
197    /// elements — which can only mean a cycle, and is what the repair pass
198    /// exists to void.
199    pub fn is_rooted(&self, id: ElementId) -> bool {
200        let mut at = id;
201        for _ in 0..=self.elements.len() {
202            if at.is_document() {
203                return true;
204            }
205            match self.live(at).and_then(Element::parent) {
206                Some(parent) => at = parent,
207                None => return false,
208            }
209        }
210        false
211    }
212
213    pub(crate) fn ensure(&mut self, id: ElementId, kind: ElementKind, created: WriteOrder) {
214        match self.elements.get_mut(&id) {
215            // Re-applying a create — the same change delivered twice — must
216            // not move the element in z-order, so the earliest create wins.
217            Some(existing) => existing.created = existing.created.min(created),
218            None => {
219                self.elements.insert(id, Element::new(kind, created));
220            }
221        }
222    }
223
224    pub(crate) fn write(
225        &mut self,
226        id: ElementId,
227        tag: PropTag,
228        value: Option<PropWrite>,
229        order: WriteOrder,
230    ) -> Option<Applied> {
231        Some(self.elements.get_mut(&id)?.write(tag, value, order))
232    }
233
234    pub(crate) fn set_liveness(
235        &mut self,
236        id: ElementId,
237        liveness: Liveness,
238        order: WriteOrder,
239    ) -> Option<Applied> {
240        Some(self.elements.get_mut(&id)?.set_liveness(liveness, order))
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247    use crate::log::id::{ActorId, BatchIndex, Lamport, Stamp};
248
249    fn order(lamport: u64, actor_byte: u8) -> WriteOrder {
250        WriteOrder::new(
251            Stamp {
252                lamport: Lamport::new(lamport),
253                actor: ActorId::from_uuid(uuid::Uuid::from_bytes([actor_byte; 16])),
254            },
255            BatchIndex::FIRST,
256        )
257    }
258
259    fn element(byte: u8) -> ElementId {
260        ElementId::from_uuid(uuid::Uuid::from_bytes([byte; 16]))
261    }
262
263    fn with_block(state: &mut DocState, id: ElementId, at: WriteOrder) {
264        state.ensure(id, ElementKind::Block, at);
265        set(state, id, PropWrite::Parent(ElementId::DOCUMENT), at);
266    }
267
268    /// Write a value, deriving its register from the value itself — the shape
269    /// nearly every test wants, as against clearing a register.
270    fn set(state: &mut DocState, id: ElementId, value: PropWrite, at: WriteOrder) -> Applied {
271        state
272            .write(id, value.tag(), Some(value), at)
273            .expect("the element exists")
274    }
275
276    #[test]
277    fn a_stale_write_loses_and_leaves_the_value_alone() {
278        let mut state = DocState::new();
279        let id = element(1);
280        with_block(&mut state, id, order(1, 1));
281
282        assert_eq!(
283            set(&mut state, id, PropWrite::Accent(9), order(5, 1)),
284            Applied::Won
285        );
286        assert_eq!(
287            set(&mut state, id, PropWrite::Accent(2), order(3, 1)),
288            Applied::LostToNewer,
289            "a write ordered earlier must not land on a later one"
290        );
291        assert_eq!(state.get(id, PropTag::Accent), Some(&PropWrite::Accent(9)));
292    }
293
294    /// The convergence property in miniature: the same two writes applied in
295    /// either order leave the same value.
296    #[test]
297    fn two_writes_commute() {
298        let id = element(1);
299        let (early, late) = (
300            (PropWrite::Accent(1), order(4, 1)),
301            (PropWrite::Accent(2), order(6, 1)),
302        );
303
304        let mut forwards = DocState::new();
305        with_block(&mut forwards, id, order(1, 1));
306        set(&mut forwards, id, early.0.clone(), early.1);
307        set(&mut forwards, id, late.0.clone(), late.1);
308
309        let mut backwards = DocState::new();
310        with_block(&mut backwards, id, order(1, 1));
311        set(&mut backwards, id, late.0.clone(), late.1);
312        set(&mut backwards, id, early.0.clone(), early.1);
313
314        assert_eq!(forwards, backwards);
315        assert_eq!(
316            forwards.get(id, PropTag::Accent),
317            Some(&PropWrite::Accent(2))
318        );
319    }
320
321    #[test]
322    fn deleting_and_restoring_resolve_by_order_not_arrival() {
323        let id = element(1);
324        let mut delete_last = DocState::new();
325        with_block(&mut delete_last, id, order(1, 1));
326        delete_last.set_liveness(id, Liveness::Deleted, order(9, 1));
327        delete_last.set_liveness(id, Liveness::Live, order(5, 1));
328
329        let mut restore_last = DocState::new();
330        with_block(&mut restore_last, id, order(1, 1));
331        restore_last.set_liveness(id, Liveness::Live, order(5, 1));
332        restore_last.set_liveness(id, Liveness::Deleted, order(9, 1));
333
334        assert_eq!(delete_last, restore_last);
335        assert!(!delete_last.element(id).expect("still present").is_live());
336        assert!(
337            delete_last.live(id).is_none(),
338            "a tombstone is present but not part of the document"
339        );
340    }
341
342    #[test]
343    fn children_come_back_in_creation_order() {
344        let mut state = DocState::new();
345        let parent = element(1);
346        with_block(&mut state, parent, order(1, 1));
347
348        // Inserted late-first, so id order cannot be what produces the answer.
349        let (first, second) = (element(9), element(2));
350        state.ensure(first, ElementKind::Block, order(4, 1));
351        set(&mut state, first, PropWrite::Parent(parent), order(4, 1));
352        state.ensure(second, ElementKind::Block, order(7, 1));
353        set(&mut state, second, PropWrite::Parent(parent), order(7, 1));
354
355        assert!(first > second, "id order must differ from creation order");
356        assert_eq!(state.children(parent), vec![first, second]);
357    }
358
359    #[test]
360    fn a_tombstoned_child_leaves_its_parents_list() {
361        let mut state = DocState::new();
362        let parent = element(1);
363        let child = element(2);
364        with_block(&mut state, parent, order(1, 1));
365        state.ensure(child, ElementKind::Block, order(2, 1));
366        set(&mut state, child, PropWrite::Parent(parent), order(2, 1));
367        assert_eq!(state.children(parent), vec![child]);
368
369        state.set_liveness(child, Liveness::Deleted, order(3, 1));
370        assert!(state.children(parent).is_empty());
371    }
372
373    /// A parent cycle must not hang the walk. The repair pass in phase 2 is
374    /// what voids it; this only has to terminate.
375    #[test]
376    fn a_parent_cycle_is_reported_unrooted_rather_than_looping() {
377        let mut state = DocState::new();
378        let (a, b) = (element(1), element(2));
379        state.ensure(a, ElementKind::Block, order(1, 1));
380        state.ensure(b, ElementKind::Block, order(2, 1));
381        set(&mut state, a, PropWrite::Parent(b), order(3, 1));
382        set(&mut state, b, PropWrite::Parent(a), order(4, 1));
383
384        assert!(!state.is_rooted(a));
385        assert!(!state.is_rooted(b));
386    }
387
388    #[test]
389    fn an_element_parented_to_the_document_is_rooted() {
390        let mut state = DocState::new();
391        let id = element(1);
392        with_block(&mut state, id, order(1, 1));
393        assert!(state.is_rooted(id));
394    }
395
396    /// Re-applying a create is how a duplicate delivery arrives; it must not
397    /// restack the element.
398    #[test]
399    fn re_creating_keeps_the_earliest_creation_order() {
400        let mut state = DocState::new();
401        let id = element(1);
402        state.ensure(id, ElementKind::Block, order(5, 1));
403        state.ensure(id, ElementKind::Block, order(9, 1));
404        assert_eq!(state.element(id).expect("present").created(), order(5, 1));
405    }
406}