public void Integration_test() { var p0 = Replica.Empty(ReplicaId.New("p")).ApplyCmd(new Doc().DownField("key").Assign("A")); var q0 = Merge(Replica.Empty(ReplicaId.New("q")), p0); // Assert that p0 and q0 are converged Converged(p0, q0); var p1 = p0.ApplyCmd(new Doc().DownField("key").Assign("B")); var q1 = q0.ApplyCmd(new Doc().DownField("key").Assign("C")); // Assert that p1 and q1 are diverged Diverged(p1, q1); var p2 = Merge(p1, q1); var q2 = Merge(q1, p1); // Assert that p2 and q2 are converged again. Converged(p2, q2); }
private static Replica Merge(Replica a, Replica b) => a.ApplyRemoteOps(b.GeneratedOps);
private static void Converged(Replica a, Replica b, Replica c) { Converged(a, b); Converged(b, c); }
public Node ApplyOp(Operation op, Replica replica) { var view = op.Cursor.View(); switch (view) { case Cursor.Leaf l: { switch (this) { case ListNode ln: { IEnumerable <Operation> ConcurrentOpsSince(bigint count) { var allOps = replica.GeneratedOps.Append(replica.ReceivedOps); throw new NotImplementedException(); } var concurrentOps = ConcurrentOpsSince(op.Id.OpsCounter); if (concurrentOps.Length() > 1 && concurrentOps.Select(x => x.Mutation).OfType <MoveVerticalM>().Length() >= 1) { // Before applying an operation we save the order in orderArchive. // It's a Map whose key is the lamport timestamp counter value. // To improve performance and save disk space, we don't save the // order before assign operations, since they don't change the order. // Now there might be this situation: Alice did an assign and then a // move op, while Bob did a move op. Now Bobs op comes in and // Alice resets her order to the order with counter value like the // incoming op. However, locally exists no such saved order, since // she has done an assign op at that count. Therefore she resets // to the next higher saved order. // This fix is implemented by getting all orders whose counter is // greater equals than the counter of the incoming op and then // choosing the earliest order of those: var newerOrders = ln.OrderArchive.Filter((k, v) => k >= op.Id.OpsCounter); // restore the order // TODO: // val ctx1 = // if (newerOrders.nonEmpty) ln.copy(order = newerOrders.minBy { // case (c, _) => c // }._2) // else this throw new NotImplementedException(); // Node ctx1 = this; // return ctx1.ApplyMany(concurrentOps.OrderBy(x => x.Id)); } else { // The op was done without me doing an op concurrently, so there is // no need to restore anything. Just apply the op. return(ApplyAtLeaf(op, replica)); } } default: return(ApplyAtLeaf(op, replica)); } throw new InvalidOperationException("Invalid Cursor type"); } case Cursor.Branch bn: { var child0 = GetChild(bn.Head); var child1 = child0.ApplyOp(op.Copy(cursor: bn.Tail), replica); var ctx1 = AddId(bn.Head, op.Id, op.Mutation); return(ctx1.AddNode(bn.Head, child1)); } } return(this); }