TreeReplica

Struct TreeReplica 

Source
pub struct TreeReplica<ID: TreeId, TM: TreeMeta, A: Actor> { /* private fields */ }
Expand description

TreeReplica holds tree State plus lamport timestamp (actor + counter)

It can optionally keep track of the latest timestamp for each replica which is needed for calculating the causally stable threshold which is in turn needed for log truncation.

TreeReplica is a higher-level interface to the Tree CRDT and is tied to a particular actor/peer.

State is a lower-level interface to the Tree CRDT and is not tied to any actor/peer.

Implementations§

Source§

impl<ID: TreeId, TM: TreeMeta, A: Actor + Debug> TreeReplica<ID, TM, A>

Source

pub fn new(id: A) -> Self

returns new TreeReplica

Examples found in repository?
examples/demo.rs (line 43)
42fn demo_concurrent_moves() {
43    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
44    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
45
46    let ids: HashMap<&str, TypeId> = [
47        ("root", new_id()),
48        ("a", new_id()),
49        ("b", new_id()),
50        ("c", new_id()),
51    ]
52    .iter()
53    .cloned()
54    .collect();
55
56    // Setup initial tree state.
57    let ops = r1.opmoves(vec![
58        (0, "root", ids["root"]),
59        (ids["root"], "a", ids["a"]),
60        (ids["root"], "b", ids["b"]),
61        (ids["root"], "c", ids["c"]),
62    ]);
63
64    r1.apply_ops_byref(&ops);
65    r2.apply_ops_byref(&ops);
66
67    println!("Initial tree state on both replicas");
68    print_tree(r1.tree(), &ids["root"]);
69
70    // replica_1 moves /root/a to /root/b
71    let repl1_ops = vec![r1.opmove(ids["b"], "a", ids["a"])];
72
73    // replica_2 "simultaneously" moves /root/a to /root/c
74    let repl2_ops = vec![r2.opmove(ids["c"], "a", ids["a"])];
75
76    // replica_1 applies his op, then merges op from replica_2
77    r1.apply_ops_byref(&repl1_ops);
78    println!("\nreplica_1 tree after move");
79    print_tree(r1.tree(), &ids["root"]);
80    r1.apply_ops_byref(&repl2_ops);
81
82    // replica_2 applies his op, then merges op from replica_1
83    r2.apply_ops_byref(&repl2_ops);
84    println!("\nreplica_2 tree after move");
85    print_tree(r2.tree(), &ids["root"]);
86    r2.apply_ops_byref(&repl1_ops);
87
88    // expected result: state is the same on both replicas
89    // and final path is /root/c/a because last-writer-wins
90    // and replica_2's op has a later timestamp.
91    //    if r1.state.is_equal(&r2.state) {
92    if r1.state() == r2.state() {
93        println!("\nreplica_1 state matches replica_2 state after each merges other's change.  conflict resolved!");
94        print_replica_trees(&r1, &r2, &ids["root"]);
95    } else {
96        println!("\nwarning: replica_1 state does not match replica_2 state after merge");
97        print_replica_trees(&r1, &r2, &ids["root"]);
98        println!("-- replica_1 state --");
99        println!("{:#?}", r1.state());
100        println!("\n-- replica_2 state --");
101        println!("{:#?}", r2.state());
102    }
103}
104
105// Demo: cycle test from the paper
106//
107// Tests what happen when two peers independently perform operations that would
108// introduce a cycle when combined.
109//
110// Upon applying eachother's ops, they must converge to a common location without
111// any cycles.
112fn demo_concurrent_moves_cycle() {
113    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
114    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
115
116    let ids: HashMap<&str, TypeId> = [
117        ("root", new_id()),
118        ("a", new_id()),
119        ("b", new_id()),
120        ("c", new_id()),
121    ]
122    .iter()
123    .cloned()
124    .collect();
125
126    // Setup initial tree state.
127    let ops = r1.opmoves(vec![
128        (0, "root", ids["root"]),
129        (ids["root"], "a", ids["a"]),
130        (ids["root"], "b", ids["b"]),
131        (ids["a"], "c", ids["c"]),
132    ]);
133
134    r1.apply_ops_byref(&ops);
135    r2.apply_ops_byref(&ops);
136
137    println!("Initial tree state on both replicas");
138    print_tree(r1.tree(), &ids["root"]);
139
140    // replica_1 moves /root/b to /root/a,  creating /root/a/b
141    let repl1_ops = r1.opmoves(vec![(ids["a"], "b", ids["b"])]);
142
143    // replica_2 "simultaneously" moves /root/a to /root/b, creating /root/b/a
144    let repl2_ops = r2.opmoves(vec![(ids["b"], "a", ids["a"])]);
145
146    // replica_1 applies his op, then merges op from replica_2
147    r1.apply_ops_byref(&repl1_ops);
148    println!("\nreplica_1 tree after move");
149    print_tree(r1.tree(), &ids["root"]);
150    r1.apply_ops_byref(&repl2_ops);
151
152    // replica_2 applies his op, then merges op from replica_1
153    r2.apply_ops_byref(&repl2_ops);
154    println!("\nreplica_2 tree after move");
155    print_tree(r2.tree(), &ids["root"]);
156    r2.apply_ops_byref(&repl1_ops);
157
158    // expected result: state is the same on both replicas
159    // and final path is /root/b/a because last-writer-wins
160    // and replica_2's op has a later timestamp.
161    if r1.state() == r2.state() {
162        println!("\nreplica_1 state matches replica_2 state after each merges other's change.  conflict resolved!");
163        print_replica_trees(&r1, &r2, &ids["root"]);
164    } else {
165        println!("\nwarning: replica_1 state does not match replica_2 state after merge");
166        print_replica_trees(&r1, &r2, &ids["root"]);
167        println!("-- replica_1 state --");
168        println!("{:#?}", r1.state());
169        println!("\n-- replica_2 state --");
170        println!("{:#?}", r2.state());
171    }
172}
173
174// Demo: Walk a deep tree
175//
176// This demonstrates creation of a deep tree which we then walk in depth-first
177// fashion.
178//
179// This particular tree contains 2^6-1 nodes and is up to 6 levels deep.
180fn demo_walk_deep_tree() {
181    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
182
183    let ids: HashMap<&str, TypeId> = [("root", new_id())].iter().cloned().collect();
184
185    // Generate initial tree state.
186    println!("generating ops...");
187    let mut ops = vec![(0, "root", ids["root"])];
188    mktree_ops(&mut ops, &mut r1, ids["root"], 2, 6); //  <-- max 6 levels deep.
189
190    println!("applying ops...");
191    let ops_len = ops.len();
192    r1.apply_ops_byref(&r1.opmoves(ops));
193
194    println!("walking tree...");
195    r1.tree().walk(&ids["root"], |tree, node_id, depth| {
196        if true {
197            let meta = match tree.find(node_id) {
198                Some(tn) => format!("{:?}", tn.metadata()),
199                None => format!("{:?}", node_id),
200            };
201            println!("{:indent$}{}", "", meta, indent = depth);
202        }
203    });
204
205    println!("\nnodes in tree: {}", ops_len);
206}
207
208/// Demonstrates log truncation
209///
210/// This requires that causally stable threshold tracking is enabled in `TreeReplica`
211fn demo_truncate_log() {
212    let mut replicas: Vec<TreeReplica<TypeId, TypeMeta, TypeActor>> = Vec::new();
213    let num_replicas = 5;
214
215    // start some replicas.
216    for _i in 0..num_replicas {
217        // pass true flag to enable causally stable threshold tracking
218        let r: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
219        replicas.push(r);
220    }
221
222    let root_id = new_id();
223
224    // Generate initial tree state.
225    let mut opmoves = vec![replicas[0].opmove(0, "root", root_id)];
226
227    println!("generating move operations...");
228
229    // generate some initial ops from all replicas.
230    for r in replicas.iter_mut() {
231        let finaldepth = rand::thread_rng().gen_range(3, 6);
232        let mut ops = vec![];
233        mktree_ops(&mut ops, r, root_id, 2, finaldepth);
234        opmoves.extend(r.opmoves(ops));
235    }
236
237    // apply all ops to all replicas
238    println!(
239        "applying {} operations to all {} replicas...\n",
240        opmoves.len(),
241        replicas.len()
242    );
243    apply_ops_to_replicas(&mut replicas, &opmoves);
244
245    #[derive(Debug)]
246    #[allow(dead_code)]
247    struct Stat {
248        pub replica: TypeActor,
249        pub ops_before_truncate: usize,
250        pub ops_after_truncate: usize,
251    }
252
253    let mut stats: Vec<Stat> = Vec::new();
254    for r in replicas.iter_mut() {
255        println!("truncating log of replica {}...", r.id());
256        println!(
257            "causally stable threshold: {:?}\n",
258            r.causally_stable_threshold()
259        );
260        let ops_b4 = r.state().log().len();
261        r.truncate_log();
262        let ops_after = r.state().log().len();
263        stats.push(Stat {
264            replica: *r.id(),
265            ops_before_truncate: ops_b4,
266            ops_after_truncate: ops_after,
267        });
268    }
269
270    println!("-- Stats -- ");
271    println!("\n{:#?}", stats);
272}
273
274/// Demonstrates moving items to a Trash node outside the nominal root and then
275/// emptying the trash after the log is truncated.
276///
277/// This requires that causally stable threshold tracking is enabled in `TreeReplica`
278fn demo_move_to_trash() {
279    // pass true flag to enable causally stable threshold tracking
280    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
281    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
282
283    let ids: HashMap<&str, TypeId> = [
284        ("forest", new_id()),
285        ("trash", new_id()),
286        ("root", new_id()),
287        ("home", new_id()),
288        ("bob", new_id()),
289        ("project", new_id()),
290    ]
291    .iter()
292    .cloned()
293    .collect();
294
295    // Generate initial tree state.
296    //
297    // - forest
298    //   - trash
299    //   - root
300    //     - home
301    //       - bob
302    //         - project
303    let mut ops = vec![
304        (ids["forest"], "root", ids["root"]),
305        (ids["forest"], "trash", ids["trash"]),
306        (ids["root"], "home", ids["home"]),
307        (ids["home"], "bob", ids["bob"]),
308        (ids["bob"], "project", ids["project"]),
309    ];
310
311    // add some nodes under project
312    mktree_ops(&mut ops, &mut r1, ids["project"], 2, 3);
313    let opmoves = r1.opmoves(ops);
314    r1.apply_ops_byref(&opmoves);
315    r2.apply_ops_byref(&opmoves);
316
317    println!("Initial tree");
318    print_tree(r1.tree(), &ids["forest"]);
319
320    // move project to trash
321    let ops = vec![r1.opmove(ids["trash"], "project", ids["project"])];
322    r1.apply_ops_byref(&ops);
323    r2.apply_ops_byref(&ops);
324
325    println!("\nAfter project moved to trash (deleted) on both replicas");
326    print_tree(r1.tree(), &ids["forest"]);
327
328    // Initially, trashed nodes must be retained because a concurrent move
329    // operation may move them back out of the trash.
330    //
331    // Once the operation that moved a node to the trash is causally
332    // stable, we know that no future operations will refer to this node,
333    // and so the trashed node and its descendants can be discarded.
334    //
335    // note:  change r1.opmoves() to r2.opmoves() above to
336    //        make the causally stable threshold less than the trash operation
337    //        timestamp, which will cause this test to fail, ie hit the
338    //        "trash should not be emptied" condition.
339    let result = r2.causally_stable_threshold();
340    match result {
341        Some(cst) if cst < ops[0].timestamp() => {
342            println!(
343                "\ncausally stable threshold:\n{:#?}\n\ntrash operation:\n{:#?}",
344                cst,
345                ops[0].timestamp()
346            );
347            panic!("!error: causally stable threshold is less than trash operation timestamp");
348        }
349        None => panic!("!error: causally stable threshold not found"),
350        _ => {}
351    }
352
353    // empty trash
354    r1.tree_mut().rm_subtree(&ids["trash"], false);
355    println!("\nDelete op is now causally stable, so we can empty trash:");
356    print_tree(r1.tree(), &ids["forest"]);
357}
Source

