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>
impl<ID: TreeId, TM: TreeMeta, A: Actor + Debug> TreeReplica<ID, TM, A>
Sourcepub fn new(id: A) -> Self
pub fn new(id: A) -> Self
returns new TreeReplica
Examples found in repository?
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}Sourcepub fn opmove(
&self,
parent_id: ID,
metadata: TM,
child_id: ID,
) -> OpMove<ID, TM, A>
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?
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}Sourcepub fn opmoves(&self, ops: Vec<(ID, TM, ID)>) -> Vec<OpMove<ID, TM, A>>
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?
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}Sourcepub fn id(&self) -> &A
pub fn id(&self) -> &A
Returns actor ID for this replica
Examples found in repository?
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}Sourcepub fn state(&self) -> &State<ID, TM, A>
pub fn state(&self) -> &State<ID, TM, A>
Returns Tree State reference
Examples found in repository?
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}Sourcepub fn tree(&self) -> &Tree<ID, TM>
pub fn tree(&self) -> &Tree<ID, TM>
Returns Tree reference
Examples found in repository?
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}Sourcepub fn tree_mut(&mut self) -> &mut Tree<ID, TM>
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?
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}Sourcepub fn apply_op(&mut self, op: OpMove<ID, TM, A>)
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.
Sourcepub fn apply_ops_byref(&mut self, ops: &[OpMove<ID, TM, A>])
pub fn apply_ops_byref(&mut self, ops: &[OpMove<ID, TM, A>])
Applies list of operations without taking ownership
Examples found in repository?
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}Sourcepub fn apply_log_op(&mut self, log_op: LogOpMove<ID, TM, A>)
pub fn apply_log_op(&mut self, log_op: LogOpMove<ID, TM, A>)
applies op from a log. useful for log replay.
Sourcepub fn apply_log_ops(&mut self, log_ops: Vec<LogOpMove<ID, TM, A>>)
pub fn apply_log_ops(&mut self, log_ops: Vec<LogOpMove<ID, TM, A>>)
applies ops from a log. useful for log replay.
Sourcepub fn causally_stable_threshold(&self) -> Option<&Clock<A>>
pub fn causally_stable_threshold(&self) -> Option<&Clock<A>>
returns the causally stable threshold
Examples found in repository?
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}Sourcepub fn truncate_log(&mut self) -> bool
pub fn truncate_log(&mut self) -> bool
truncates log
Examples found in repository?
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>
impl<ID: Clone + TreeId, TM: Clone + TreeMeta, A: Clone + Actor> Clone for TreeReplica<ID, TM, A>
Source§fn clone(&self) -> TreeReplica<ID, TM, A>
fn clone(&self) -> TreeReplica<ID, TM, A>
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl<ID: Debug + TreeId, TM: Debug + TreeMeta, A: Debug + Actor> Debug for TreeReplica<ID, TM, A>
impl<ID: Debug + TreeId, TM: Debug + TreeMeta, A: Debug + Actor> Debug for TreeReplica<ID, TM, A>
Source§impl<'de, ID, TM, A> Deserialize<'de> for TreeReplica<ID, TM, A>
impl<'de, ID, TM, A> Deserialize<'de> for TreeReplica<ID, TM, A>
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl<ID: PartialEq + TreeId, TM: PartialEq + TreeMeta, A: PartialEq + Actor> PartialEq for TreeReplica<ID, TM, A>
impl<ID: PartialEq + TreeId, TM: PartialEq + TreeMeta, A: PartialEq + Actor> PartialEq for TreeReplica<ID, TM, A>
Source§impl<ID, TM, A> Serialize for TreeReplica<ID, TM, A>
impl<ID, TM, A> Serialize for TreeReplica<ID, TM, A>
impl<ID: Eq + TreeId, TM: Eq + TreeMeta, A: Eq + Actor> Eq for TreeReplica<ID, TM, A>
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>
impl<ID, TM, A> Sync for TreeReplica<ID, TM, A>
impl<ID, TM, A> Unpin for TreeReplica<ID, TM, A>
impl<ID, TM, A> UnwindSafe for TreeReplica<ID, TM, A>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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