pub fn opmove( &self, parent_id: ID, metadata: TM, child_id: ID, ) -> OpMove<ID, TM, A>

Generates an OpMove

Note that OpMove::timestamp is incremented from TreeReplica::time. TreeReplica::time is not updated until ::apply_op() is called.

Therefore, multiple ops generated with this method may share the same timestamp, and only one can be sucessfully applied.

To generate multiple ops before calling ::apply_op(), use ::opmoves() instead.

Examples found in repository?
examples/demo.rs (line 71)
42fn demo_concurrent_moves() {
43    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
44    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
45
46    let ids: HashMap<&str, TypeId> = [
47        ("root", new_id()),
48        ("a", new_id()),
49        ("b", new_id()),
50        ("c", new_id()),
51    ]
52    .iter()
53    .cloned()
54    .collect();
55
56    // Setup initial tree state.
57    let ops = r1.opmoves(vec![
58        (0, "root", ids["root"]),
59        (ids["root"], "a", ids["a"]),
60        (ids["root"], "b", ids["b"]),
61        (ids["root"], "c", ids["c"]),
62    ]);
63
64    r1.apply_ops_byref(&ops);
65    r2.apply_ops_byref(&ops);
66
67    println!("Initial tree state on both replicas");
68    print_tree(r1.tree(), &ids["root"]);
69
70    // replica_1 moves /root/a to /root/b
71    let repl1_ops = vec![r1.opmove(ids["b"], "a", ids["a"])];
72
73    // replica_2 "simultaneously" moves /root/a to /root/c
74    let repl2_ops = vec![r2.opmove(ids["c"], "a", ids["a"])];
75
76    // replica_1 applies his op, then merges op from replica_2
77    r1.apply_ops_byref(&repl1_ops);
78    println!("\nreplica_1 tree after move");
79    print_tree(r1.tree(), &ids["root"]);
80    r1.apply_ops_byref(&repl2_ops);
81
82    // replica_2 applies his op, then merges op from replica_1
83    r2.apply_ops_byref(&repl2_ops);
84    println!("\nreplica_2 tree after move");
85    print_tree(r2.tree(), &ids["root"]);
86    r2.apply_ops_byref(&repl1_ops);
87
88    // expected result: state is the same on both replicas
89    // and final path is /root/c/a because last-writer-wins
90    // and replica_2's op has a later timestamp.
91    //    if r1.state.is_equal(&r2.state) {
92    if r1.state() == r2.state() {
93        println!("\nreplica_1 state matches replica_2 state after each merges other's change.  conflict resolved!");
94        print_replica_trees(&r1, &r2, &ids["root"]);
95    } else {
96        println!("\nwarning: replica_1 state does not match replica_2 state after merge");
97        print_replica_trees(&r1, &r2, &ids["root"]);
98        println!("-- replica_1 state --");
99        println!("{:#?}", r1.state());
100        println!("\n-- replica_2 state --");
101        println!("{:#?}", r2.state());
102    }
103}
104
105// Demo: cycle test from the paper
106//
107// Tests what happen when two peers independently perform operations that would
108// introduce a cycle when combined.
109//
110// Upon applying eachother's ops, they must converge to a common location without
111// any cycles.
112fn demo_concurrent_moves_cycle() {
113    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
114    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
115
116    let ids: HashMap<&str, TypeId> = [
117        ("root", new_id()),
118        ("a", new_id()),
119        ("b", new_id()),
120        ("c", new_id()),
121    ]
122    .iter()
123    .cloned()
124    .collect();
125
126    // Setup initial tree state.
127    let ops = r1.opmoves(vec![
128        (0, "root", ids["root"]),
129        (ids["root"], "a", ids["a"]),
130        (ids["root"], "b", ids["b"]),
131        (ids["a"], "c", ids["c"]),
132    ]);
133
134    r1.apply_ops_byref(&ops);
135    r2.apply_ops_byref(&ops);
136
137    println!("Initial tree state on both replicas");
138    print_tree(r1.tree(), &ids["root"]);
139
140    // replica_1 moves /root/b to /root/a,  creating /root/a/b
141    let repl1_ops = r1.opmoves(vec![(ids["a"], "b", ids["b"])]);
142
143    // replica_2 "simultaneously" moves /root/a to /root/b, creating /root/b/a
144    let repl2_ops = r2.opmoves(vec![(ids["b"], "a", ids["a"])]);
145
146    // replica_1 applies his op, then merges op from replica_2
147    r1.apply_ops_byref(&repl1_ops);
148    println!("\nreplica_1 tree after move");
149    print_tree(r1.tree(), &ids["root"]);
150    r1.apply_ops_byref(&repl2_ops);
151
152    // replica_2 applies his op, then merges op from replica_1
153    r2.apply_ops_byref(&repl2_ops);
154    println!("\nreplica_2 tree after move");
155    print_tree(r2.tree(), &ids["root"]);
156    r2.apply_ops_byref(&repl1_ops);
157
158    // expected result: state is the same on both replicas
159    // and final path is /root/b/a because last-writer-wins
160    // and replica_2's op has a later timestamp.
161    if r1.state() == r2.state() {
162        println!("\nreplica_1 state matches replica_2 state after each merges other's change.  conflict resolved!");
163        print_replica_trees(&r1, &r2, &ids["root"]);
164    } else {
165        println!("\nwarning: replica_1 state does not match replica_2 state after merge");
166        print_replica_trees(&r1, &r2, &ids["root"]);
167        println!("-- replica_1 state --");
168        println!("{:#?}", r1.state());
169        println!("\n-- replica_2 state --");
170        println!("{:#?}", r2.state());
171    }
172}
173
174// Demo: Walk a deep tree
175//
176// This demonstrates creation of a deep tree which we then walk in depth-first
177// fashion.
178//
179// This particular tree contains 2^6-1 nodes and is up to 6 levels deep.
180fn demo_walk_deep_tree() {
181    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
182
183    let ids: HashMap<&str, TypeId> = [("root", new_id())].iter().cloned().collect();
184
185    // Generate initial tree state.
186    println!("generating ops...");
187    let mut ops = vec![(0, "root", ids["root"])];
188    mktree_ops(&mut ops, &mut r1, ids["root"], 2, 6); //  <-- max 6 levels deep.
189
190    println!("applying ops...");
191    let ops_len = ops.len();
192    r1.apply_ops_byref(&r1.opmoves(ops));
193
194    println!("walking tree...");
195    r1.tree().walk(&ids["root"], |tree, node_id, depth| {
196        if true {
197            let meta = match tree.find(node_id) {
198                Some(tn) => format!("{:?}", tn.metadata()),
199                None => format!("{:?}", node_id),
200            };
201            println!("{:indent$}{}", "", meta, indent = depth);
202        }
203    });
204
205    println!("\nnodes in tree: {}", ops_len);
206}
207
208/// Demonstrates log truncation
209///
210/// This requires that causally stable threshold tracking is enabled in `TreeReplica`
211fn demo_truncate_log() {
212    let mut replicas: Vec<TreeReplica<TypeId, TypeMeta, TypeActor>> = Vec::new();
213    let num_replicas = 5;
214
215    // start some replicas.
216    for _i in 0..num_replicas {
217        // pass true flag to enable causally stable threshold tracking
218        let r: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
219        replicas.push(r);
220    }
221
222    let root_id = new_id();
223
224    // Generate initial tree state.
225    let mut opmoves = vec![replicas[0].opmove(0, "root", root_id)];
226
227    println!("generating move operations...");
228
229    // generate some initial ops from all replicas.
230    for r in replicas.iter_mut() {
231        let finaldepth = rand::thread_rng().gen_range(3, 6);
232        let mut ops = vec![];
233        mktree_ops(&mut ops, r, root_id, 2, finaldepth);
234        opmoves.extend(r.opmoves(ops));
235    }
236
237    // apply all ops to all replicas
238    println!(
239        "applying {} operations to all {} replicas...\n",
240        opmoves.len(),
241        replicas.len()
242    );
243    apply_ops_to_replicas(&mut replicas, &opmoves);
244
245    #[derive(Debug)]
246    #[allow(dead_code)]
247    struct Stat {
248        pub replica: TypeActor,
249        pub ops_before_truncate: usize,
250        pub ops_after_truncate: usize,
251    }
252
253    let mut stats: Vec<Stat> = Vec::new();
254    for r in replicas.iter_mut() {
255        println!("truncating log of replica {}...", r.id());
256        println!(
257            "causally stable threshold: {:?}\n",
258            r.causally_stable_threshold()
259        );
260        let ops_b4 = r.state().log().len();
261        r.truncate_log();
262        let ops_after = r.state().log().len();
263        stats.push(Stat {
264            replica: *r.id(),
265            ops_before_truncate: ops_b4,
266            ops_after_truncate: ops_after,
267        });
268    }
269
270    println!("-- Stats -- ");
271    println!("\n{:#?}", stats);
272}
273
274/// Demonstrates moving items to a Trash node outside the nominal root and then
275/// emptying the trash after the log is truncated.
276///
277/// This requires that causally stable threshold tracking is enabled in `TreeReplica`
278fn demo_move_to_trash() {
279    // pass true flag to enable causally stable threshold tracking
280    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
281    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
282
283    let ids: HashMap<&str, TypeId> = [
284        ("forest", new_id()),
285        ("trash", new_id()),
286        ("root", new_id()),
287        ("home", new_id()),
288        ("bob", new_id()),
289        ("project", new_id()),
290    ]
291    .iter()
292    .cloned()
293    .collect();
294
295    // Generate initial tree state.
296    //
297    // - forest
298    //   - trash
299    //   - root
300    //     - home
301    //       - bob
302    //         - project
303    let mut ops = vec![
304        (ids["forest"], "root", ids["root"]),
305        (ids["forest"], "trash", ids["trash"]),
306        (ids["root"], "home", ids["home"]),
307        (ids["home"], "bob", ids["bob"]),
308        (ids["bob"], "project", ids["project"]),
309    ];
310
311    // add some nodes under project
312    mktree_ops(&mut ops, &mut r1, ids["project"], 2, 3);
313    let opmoves = r1.opmoves(ops);
314    r1.apply_ops_byref(&opmoves);
315    r2.apply_ops_byref(&opmoves);
316
317    println!("Initial tree");
318    print_tree(r1.tree(), &ids["forest"]);
319
320    // move project to trash
321    let ops = vec![r1.opmove(ids["trash"], "project", ids["project"])];
322    r1.apply_ops_byref(&ops);
323    r2.apply_ops_byref(&ops);
324
325    println!("\nAfter project moved to trash (deleted) on both replicas");
326    print_tree(r1.tree(), &ids["forest"]);
327
328    // Initially, trashed nodes must be retained because a concurrent move
329    // operation may move them back out of the trash.
330    //
331    // Once the operation that moved a node to the trash is causally
332    // stable, we know that no future operations will refer to this node,
333    // and so the trashed node and its descendants can be discarded.
334    //
335    // note:  change r1.opmoves() to r2.opmoves() above to
336    //        make the causally stable threshold less than the trash operation
337    //        timestamp, which will cause this test to fail, ie hit the
338    //        "trash should not be emptied" condition.
339    let result = r2.causally_stable_threshold();
340    match result {
341        Some(cst) if cst < ops[0].timestamp() => {
342            println!(
343                "\ncausally stable threshold:\n{:#?}\n\ntrash operation:\n{:#?}",
344                cst,
345                ops[0].timestamp()
346            );
347            panic!("!error: causally stable threshold is less than trash operation timestamp");
348        }
349        None => panic!("!error: causally stable threshold not found"),
350        _ => {}
351    }
352
353    // empty trash
354    r1.tree_mut().rm_subtree(&ids["trash"], false);
355    println!("\nDelete op is now causally stable, so we can empty trash:");
356    print_tree(r1.tree(), &ids["forest"]);
357}
Source

pub fn opmoves(&self, ops: Vec<(ID, TM, ID)>) -> Vec<OpMove<ID, TM, A>>

Generates a list of OpMove from a list of tuples (child_id, metadata, parent_id)

Each OpMove::timestamp will be greater than the previous op in the returned list. Therefore, these operations can be successfully applied via ::apply_op() without timestamp collision.

Examples found in repository?
examples/demo.rs (lines 57-62)
42fn demo_concurrent_moves() {
43    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
44    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
45
46    let ids: HashMap<&str, TypeId> = [
47        ("root", new_id()),
48        ("a", new_id()),
49        ("b", new_id()),
50        ("c", new_id()),
51    ]
52    .iter()
53    .cloned()
54    .collect();
55
56    // Setup initial tree state.
57    let ops = r1.opmoves(vec![
58        (0, "root", ids["root"]),
59        (ids["root"], "a", ids["a"]),
60        (ids["root"], "b", ids["b"]),
61        (ids["root"], "c", ids["c"]),
62    ]);
63
64    r1.apply_ops_byref(&ops);
65    r2.apply_ops_byref(&ops);
66
67    println!("Initial tree state on both replicas");
68    print_tree(r1.tree(), &ids["root"]);
69
70    // replica_1 moves /root/a to /root/b
71    let repl1_ops = vec![r1.opmove(ids["b"], "a", ids["a"])];
72
73    // replica_2 "simultaneously" moves /root/a to /root/c
74    let repl2_ops = vec![r2.opmove(ids["c"], "a", ids["a"])];
75
76    // replica_1 applies his op, then merges op from replica_2
77    r1.apply_ops_byref(&repl1_ops);
78    println!("\nreplica_1 tree after move");
79    print_tree(r1.tree(), &ids["root"]);
80    r1.apply_ops_byref(&repl2_ops);
81
82    // replica_2 applies his op, then merges op from replica_1
83    r2.apply_ops_byref(&repl2_ops);
84    println!("\nreplica_2 tree after move");
85    print_tree(r2.tree(), &ids["root"]);
86    r2.apply_ops_byref(&repl1_ops);
87
88    // expected result: state is the same on both replicas
89    // and final path is /root/c/a because last-writer-wins
90    // and replica_2's op has a later timestamp.
91    //    if r1.state.is_equal(&r2.state) {
92    if r1.state() == r2.state() {
93        println!("\nreplica_1 state matches replica_2 state after each merges other's change.  conflict resolved!");
94        print_replica_trees(&r1, &r2, &ids["root"]);
95    } else {
96        println!("\nwarning: replica_1 state does not match replica_2 state after merge");
97        print_replica_trees(&r1, &r2, &ids["root"]);
98        println!("-- replica_1 state --");
99        println!("{:#?}", r1.state());
100        println!("\n-- replica_2 state --");
101        println!("{:#?}", r2.state());
102    }
103}
104
105// Demo: cycle test from the paper
106//
107// Tests what happen when two peers independently perform operations that would
108// introduce a cycle when combined.
109//
110// Upon applying eachother's ops, they must converge to a common location without
111// any cycles.
112fn demo_concurrent_moves_cycle() {
113    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
114    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
115
116    let ids: HashMap<&str, TypeId> = [
117        ("root", new_id()),
118        ("a", new_id()),
119        ("b", new_id()),
120        ("c", new_id()),
121    ]
122    .iter()
123    .cloned()
124    .collect();
125
126    // Setup initial tree state.
127    let ops = r1.opmoves(vec![
128        (0, "root", ids["root"]),
129        (ids["root"], "a", ids["a"]),
130        (ids["root"], "b", ids["b"]),
131        (ids["a"], "c", ids["c"]),
132    ]);
133
134    r1.apply_ops_byref(&ops);
135    r2.apply_ops_byref(&ops);
136
137    println!("Initial tree state on both replicas");
138    print_tree(r1.tree(), &ids["root"]);
139
140    // replica_1 moves /root/b to /root/a,  creating /root/a/b
141    let repl1_ops = r1.opmoves(vec![(ids["a"], "b", ids["b"])]);
142
143    // replica_2 "simultaneously" moves /root/a to /root/b, creating /root/b/a
144    let repl2_ops = r2.opmoves(vec![(ids["b"], "a", ids["a"])]);
145
146    // replica_1 applies his op, then merges op from replica_2
147    r1.apply_ops_byref(&repl1_ops);
148    println!("\nreplica_1 tree after move");
149    print_tree(r1.tree(), &ids["root"]);
150    r1.apply_ops_byref(&repl2_ops);
151
152    // replica_2 applies his op, then merges op from replica_1
153    r2.apply_ops_byref(&repl2_ops);
154    println!("\nreplica_2 tree after move");
155    print_tree(r2.tree(), &ids["root"]);
156    r2.apply_ops_byref(&repl1_ops);
157
158    // expected result: state is the same on both replicas
159    // and final path is /root/b/a because last-writer-wins
160    // and replica_2's op has a later timestamp.
161    if r1.state() == r2.state() {
162        println!("\nreplica_1 state matches replica_2 state after each merges other's change.  conflict resolved!");
163        print_replica_trees(&r1, &r2, &ids["root"]);
164    } else {
165        println!("\nwarning: replica_1 state does not match replica_2 state after merge");
166        print_replica_trees(&r1, &r2, &ids["root"]);
167        println!("-- replica_1 state --");
168        println!("{:#?}", r1.state());
169        println!("\n-- replica_2 state --");
170        println!("{:#?}", r2.state());
171    }
172}
173
174// Demo: Walk a deep tree
175//
176// This demonstrates creation of a deep tree which we then walk in depth-first
177// fashion.
178//
179// This particular tree contains 2^6-1 nodes and is up to 6 levels deep.
180fn demo_walk_deep_tree() {
181    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
182
183    let ids: HashMap<&str, TypeId> = [("root", new_id())].iter().cloned().collect();
184
185    // Generate initial tree state.
186    println!("generating ops...");
187    let mut ops = vec![(0, "root", ids["root"])];
188    mktree_ops(&mut ops, &mut r1, ids["root"], 2, 6); //  <-- max 6 levels deep.
189
190    println!("applying ops...");
191    let ops_len = ops.len();
192    r1.apply_ops_byref(&r1.opmoves(ops));
193
194    println!("walking tree...");
195    r1.tree().walk(&ids["root"], |tree, node_id, depth| {
196        if true {
197            let meta = match tree.find(node_id) {
198                Some(tn) => format!("{:?}", tn.metadata()),
199                None => format!("{:?}", node_id),
200            };
201            println!("{:indent$}{}", "", meta, indent = depth);
202        }
203    });
204
205    println!("\nnodes in tree: {}", ops_len);
206}
207
208/// Demonstrates log truncation
209///
210/// This requires that causally stable threshold tracking is enabled in `TreeReplica`
211fn demo_truncate_log() {
212    let mut replicas: Vec<TreeReplica<TypeId, TypeMeta, TypeActor>> = Vec::new();
213    let num_replicas = 5;
214
215    // start some replicas.
216    for _i in 0..num_replicas {
217        // pass true flag to enable causally stable threshold tracking
218        let r: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
219        replicas.push(r);
220    }
221
222    let root_id = new_id();
223
224    // Generate initial tree state.
225    let mut opmoves = vec![replicas[0].opmove(0, "root", root_id)];
226
227    println!("generating move operations...");
228
229    // generate some initial ops from all replicas.
230    for r in replicas.iter_mut() {
231        let finaldepth = rand::thread_rng().gen_range(3, 6);
232        let mut ops = vec![];
233        mktree_ops(&mut ops, r, root_id, 2, finaldepth);
234        opmoves.extend(r.opmoves(ops));
235    }
236
237    // apply all ops to all replicas
238    println!(
239        "applying {} operations to all {} replicas...\n",
240        opmoves.len(),
241        replicas.len()
242    );
243    apply_ops_to_replicas(&mut replicas, &opmoves);
244
245    #[derive(Debug)]
246    #[allow(dead_code)]
247    struct Stat {
248        pub replica: TypeActor,
249        pub ops_before_truncate: usize,
250        pub ops_after_truncate: usize,
251    }
252
253    let mut stats: Vec<Stat> = Vec::new();
254    for r in replicas.iter_mut() {
255        println!("truncating log of replica {}...", r.id());
256        println!(
257            "causally stable threshold: {:?}\n",
258            r.causally_stable_threshold()
259        );
260        let ops_b4 = r.state().log().len();
261        r.truncate_log();
262        let ops_after = r.state().log().len();
263        stats.push(Stat {
264            replica: *r.id(),
265            ops_before_truncate: ops_b4,
266            ops_after_truncate: ops_after,
267        });
268    }
269
270    println!("-- Stats -- ");
271    println!("\n{:#?}", stats);
272}
273
274/// Demonstrates moving items to a Trash node outside the nominal root and then
275/// emptying the trash after the log is truncated.
276///
277/// This requires that causally stable threshold tracking is enabled in `TreeReplica`
278fn demo_move_to_trash() {
279    // pass true flag to enable causally stable threshold tracking
280    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
281    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
282
283    let ids: HashMap<&str, TypeId> = [
284        ("forest", new_id()),
285        ("trash", new_id()),
286        ("root", new_id()),
287        ("home", new_id()),
288        ("bob", new_id()),
289        ("project", new_id()),
290    ]
291    .iter()
292    .cloned()
293    .collect();
294
295    // Generate initial tree state.
296    //
297    // - forest
298    //   - trash
299    //   - root
300    //     - home
301    //       - bob
302    //         - project
303    let mut ops = vec![
304        (ids["forest"], "root", ids["root"]),
305        (ids["forest"], "trash", ids["trash"]),
306        (ids["root"], "home", ids["home"]),
307        (ids["home"], "bob", ids["bob"]),
308        (ids["bob"], "project", ids["project"]),
309    ];
310
311    // add some nodes under project
312    mktree_ops(&mut ops, &mut r1, ids["project"], 2, 3);
313    let opmoves = r1.opmoves(ops);
314    r1.apply_ops_byref(&opmoves);
315    r2.apply_ops_byref(&opmoves);
316
317    println!("Initial tree");
318    print_tree(r1.tree(), &ids["forest"]);
319
320    // move project to trash
321    let ops = vec![r1.opmove(ids["trash"], "project", ids["project"])];
322    r1.apply_ops_byref(&ops);
323    r2.apply_ops_byref(&ops);
324
325    println!("\nAfter project moved to trash (deleted) on both replicas");
326    print_tree(r1.tree(), &ids["forest"]);
327
328    // Initially, trashed nodes must be retained because a concurrent move
329    // operation may move them back out of the trash.
330    //
331    // Once the operation that moved a node to the trash is causally
332    // stable, we know that no future operations will refer to this node,
333    // and so the trashed node and its descendants can be discarded.
334    //
335    // note:  change r1.opmoves() to r2.opmoves() above to
336    //        make the causally stable threshold less than the trash operation
337    //        timestamp, which will cause this test to fail, ie hit the
338    //        "trash should not be emptied" condition.
339    let result = r2.causally_stable_threshold();
340    match result {
341        Some(cst) if cst < ops[0].timestamp() => {
342            println!(
343                "\ncausally stable threshold:\n{:#?}\n\ntrash operation:\n{:#?}",
344                cst,
345                ops[0].timestamp()
346            );
347            panic!("!error: causally stable threshold is less than trash operation timestamp");
348        }
349        None => panic!("!error: causally stable threshold not found"),
350        _ => {}
351    }
352
353    // empty trash
354    r1.tree_mut().rm_subtree(&ids["trash"], false);
355    println!("\nDelete op is now causally stable, so we can empty trash:");
356    print_tree(r1.tree(), &ids["forest"]);
357}
Source

pub fn id(&self) -> &A

Returns actor ID for this replica

Examples found in repository?
examples/demo.rs (line 255)
211fn demo_truncate_log() {
212    let mut replicas: Vec<TreeReplica<TypeId, TypeMeta, TypeActor>> = Vec::new();
213    let num_replicas = 5;
214
215    // start some replicas.
216    for _i in 0..num_replicas {
217        // pass true flag to enable causally stable threshold tracking
218        let r: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
219        replicas.push(r);
220    }
221
222    let root_id = new_id();
223
224    // Generate initial tree state.
225    let mut opmoves = vec![replicas[0].opmove(0, "root", root_id)];
226
227    println!("generating move operations...");
228
229    // generate some initial ops from all replicas.
230    for r in replicas.iter_mut() {
231        let finaldepth = rand::thread_rng().gen_range(3, 6);
232        let mut ops = vec![];
233        mktree_ops(&mut ops, r, root_id, 2, finaldepth);
234        opmoves.extend(r.opmoves(ops));
235    }
236
237    // apply all ops to all replicas
238    println!(
239        "applying {} operations to all {} replicas...\n",
240        opmoves.len(),
241        replicas.len()
242    );
243    apply_ops_to_replicas(&mut replicas, &opmoves);
244
245    #[derive(Debug)]
246    #[allow(dead_code)]
247    struct Stat {
248        pub replica: TypeActor,
249        pub ops_before_truncate: usize,
250        pub ops_after_truncate: usize,
251    }
252
253    let mut stats: Vec<Stat> = Vec::new();
254    for r in replicas.iter_mut() {
255        println!("truncating log of replica {}...", r.id());
256        println!(
257            "causally stable threshold: {:?}\n",
258            r.causally_stable_threshold()
259        );
260        let ops_b4 = r.state().log().len();
261        r.truncate_log();
262        let ops_after = r.state().log().len();
263        stats.push(Stat {
264            replica: *r.id(),
265            ops_before_truncate: ops_b4,
266            ops_after_truncate: ops_after,
267        });
268    }
269
270    println!("-- Stats -- ");
271    println!("\n{:#?}", stats);
272}
Source

pub fn time(&self) -> &Clock<A>

Returns the latest lamport time seen by this replica

Source

pub fn state(&self) -> &State<ID, TM, A>

Returns Tree State reference

Examples found in repository?
examples/demo.rs (line 92)
42fn demo_concurrent_moves() {
43    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
44    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
45
46    let ids: HashMap<&str, TypeId> = [
47        ("root", new_id()),
48        ("a", new_id()),
49        ("b", new_id()),
50        ("c", new_id()),
51    ]
52    .iter()
53    .cloned()
54    .collect();
55
56    // Setup initial tree state.
57    let ops = r1.opmoves(vec![
58        (0, "root", ids["root"]),
59        (ids["root"], "a", ids["a"]),
60        (ids["root"], "b", ids["b"]),
61        (ids["root"], "c", ids["c"]),
62    ]);
63
64    r1.apply_ops_byref(&ops);
65    r2.apply_ops_byref(&ops);
66
67    println!("Initial tree state on both replicas");
68    print_tree(r1.tree(), &ids["root"]);
69
70    // replica_1 moves /root/a to /root/b
71    let repl1_ops = vec![r1.opmove(ids["b"], "a", ids["a"])];
72
73    // replica_2 "simultaneously" moves /root/a to /root/c
74    let repl2_ops = vec![r2.opmove(ids["c"], "a", ids["a"])];
75
76    // replica_1 applies his op, then merges op from replica_2
77    r1.apply_ops_byref(&repl1_ops);
78    println!("\nreplica_1 tree after move");
79    print_tree(r1.tree(), &ids["root"]);
80    r1.apply_ops_byref(&repl2_ops);
81
82    // replica_2 applies his op, then merges op from replica_1
83    r2.apply_ops_byref(&repl2_ops);
84    println!("\nreplica_2 tree after move");
85    print_tree(r2.tree(), &ids["root"]);
86    r2.apply_ops_byref(&repl1_ops);
87
88    // expected result: state is the same on both replicas
89    // and final path is /root/c/a because last-writer-wins
90    // and replica_2's op has a later timestamp.
91    //    if r1.state.is_equal(&r2.state) {
92    if r1.state() == r2.state() {
93        println!("\nreplica_1 state matches replica_2 state after each merges other's change.  conflict resolved!");
94        print_replica_trees(&r1, &r2, &ids["root"]);
95    } else {
96        println!("\nwarning: replica_1 state does not match replica_2 state after merge");
97        print_replica_trees(&r1, &r2, &ids["root"]);
98        println!("-- replica_1 state --");
99        println!("{:#?}", r1.state());
100        println!("\n-- replica_2 state --");
101        println!("{:#?}", r2.state());
102    }
103}
104
105// Demo: cycle test from the paper
106//
107// Tests what happen when two peers independently perform operations that would
108// introduce a cycle when combined.
109//
110// Upon applying eachother's ops, they must converge to a common location without
111// any cycles.
112fn demo_concurrent_moves_cycle() {
113    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
114    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
115
116    let ids: HashMap<&str, TypeId> = [
117        ("root", new_id()),
118        ("a", new_id()),
119        ("b", new_id()),
120        ("c", new_id()),
121    ]
122    .iter()
123    .cloned()
124    .collect();
125
126    // Setup initial tree state.
127    let ops = r1.opmoves(vec![
128        (0, "root", ids["root"]),
129        (ids["root"], "a", ids["a"]),
130        (ids["root"], "b", ids["b"]),
131        (ids["a"], "c", ids["c"]),
132    ]);
133
134    r1.apply_ops_byref(&ops);
135    r2.apply_ops_byref(&ops);
136
137    println!("Initial tree state on both replicas");
138    print_tree(r1.tree(), &ids["root"]);
139
140    // replica_1 moves /root/b to /root/a,  creating /root/a/b
141    let repl1_ops = r1.opmoves(vec![(ids["a"], "b", ids["b"])]);
142
143    // replica_2 "simultaneously" moves /root/a to /root/b, creating /root/b/a
144    let repl2_ops = r2.opmoves(vec![(ids["b"], "a", ids["a"])]);
145
146    // replica_1 applies his op, then merges op from replica_2
147    r1.apply_ops_byref(&repl1_ops);
148    println!("\nreplica_1 tree after move");
149    print_tree(r1.tree(), &ids["root"]);
150    r1.apply_ops_byref(&repl2_ops);
151
152    // replica_2 applies his op, then merges op from replica_1
153    r2.apply_ops_byref(&repl2_ops);
154    println!("\nreplica_2 tree after move");
155    print_tree(r2.tree(), &ids["root"]);
156    r2.apply_ops_byref(&repl1_ops);
157
158    // expected result: state is the same on both replicas
159    // and final path is /root/b/a because last-writer-wins
160    // and replica_2's op has a later timestamp.
161    if r1.state() == r2.state() {
162        println!("\nreplica_1 state matches replica_2 state after each merges other's change.  conflict resolved!");
163        print_replica_trees(&r1, &r2, &ids["root"]);
164    } else {
165        println!("\nwarning: replica_1 state does not match replica_2 state after merge");
166        print_replica_trees(&r1, &r2, &ids["root"]);
167        println!("-- replica_1 state --");
168        println!("{:#?}", r1.state());
169        println!("\n-- replica_2 state --");
170        println!("{:#?}", r2.state());
171    }
172}
173
174// Demo: Walk a deep tree
175//
176// This demonstrates creation of a deep tree which we then walk in depth-first
177// fashion.
178//
179// This particular tree contains 2^6-1 nodes and is up to 6 levels deep.
180fn demo_walk_deep_tree() {
181    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
182
183    let ids: HashMap<&str, TypeId> = [("root", new_id())].iter().cloned().collect();
184
185    // Generate initial tree state.
186    println!("generating ops...");
187    let mut ops = vec![(0, "root", ids["root"])];
188    mktree_ops(&mut ops, &mut r1, ids["root"], 2, 6); //  <-- max 6 levels deep.
189
190    println!("applying ops...");
191    let ops_len = ops.len();
192    r1.apply_ops_byref(&r1.opmoves(ops));
193
194    println!("walking tree...");
195    r1.tree().walk(&ids["root"], |tree, node_id, depth| {
196        if true {
197            let meta = match tree.find(node_id) {
198                Some(tn) => format!("{:?}", tn.metadata()),
199                None => format!("{:?}", node_id),
200            };
201            println!("{:indent$}{}", "", meta, indent = depth);
202        }
203    });
204
205    println!("\nnodes in tree: {}", ops_len);
206}
207
208/// Demonstrates log truncation
209///
210/// This requires that causally stable threshold tracking is enabled in `TreeReplica`
211fn demo_truncate_log() {
212    let mut replicas: Vec<TreeReplica<TypeId, TypeMeta, TypeActor>> = Vec::new();
213    let num_replicas = 5;
214
215    // start some replicas.
216    for _i in 0..num_replicas {
217        // pass true flag to enable causally stable threshold tracking
218        let r: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
219        replicas.push(r);
220    }
221
222    let root_id = new_id();
223
224    // Generate initial tree state.
225    let mut opmoves = vec![replicas[0].opmove(0, "root", root_id)];
226
227    println!("generating move operations...");
228
229    // generate some initial ops from all replicas.
230    for r in replicas.iter_mut() {
231        let finaldepth = rand::thread_rng().gen_range(3, 6);
232        let mut ops = vec![];
233        mktree_ops(&mut ops, r, root_id, 2, finaldepth);
234        opmoves.extend(r.opmoves(ops));
235    }
236
237    // apply all ops to all replicas
238    println!(
239        "applying {} operations to all {} replicas...\n",
240        opmoves.len(),
241        replicas.len()
242    );
243    apply_ops_to_replicas(&mut replicas, &opmoves);
244
245    #[derive(Debug)]
246    #[allow(dead_code)]
247    struct Stat {
248        pub replica: TypeActor,
249        pub ops_before_truncate: usize,
250        pub ops_after_truncate: usize,
251    }
252
253    let mut stats: Vec<Stat> = Vec::new();
254    for r in replicas.iter_mut() {
255        println!("truncating log of replica {}...", r.id());
256        println!(
257            "causally stable threshold: {:?}\n",
258            r.causally_stable_threshold()
259        );
260        let ops_b4 = r.state().log().len();
261        r.truncate_log();
262        let ops_after = r.state().log().len();
263        stats.push(Stat {
264            replica: *r.id(),
265            ops_before_truncate: ops_b4,
266            ops_after_truncate: ops_after,
267        });
268    }
269
270    println!("-- Stats -- ");
271    println!("\n{:#?}", stats);
272}
Source

pub fn tree(&self) -> &Tree<ID, TM>

Returns Tree reference

Examples found in repository?
examples/demo.rs (line 68)
42fn demo_concurrent_moves() {
43    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
44    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
45
46    let ids: HashMap<&str, TypeId> = [
47        ("root", new_id()),
48        ("a", new_id()),
49        ("b", new_id()),
50        ("c", new_id()),
51    ]
52    .iter()
53    .cloned()
54    .collect();
55
56    // Setup initial tree state.
57    let ops = r1.opmoves(vec![
58        (0, "root", ids["root"]),
59        (ids["root"], "a", ids["a"]),
60        (ids["root"], "b", ids["b"]),
61        (ids["root"], "c", ids["c"]),
62    ]);
63
64    r1.apply_ops_byref(&ops);
65    r2.apply_ops_byref(&ops);
66
67    println!("Initial tree state on both replicas");
68    print_tree(r1.tree(), &ids["root"]);
69
70    // replica_1 moves /root/a to /root/b
71    let repl1_ops = vec![r1.opmove(ids["b"], "a", ids["a"])];
72
73    // replica_2 "simultaneously" moves /root/a to /root/c
74    let repl2_ops = vec![r2.opmove(ids["c"], "a", ids["a"])];
75
76    // replica_1 applies his op, then merges op from replica_2
77    r1.apply_ops_byref(&repl1_ops);
78    println!("\nreplica_1 tree after move");
79    print_tree(r1.tree(), &ids["root"]);
80    r1.apply_ops_byref(&repl2_ops);
81
82    // replica_2 applies his op, then merges op from replica_1
83    r2.apply_ops_byref(&repl2_ops);
84    println!("\nreplica_2 tree after move");
85    print_tree(r2.tree(), &ids["root"]);
86    r2.apply_ops_byref(&repl1_ops);
87
88    // expected result: state is the same on both replicas
89    // and final path is /root/c/a because last-writer-wins
90    // and replica_2's op has a later timestamp.
91    //    if r1.state.is_equal(&r2.state) {
92    if r1.state() == r2.state() {
93        println!("\nreplica_1 state matches replica_2 state after each merges other's change.  conflict resolved!");
94        print_replica_trees(&r1, &r2, &ids["root"]);
95    } else {
96        println!("\nwarning: replica_1 state does not match replica_2 state after merge");
97        print_replica_trees(&r1, &r2, &ids["root"]);
98        println!("-- replica_1 state --");
99        println!("{:#?}", r1.state());
100        println!("\n-- replica_2 state --");
101        println!("{:#?}", r2.state());
102    }
103}
104
105// Demo: cycle test from the paper
106//
107// Tests what happen when two peers independently perform operations that would
108// introduce a cycle when combined.
109//
110// Upon applying eachother's ops, they must converge to a common location without
111// any cycles.
112fn demo_concurrent_moves_cycle() {
113    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
114    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
115
116    let ids: HashMap<&str, TypeId> = [
117        ("root", new_id()),
118        ("a", new_id()),
119        ("b", new_id()),
120        ("c", new_id()),
121    ]
122    .iter()
123    .cloned()
124    .collect();
125
126    // Setup initial tree state.
127    let ops = r1.opmoves(vec![
128        (0, "root", ids["root"]),
129        (ids["root"], "a", ids["a"]),
130        (ids["root"], "b", ids["b"]),
131        (ids["a"], "c", ids["c"]),
132    ]);
133
134    r1.apply_ops_byref(&ops);
135    r2.apply_ops_byref(&ops);
136
137    println!("Initial tree state on both replicas");
138    print_tree(r1.tree(), &ids["root"]);
139
140    // replica_1 moves /root/b to /root/a,  creating /root/a/b
141    let repl1_ops = r1.opmoves(vec![(ids["a"], "b", ids["b"])]);
142
143    // replica_2 "simultaneously" moves /root/a to /root/b, creating /root/b/a
144    let repl2_ops = r2.opmoves(vec![(ids["b"], "a", ids["a"])]);
145
146    // replica_1 applies his op, then merges op from replica_2
147    r1.apply_ops_byref(&repl1_ops);
148    println!("\nreplica_1 tree after move");
149    print_tree(r1.tree(), &ids["root"]);
150    r1.apply_ops_byref(&repl2_ops);
151
152    // replica_2 applies his op, then merges op from replica_1
153    r2.apply_ops_byref(&repl2_ops);
154    println!("\nreplica_2 tree after move");
155    print_tree(r2.tree(), &ids["root"]);
156    r2.apply_ops_byref(&repl1_ops);
157
158    // expected result: state is the same on both replicas
159    // and final path is /root/b/a because last-writer-wins
160    // and replica_2's op has a later timestamp.
161    if r1.state() == r2.state() {
162        println!("\nreplica_1 state matches replica_2 state after each merges other's change.  conflict resolved!");
163        print_replica_trees(&r1, &r2, &ids["root"]);
164    } else {
165        println!("\nwarning: replica_1 state does not match replica_2 state after merge");
166        print_replica_trees(&r1, &r2, &ids["root"]);
167        println!("-- replica_1 state --");
168        println!("{:#?}", r1.state());
169        println!("\n-- replica_2 state --");
170        println!("{:#?}", r2.state());
171    }
172}
173
174// Demo: Walk a deep tree
175//
176// This demonstrates creation of a deep tree which we then walk in depth-first
177// fashion.
178//
179// This particular tree contains 2^6-1 nodes and is up to 6 levels deep.
180fn demo_walk_deep_tree() {
181    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
182
183    let ids: HashMap<&str, TypeId> = [("root", new_id())].iter().cloned().collect();
184
185    // Generate initial tree state.
186    println!("generating ops...");
187    let mut ops = vec![(0, "root", ids["root"])];
188    mktree_ops(&mut ops, &mut r1, ids["root"], 2, 6); //  <-- max 6 levels deep.
189
190    println!("applying ops...");
191    let ops_len = ops.len();
192    r1.apply_ops_byref(&r1.opmoves(ops));
193
194    println!("walking tree...");
195    r1.tree().walk(&ids["root"], |tree, node_id, depth| {
196        if true {
197            let meta = match tree.find(node_id) {
198                Some(tn) => format!("{:?}", tn.metadata()),
199                None => format!("{:?}", node_id),
200            };
201            println!("{:indent$}{}", "", meta, indent = depth);
202        }
203    });
204
205    println!("\nnodes in tree: {}", ops_len);
206}
207
208/// Demonstrates log truncation
209///
210/// This requires that causally stable threshold tracking is enabled in `TreeReplica`
211fn demo_truncate_log() {
212    let mut replicas: Vec<TreeReplica<TypeId, TypeMeta, TypeActor>> = Vec::new();
213    let num_replicas = 5;
214
215    // start some replicas.
216    for _i in 0..num_replicas {
217        // pass true flag to enable causally stable threshold tracking
218        let r: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
219        replicas.push(r);
220    }
221
222    let root_id = new_id();
223
224    // Generate initial tree state.
225    let mut opmoves = vec![replicas[0].opmove(0, "root", root_id)];
226
227    println!("generating move operations...");
228
229    // generate some initial ops from all replicas.
230    for r in replicas.iter_mut() {
231        let finaldepth = rand::thread_rng().gen_range(3, 6);
232        let mut ops = vec![];
233        mktree_ops(&mut ops, r, root_id, 2, finaldepth);
234        opmoves.extend(r.opmoves(ops));
235    }
236
237    // apply all ops to all replicas
238    println!(
239        "applying {} operations to all {} replicas...\n",
240        opmoves.len(),
241        replicas.len()
242    );
243    apply_ops_to_replicas(&mut replicas, &opmoves);
244
245    #[derive(Debug)]
246    #[allow(dead_code)]
247    struct Stat {
248        pub replica: TypeActor,
249        pub ops_before_truncate: usize,
250        pub ops_after_truncate: usize,
251    }
252
253    let mut stats: Vec<Stat> = Vec::new();
254    for r in replicas.iter_mut() {
255        println!("truncating log of replica {}...", r.id());
256        println!(
257            "causally stable threshold: {:?}\n",
258            r.causally_stable_threshold()
259        );
260        let ops_b4 = r.state().log().len();
261        r.truncate_log();
262        let ops_after = r.state().log().len();
263        stats.push(Stat {
264            replica: *r.id(),
265            ops_before_truncate: ops_b4,
266            ops_after_truncate: ops_after,
267        });
268    }
269
270    println!("-- Stats -- ");
271    println!("\n{:#?}", stats);
272}
273
274/// Demonstrates moving items to a Trash node outside the nominal root and then
275/// emptying the trash after the log is truncated.
276///
277/// This requires that causally stable threshold tracking is enabled in `TreeReplica`
278fn demo_move_to_trash() {
279    // pass true flag to enable causally stable threshold tracking
280    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
281    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
282
283    let ids: HashMap<&str, TypeId> = [
284        ("forest", new_id()),
285        ("trash", new_id()),
286        ("root", new_id()),
287        ("home", new_id()),
288        ("bob", new_id()),
289        ("project", new_id()),
290    ]
291    .iter()
292    .cloned()
293    .collect();
294
295    // Generate initial tree state.
296    //
297    // - forest
298    //   - trash
299    //   - root
300    //     - home
301    //       - bob
302    //         - project
303    let mut ops = vec![
304        (ids["forest"], "root", ids["root"]),
305        (ids["forest"], "trash", ids["trash"]),
306        (ids["root"], "home", ids["home"]),
307        (ids["home"], "bob", ids["bob"]),
308        (ids["bob"], "project", ids["project"]),
309    ];
310
311    // add some nodes under project
312    mktree_ops(&mut ops, &mut r1, ids["project"], 2, 3);
313    let opmoves = r1.opmoves(ops);
314    r1.apply_ops_byref(&opmoves);
315    r2.apply_ops_byref(&opmoves);
316
317    println!("Initial tree");
318    print_tree(r1.tree(), &ids["forest"]);
319
320    // move project to trash
321    let ops = vec![r1.opmove(ids["trash"], "project", ids["project"])];
322    r1.apply_ops_byref(&ops);
323    r2.apply_ops_byref(&ops);
324
325    println!("\nAfter project moved to trash (deleted) on both replicas");
326    print_tree(r1.tree(), &ids["forest"]);
327
328    // Initially, trashed nodes must be retained because a concurrent move
329    // operation may move them back out of the trash.
330    //
331    // Once the operation that moved a node to the trash is causally
332    // stable, we know that no future operations will refer to this node,
333    // and so the trashed node and its descendants can be discarded.
334    //
335    // note:  change r1.opmoves() to r2.opmoves() above to
336    //        make the causally stable threshold less than the trash operation
337    //        timestamp, which will cause this test to fail, ie hit the
338    //        "trash should not be emptied" condition.
339    let result = r2.causally_stable_threshold();
340    match result {
341        Some(cst) if cst < ops[0].timestamp() => {
342            println!(
343                "\ncausally stable threshold:\n{:#?}\n\ntrash operation:\n{:#?}",
344                cst,
345                ops[0].timestamp()
346            );
347            panic!("!error: causally stable threshold is less than trash operation timestamp");
348        }
349        None => panic!("!error: causally stable threshold not found"),
350        _ => {}
351    }
352
353    // empty trash
354    r1.tree_mut().rm_subtree(&ids["trash"], false);
355    println!("\nDelete op is now causally stable, so we can empty trash:");
356    print_tree(r1.tree(), &ids["forest"]);
357}
358
359fn print_help() {
360    let buf = "
361Usage: tree <demo>
362
363<demo> can be any of:
364  demo_concurrent_moves
365  demo_concurrent_moves_cycle
366  demo_truncate_log
367  demo_walk_deep_tree
368  demo_move_to_trash
369
370";
371    println!("{}", buf);
372}
373
374// Returns op tuples representing a depth-first tree,
375// with 2 children for each parent.
376fn mktree_ops(
377    ops: &mut Vec<(TypeId, TypeMeta, TypeActor)>,
378    r: &mut TreeReplica<TypeId, TypeMeta, TypeActor>,
379    parent_id: u64,
380    depth: usize,
381    max_depth: usize,
382) {
383    if depth > max_depth {
384        return;
385    }
386
387    for i in 0..2 {
388        let name = if i == 0 { "a" } else { "b" };
389        let child_id = new_id();
390        ops.push((parent_id, name, child_id));
391        mktree_ops(ops, r, child_id, depth + 1, max_depth);
392    }
393}
394
395// applies each operation in ops to each replica in replicas.
396fn apply_ops_to_replicas<ID, TM, A>(
397    replicas: &mut [TreeReplica<ID, TM, A>],
398    ops: &[OpMove<ID, TM, A>],
399) where
400    ID: TreeId,
401    A: Actor + std::fmt::Debug,
402    TM: TreeMeta,
403{
404    for r in replicas.iter_mut() {
405        r.apply_ops_byref(ops);
406    }
407}
408
409// note: in practice a UUID (at least 128 bits should be used)
410fn new_id() -> TypeId {
411    rand::random::<TypeId>()
412}
413
414// print a treenode, recursively
415fn print_treenode<ID, TM>(tree: &Tree<ID, TM>, node_id: &ID, depth: usize, with_id: bool)
416where
417    ID: TreeId + std::fmt::Debug,
418    TM: TreeMeta + std::fmt::Debug,
419{
420    let result = tree.find(node_id);
421    let meta = match result {
422        Some(tn) => format!("{:?}", tn.metadata()),
423        None if depth == 0 => "forest".to_string(),
424        None => {
425            panic!("tree node {:?} not found", node_id);
426        }
427    };
428    println!("{:indent$}{}", "", meta, indent = depth * 2);
429
430    for c in tree.children(node_id) {
431        print_treenode(tree, &c, depth + 1, with_id);
432    }
433}
434
435// print a tree.
436fn print_tree<ID, TM>(tree: &Tree<ID, TM>, root: &ID)
437where
438    ID: TreeId + std::fmt::Debug,
439    TM: TreeMeta + std::fmt::Debug,
440{
441    print_treenode(tree, root, 0, false);
442}
443
444// print trees for two replicas
445fn print_replica_trees<ID, TM, A>(
446    repl1: &TreeReplica<ID, TM, A>,
447    repl2: &TreeReplica<ID, TM, A>,
448    root: &ID,
449) where
450    ID: TreeId + std::fmt::Debug,
451    A: Actor + std::fmt::Debug,
452    TM: TreeMeta + std::fmt::Debug,
453{
454    println!("\n--replica_1 --");
455    print_tree(repl1.tree(), root);
456    println!("\n--replica_2 --");
457    print_tree(repl2.tree(), root);
458    println!();
459}
Source

pub fn tree_mut(&mut self) -> &mut Tree<ID, TM>

Returns mutable Tree reference

Warning: this is dangerous. Normally the Tree should not be mutated directly.

See the demo_move_to_trash in examples/demo.rs for a use-case, only after log truncation has been performed.

Examples found in repository?
examples/demo.rs (line 354)
278fn demo_move_to_trash() {
279    // pass true flag to enable causally stable threshold tracking
280    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
281    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
282
283    let ids: HashMap<&str, TypeId> = [
284        ("forest", new_id()),
285        ("trash", new_id()),
286        ("root", new_id()),
287        ("home", new_id()),
288        ("bob", new_id()),
289        ("project", new_id()),
290    ]
291    .iter()
292    .cloned()
293    .collect();
294
295    // Generate initial tree state.
296    //
297    // - forest
298    //   - trash
299    //   - root
300    //     - home
301    //       - bob
302    //         - project
303    let mut ops = vec![
304        (ids["forest"], "root", ids["root"]),
305        (ids["forest"], "trash", ids["trash"]),
306        (ids["root"], "home", ids["home"]),
307        (ids["home"], "bob", ids["bob"]),
308        (ids["bob"], "project", ids["project"]),
309    ];
310
311    // add some nodes under project
312    mktree_ops(&mut ops, &mut r1, ids["project"], 2, 3);
313    let opmoves = r1.opmoves(ops);
314    r1.apply_ops_byref(&opmoves);
315    r2.apply_ops_byref(&opmoves);
316
317    println!("Initial tree");
318    print_tree(r1.tree(), &ids["forest"]);
319
320    // move project to trash
321    let ops = vec![r1.opmove(ids["trash"], "project", ids["project"])];
322    r1.apply_ops_byref(&ops);
323    r2.apply_ops_byref(&ops);
324
325    println!("\nAfter project moved to trash (deleted) on both replicas");
326    print_tree(r1.tree(), &ids["forest"]);
327
328    // Initially, trashed nodes must be retained because a concurrent move
329    // operation may move them back out of the trash.
330    //
331    // Once the operation that moved a node to the trash is causally
332    // stable, we know that no future operations will refer to this node,
333    // and so the trashed node and its descendants can be discarded.
334    //
335    // note:  change r1.opmoves() to r2.opmoves() above to
336    //        make the causally stable threshold less than the trash operation
337    //        timestamp, which will cause this test to fail, ie hit the
338    //        "trash should not be emptied" condition.
339    let result = r2.causally_stable_threshold();
340    match result {
341        Some(cst) if cst < ops[0].timestamp() => {
342            println!(
343                "\ncausally stable threshold:\n{:#?}\n\ntrash operation:\n{:#?}",
344                cst,
345                ops[0].timestamp()
346            );
347            panic!("!error: causally stable threshold is less than trash operation timestamp");
348        }
349        None => panic!("!error: causally stable threshold not found"),
350        _ => {}
351    }
352
353    // empty trash
354    r1.tree_mut().rm_subtree(&ids["trash"], false);
355    println!("\nDelete op is now causally stable, so we can empty trash:");
356    print_tree(r1.tree(), &ids["forest"]);
357}
Source

pub fn apply_op(&mut self, op: OpMove<ID, TM, A>)

Applies single operation to State and updates our time clock

Also records latest timestamp for each replica if track_causally_stable_threshold flag is set.

Source

pub fn apply_ops(&mut self, ops: Vec<OpMove<ID, TM, A>>)

Applies list of operations

Source

pub fn apply_ops_byref(&mut self, ops: &[OpMove<ID, TM, A>])

Applies list of operations without taking ownership

Examples found in repository?
examples/demo.rs (line 64)
42fn demo_concurrent_moves() {
43    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
44    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
45
46    let ids: HashMap<&str, TypeId> = [
47        ("root", new_id()),
48        ("a", new_id()),
49        ("b", new_id()),
50        ("c", new_id()),
51    ]
52    .iter()
53    .cloned()
54    .collect();
55
56    // Setup initial tree state.
57    let ops = r1.opmoves(vec![
58        (0, "root", ids["root"]),
59        (ids["root"], "a", ids["a"]),
60        (ids["root"], "b", ids["b"]),
61        (ids["root"], "c", ids["c"]),
62    ]);
63
64    r1.apply_ops_byref(&ops);
65    r2.apply_ops_byref(&ops);
66
67    println!("Initial tree state on both replicas");
68    print_tree(r1.tree(), &ids["root"]);
69
70    // replica_1 moves /root/a to /root/b
71    let repl1_ops = vec![r1.opmove(ids["b"], "a", ids["a"])];
72
73    // replica_2 "simultaneously" moves /root/a to /root/c
74    let repl2_ops = vec![r2.opmove(ids["c"], "a", ids["a"])];
75
76    // replica_1 applies his op, then merges op from replica_2
77    r1.apply_ops_byref(&repl1_ops);
78    println!("\nreplica_1 tree after move");
79    print_tree(r1.tree(), &ids["root"]);
80    r1.apply_ops_byref(&repl2_ops);
81
82    // replica_2 applies his op, then merges op from replica_1
83    r2.apply_ops_byref(&repl2_ops);
84    println!("\nreplica_2 tree after move");
85    print_tree(r2.tree(), &ids["root"]);
86    r2.apply_ops_byref(&repl1_ops);
87
88    // expected result: state is the same on both replicas
89    // and final path is /root/c/a because last-writer-wins
90    // and replica_2's op has a later timestamp.
91    //    if r1.state.is_equal(&r2.state) {
92    if r1.state() == r2.state() {
93        println!("\nreplica_1 state matches replica_2 state after each merges other's change.  conflict resolved!");
94        print_replica_trees(&r1, &r2, &ids["root"]);
95    } else {
96        println!("\nwarning: replica_1 state does not match replica_2 state after merge");
97        print_replica_trees(&r1, &r2, &ids["root"]);
98        println!("-- replica_1 state --");
99        println!("{:#?}", r1.state());
100        println!("\n-- replica_2 state --");
101        println!("{:#?}", r2.state());
102    }
103}
104
105// Demo: cycle test from the paper
106//
107// Tests what happen when two peers independently perform operations that would
108// introduce a cycle when combined.
109//
110// Upon applying eachother's ops, they must converge to a common location without
111// any cycles.
112fn demo_concurrent_moves_cycle() {
113    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
114    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
115
116    let ids: HashMap<&str, TypeId> = [
117        ("root", new_id()),
118        ("a", new_id()),
119        ("b", new_id()),
120        ("c", new_id()),
121    ]
122    .iter()
123    .cloned()
124    .collect();
125
126    // Setup initial tree state.
127    let ops = r1.opmoves(vec![
128        (0, "root", ids["root"]),
129        (ids["root"], "a", ids["a"]),
130        (ids["root"], "b", ids["b"]),
131        (ids["a"], "c", ids["c"]),
132    ]);
133
134    r1.apply_ops_byref(&ops);
135    r2.apply_ops_byref(&ops);
136
137    println!("Initial tree state on both replicas");
138    print_tree(r1.tree(), &ids["root"]);
139
140    // replica_1 moves /root/b to /root/a,  creating /root/a/b
141    let repl1_ops = r1.opmoves(vec![(ids["a"], "b", ids["b"])]);
142
143    // replica_2 "simultaneously" moves /root/a to /root/b, creating /root/b/a
144    let repl2_ops = r2.opmoves(vec![(ids["b"], "a", ids["a"])]);
145
146    // replica_1 applies his op, then merges op from replica_2
147    r1.apply_ops_byref(&repl1_ops);
148    println!("\nreplica_1 tree after move");
149    print_tree(r1.tree(), &ids["root"]);
150    r1.apply_ops_byref(&repl2_ops);
151
152    // replica_2 applies his op, then merges op from replica_1
153    r2.apply_ops_byref(&repl2_ops);
154    println!("\nreplica_2 tree after move");
155    print_tree(r2.tree(), &ids["root"]);
156    r2.apply_ops_byref(&repl1_ops);
157
158    // expected result: state is the same on both replicas
159    // and final path is /root/b/a because last-writer-wins
160    // and replica_2's op has a later timestamp.
161    if r1.state() == r2.state() {
162        println!("\nreplica_1 state matches replica_2 state after each merges other's change.  conflict resolved!");
163        print_replica_trees(&r1, &r2, &ids["root"]);
164    } else {
165        println!("\nwarning: replica_1 state does not match replica_2 state after merge");
166        print_replica_trees(&r1, &r2, &ids["root"]);
167        println!("-- replica_1 state --");
168        println!("{:#?}", r1.state());
169        println!("\n-- replica_2 state --");
170        println!("{:#?}", r2.state());
171    }
172}
173
174// Demo: Walk a deep tree
175//
176// This demonstrates creation of a deep tree which we then walk in depth-first
177// fashion.
178//
179// This particular tree contains 2^6-1 nodes and is up to 6 levels deep.
180fn demo_walk_deep_tree() {
181    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
182
183    let ids: HashMap<&str, TypeId> = [("root", new_id())].iter().cloned().collect();
184
185    // Generate initial tree state.
186    println!("generating ops...");
187    let mut ops = vec![(0, "root", ids["root"])];
188    mktree_ops(&mut ops, &mut r1, ids["root"], 2, 6); //  <-- max 6 levels deep.
189
190    println!("applying ops...");
191    let ops_len = ops.len();
192    r1.apply_ops_byref(&r1.opmoves(ops));
193
194    println!("walking tree...");
195    r1.tree().walk(&ids["root"], |tree, node_id, depth| {
196        if true {
197            let meta = match tree.find(node_id) {
198                Some(tn) => format!("{:?}", tn.metadata()),
199                None => format!("{:?}", node_id),
200            };
201            println!("{:indent$}{}", "", meta, indent = depth);
202        }
203    });
204
205    println!("\nnodes in tree: {}", ops_len);
206}
207
208/// Demonstrates log truncation
209///
210/// This requires that causally stable threshold tracking is enabled in `TreeReplica`
211fn demo_truncate_log() {
212    let mut replicas: Vec<TreeReplica<TypeId, TypeMeta, TypeActor>> = Vec::new();
213    let num_replicas = 5;
214
215    // start some replicas.
216    for _i in 0..num_replicas {
217        // pass true flag to enable causally stable threshold tracking
218        let r: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
219        replicas.push(r);
220    }
221
222    let root_id = new_id();
223
224    // Generate initial tree state.
225    let mut opmoves = vec![replicas[0].opmove(0, "root", root_id)];
226
227    println!("generating move operations...");
228
229    // generate some initial ops from all replicas.
230    for r in replicas.iter_mut() {
231        let finaldepth = rand::thread_rng().gen_range(3, 6);
232        let mut ops = vec![];
233        mktree_ops(&mut ops, r, root_id, 2, finaldepth);
234        opmoves.extend(r.opmoves(ops));
235    }
236
237    // apply all ops to all replicas
238    println!(
239        "applying {} operations to all {} replicas...\n",
240        opmoves.len(),
241        replicas.len()
242    );
243    apply_ops_to_replicas(&mut replicas, &opmoves);
244
245    #[derive(Debug)]
246    #[allow(dead_code)]
247    struct Stat {
248        pub replica: TypeActor,
249        pub ops_before_truncate: usize,
250        pub ops_after_truncate: usize,
251    }
252
253    let mut stats: Vec<Stat> = Vec::new();
254    for r in replicas.iter_mut() {
255        println!("truncating log of replica {}...", r.id());
256        println!(
257            "causally stable threshold: {:?}\n",
258            r.causally_stable_threshold()
259        );
260        let ops_b4 = r.state().log().len();
261        r.truncate_log();
262        let ops_after = r.state().log().len();
263        stats.push(Stat {
264            replica: *r.id(),
265            ops_before_truncate: ops_b4,
266            ops_after_truncate: ops_after,
267        });
268    }
269
270    println!("-- Stats -- ");
271    println!("\n{:#?}", stats);
272}
273
274/// Demonstrates moving items to a Trash node outside the nominal root and then
275/// emptying the trash after the log is truncated.
276///
277/// This requires that causally stable threshold tracking is enabled in `TreeReplica`
278fn demo_move_to_trash() {
279    // pass true flag to enable causally stable threshold tracking
280    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
281    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
282
283    let ids: HashMap<&str, TypeId> = [
284        ("forest", new_id()),
285        ("trash", new_id()),
286        ("root", new_id()),
287        ("home", new_id()),
288        ("bob", new_id()),
289        ("project", new_id()),
290    ]
291    .iter()
292    .cloned()
293    .collect();
294
295    // Generate initial tree state.
296    //
297    // - forest
298    //   - trash
299    //   - root
300    //     - home
301    //       - bob
302    //         - project
303    let mut ops = vec![
304        (ids["forest"], "root", ids["root"]),
305        (ids["forest"], "trash", ids["trash"]),
306        (ids["root"], "home", ids["home"]),
307        (ids["home"], "bob", ids["bob"]),
308        (ids["bob"], "project", ids["project"]),
309    ];
310
311    // add some nodes under project
312    mktree_ops(&mut ops, &mut r1, ids["project"], 2, 3);
313    let opmoves = r1.opmoves(ops);
314    r1.apply_ops_byref(&opmoves);
315    r2.apply_ops_byref(&opmoves);
316
317    println!("Initial tree");
318    print_tree(r1.tree(), &ids["forest"]);
319
320    // move project to trash
321    let ops = vec![r1.opmove(ids["trash"], "project", ids["project"])];
322    r1.apply_ops_byref(&ops);
323    r2.apply_ops_byref(&ops);
324
325    println!("\nAfter project moved to trash (deleted) on both replicas");
326    print_tree(r1.tree(), &ids["forest"]);
327
328    // Initially, trashed nodes must be retained because a concurrent move
329    // operation may move them back out of the trash.
330    //
331    // Once the operation that moved a node to the trash is causally
332    // stable, we know that no future operations will refer to this node,
333    // and so the trashed node and its descendants can be discarded.
334    //
335    // note:  change r1.opmoves() to r2.opmoves() above to
336    //        make the causally stable threshold less than the trash operation
337    //        timestamp, which will cause this test to fail, ie hit the
338    //        "trash should not be emptied" condition.
339    let result = r2.causally_stable_threshold();
340    match result {
341        Some(cst) if cst < ops[0].timestamp() => {
342            println!(
343                "\ncausally stable threshold:\n{:#?}\n\ntrash operation:\n{:#?}",
344                cst,
345                ops[0].timestamp()
346            );
347            panic!("!error: causally stable threshold is less than trash operation timestamp");
348        }
349        None => panic!("!error: causally stable threshold not found"),
350        _ => {}
351    }
352
353    // empty trash
354    r1.tree_mut().rm_subtree(&ids["trash"], false);
355    println!("\nDelete op is now causally stable, so we can empty trash:");
356    print_tree(r1.tree(), &ids["forest"]);
357}
358
359fn print_help() {
360    let buf = "
361Usage: tree <demo>
362
363<demo> can be any of:
364  demo_concurrent_moves
365  demo_concurrent_moves_cycle
366  demo_truncate_log
367  demo_walk_deep_tree
368  demo_move_to_trash
369
370";
371    println!("{}", buf);
372}
373
374// Returns op tuples representing a depth-first tree,
375// with 2 children for each parent.
376fn mktree_ops(
377    ops: &mut Vec<(TypeId, TypeMeta, TypeActor)>,
378    r: &mut TreeReplica<TypeId, TypeMeta, TypeActor>,
379    parent_id: u64,
380    depth: usize,
381    max_depth: usize,
382) {
383    if depth > max_depth {
384        return;
385    }
386
387    for i in 0..2 {
388        let name = if i == 0 { "a" } else { "b" };
389        let child_id = new_id();
390        ops.push((parent_id, name, child_id));
391        mktree_ops(ops, r, child_id, depth + 1, max_depth);
392    }
393}
394
395// applies each operation in ops to each replica in replicas.
396fn apply_ops_to_replicas<ID, TM, A>(
397    replicas: &mut [TreeReplica<ID, TM, A>],
398    ops: &[OpMove<ID, TM, A>],
399) where
400    ID: TreeId,
401    A: Actor + std::fmt::Debug,
402    TM: TreeMeta,
403{
404    for r in replicas.iter_mut() {
405        r.apply_ops_byref(ops);
406    }
407}
Source

pub fn apply_log_op(&mut self, log_op: LogOpMove<ID, TM, A>)

applies op from a log. useful for log replay.

Source

pub fn apply_log_ops(&mut self, log_ops: Vec<LogOpMove<ID, TM, A>>)

applies ops from a log. useful for log replay.

Source

pub fn causally_stable_threshold(&self) -> Option<&Clock<A>>

returns the causally stable threshold

Examples found in repository?
examples/demo.rs (line 258)
211fn demo_truncate_log() {
212    let mut replicas: Vec<TreeReplica<TypeId, TypeMeta, TypeActor>> = Vec::new();
213    let num_replicas = 5;
214
215    // start some replicas.
216    for _i in 0..num_replicas {
217        // pass true flag to enable causally stable threshold tracking
218        let r: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
219        replicas.push(r);
220    }
221
222    let root_id = new_id();
223
224    // Generate initial tree state.
225    let mut opmoves = vec![replicas[0].opmove(0, "root", root_id)];
226
227    println!("generating move operations...");
228
229    // generate some initial ops from all replicas.
230    for r in replicas.iter_mut() {
231        let finaldepth = rand::thread_rng().gen_range(3, 6);
232        let mut ops = vec![];
233        mktree_ops(&mut ops, r, root_id, 2, finaldepth);
234        opmoves.extend(r.opmoves(ops));
235    }
236
237    // apply all ops to all replicas
238    println!(
239        "applying {} operations to all {} replicas...\n",
240        opmoves.len(),
241        replicas.len()
242    );
243    apply_ops_to_replicas(&mut replicas, &opmoves);
244
245    #[derive(Debug)]
246    #[allow(dead_code)]
247    struct Stat {
248        pub replica: TypeActor,
249        pub ops_before_truncate: usize,
250        pub ops_after_truncate: usize,
251    }
252
253    let mut stats: Vec<Stat> = Vec::new();
254    for r in replicas.iter_mut() {
255        println!("truncating log of replica {}...", r.id());
256        println!(
257            "causally stable threshold: {:?}\n",
258            r.causally_stable_threshold()
259        );
260        let ops_b4 = r.state().log().len();
261        r.truncate_log();
262        let ops_after = r.state().log().len();
263        stats.push(Stat {
264            replica: *r.id(),
265            ops_before_truncate: ops_b4,
266            ops_after_truncate: ops_after,
267        });
268    }
269
270    println!("-- Stats -- ");
271    println!("\n{:#?}", stats);
272}
273
274/// Demonstrates moving items to a Trash node outside the nominal root and then
275/// emptying the trash after the log is truncated.
276///
277/// This requires that causally stable threshold tracking is enabled in `TreeReplica`
278fn demo_move_to_trash() {
279    // pass true flag to enable causally stable threshold tracking
280    let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
281    let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
282
283    let ids: HashMap<&str, TypeId> = [
284        ("forest", new_id()),
285        ("trash", new_id()),
286        ("root", new_id()),
287        ("home", new_id()),
288        ("bob", new_id()),
289        ("project", new_id()),
290    ]
291    .iter()
292    .cloned()
293    .collect();
294
295    // Generate initial tree state.
296    //
297    // - forest
298    //   - trash
299    //   - root
300    //     - home
301    //       - bob
302    //         - project
303    let mut ops = vec![
304        (ids["forest"], "root", ids["root"]),
305        (ids["forest"], "trash", ids["trash"]),
306        (ids["root"], "home", ids["home"]),
307        (ids["home"], "bob", ids["bob"]),
308        (ids["bob"], "project", ids["project"]),
309    ];
310
311    // add some nodes under project
312    mktree_ops(&mut ops, &mut r1, ids["project"], 2, 3);
313    let opmoves = r1.opmoves(ops);
314    r1.apply_ops_byref(&opmoves);
315    r2.apply_ops_byref(&opmoves);
316
317    println!("Initial tree");
318    print_tree(r1.tree(), &ids["forest"]);
319
320    // move project to trash
321    let ops = vec![r1.opmove(ids["trash"], "project", ids["project"])];
322    r1.apply_ops_byref(&ops);
323    r2.apply_ops_byref(&ops);
324
325    println!("\nAfter project moved to trash (deleted) on both replicas");
326    print_tree(r1.tree(), &ids["forest"]);
327
328    // Initially, trashed nodes must be retained because a concurrent move
329    // operation may move them back out of the trash.
330    //
331    // Once the operation that moved a node to the trash is causally
332    // stable, we know that no future operations will refer to this node,
333    // and so the trashed node and its descendants can be discarded.
334    //
335    // note:  change r1.opmoves() to r2.opmoves() above to
336    //        make the causally stable threshold less than the trash operation
337    //        timestamp, which will cause this test to fail, ie hit the
338    //        "trash should not be emptied" condition.
339    let result = r2.causally_stable_threshold();
340    match result {
341        Some(cst) if cst < ops[0].timestamp() => {
342            println!(
343                "\ncausally stable threshold:\n{:#?}\n\ntrash operation:\n{:#?}",
344                cst,
345                ops[0].timestamp()
346            );
347            panic!("!error: causally stable threshold is less than trash operation timestamp");
348        }
349        None => panic!("!error: causally stable threshold not found"),
350        _ => {}
351    }
352
353    // empty trash
354    r1.tree_mut().rm_subtree(&ids["trash"], false);
355    println!("\nDelete op is now causally stable, so we can empty trash:");
356    print_tree(r1.tree(), &ids["forest"]);
357}
Source

pub fn truncate_log(&mut self) -> bool

truncates log

Examples found in repository?
examples/demo.rs (line 261)
211fn demo_truncate_log() {
212    let mut replicas: Vec<TreeReplica<TypeId, TypeMeta, TypeActor>> = Vec::new();
213    let num_replicas = 5;
214
215    // start some replicas.
216    for _i in 0..num_replicas {
217        // pass true flag to enable causally stable threshold tracking
218        let r: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
219        replicas.push(r);
220    }
221
222    let root_id = new_id();
223
224    // Generate initial tree state.
225    let mut opmoves = vec![replicas[0].opmove(0, "root", root_id)];
226
227    println!("generating move operations...");
228
229    // generate some initial ops from all replicas.
230    for r in replicas.iter_mut() {
231        let finaldepth = rand::thread_rng().gen_range(3, 6);
232        let mut ops = vec![];
233        mktree_ops(&mut ops, r, root_id, 2, finaldepth);
234        opmoves.extend(r.opmoves(ops));
235    }
236
237    // apply all ops to all replicas
238    println!(
239        "applying {} operations to all {} replicas...\n",
240        opmoves.len(),
241        replicas.len()
242    );
243    apply_ops_to_replicas(&mut replicas, &opmoves);
244
245    #[derive(Debug)]
246    #[allow(dead_code)]
247    struct Stat {
248        pub replica: TypeActor,
249        pub ops_before_truncate: usize,
250        pub ops_after_truncate: usize,
251    }
252
253    let mut stats: Vec<Stat> = Vec::new();
254    for r in replicas.iter_mut() {
255        println!("truncating log of replica {}...", r.id());
256        println!(
257            "causally stable threshold: {:?}\n",
258            r.causally_stable_threshold()
259        );
260        let ops_b4 = r.state().log().len();
261        r.truncate_log();
262        let ops_after = r.state().log().len();
263        stats.push(Stat {
264            replica: *r.id(),
265            ops_before_truncate: ops_b4,
266            ops_after_truncate: ops_after,
267        });
268    }
269
270    println!("-- Stats -- ");
271    println!("\n{:#?}", stats);
272}

Trait Implementations§

Source§

impl<ID: Clone + TreeId, TM: Clone + TreeMeta, A: Clone + Actor> Clone for TreeReplica<ID, TM, A>

Source§

fn clone(&self) -> TreeReplica<ID, TM, A>

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<ID: Debug + TreeId, TM: Debug + TreeMeta, A: Debug + Actor> Debug for TreeReplica<ID, TM, A>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de, ID, TM, A> Deserialize<'de> for TreeReplica<ID, TM, A>
where ID: Deserialize<'de> + TreeId, TM: Deserialize<'de> + TreeMeta, A: Deserialize<'de> + Actor,

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl<ID: PartialEq + TreeId, TM: PartialEq + TreeMeta, A: PartialEq + Actor> PartialEq for TreeReplica<ID, TM, A>

Source§

fn eq(&self, other: &TreeReplica<ID, TM, A>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl<ID, TM, A> Serialize for TreeReplica<ID, TM, A>
where ID: Serialize + TreeId, TM: Serialize + TreeMeta, A: Serialize + Actor,

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl<ID: Eq + TreeId, TM: Eq + TreeMeta, A: Eq + Actor> Eq for TreeReplica<ID, TM, A>

Source§

impl<ID: TreeId, TM: TreeMeta, A: Actor> StructuralPartialEq for TreeReplica<ID, TM, A>

Auto Trait Implementations§

§

impl<ID, TM, A> Freeze for TreeReplica<ID, TM, A>
where A: Freeze,

§

impl<ID, TM, A> RefUnwindSafe for TreeReplica<ID, TM, A>

§

impl<ID, TM, A> Send for TreeReplica<ID, TM, A>
where A: Send, ID: Send, TM: Send,

§

impl<ID, TM, A> Sync for TreeReplica<ID, TM, A>
where A: Sync, ID: Sync, TM: Sync,

§

impl<ID, TM, A> Unpin for TreeReplica<ID, TM, A>
where A: Unpin, ID: Unpin, TM: Unpin,

§

impl<ID, TM, A> UnwindSafe for TreeReplica<ID, TM, A>
where A: UnwindSafe, ID: UnwindSafe, TM: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<TM> TreeMeta for TM
where TM: Clone,