├── .gitignore ├── README.md ├── rebar.config ├── src ├── itc.app.src └── itc.erl └── test └── itc_SUITE.erl /.gitignore: -------------------------------------------------------------------------------- 1 | /ebin 2 | /deps 3 | /logs 4 | /log 5 | .DS_Store 6 | *.swp 7 | *.swo 8 | *.beam 9 | erl_crash.dump 10 | .eunit 11 | /doc/*.html 12 | /doc/edoc-info 13 | /doc/erlang.png 14 | /doc/stylesheet.css 15 | _build/ 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Interval Tree Clocks 2 | 3 | Classic causality tracking mechanisms, such as version vectors and vector clocks, have been designed under the assumption of a fixed, well known, set of participants. These mechanisms are less than ideal when applied to dynamic scenarios, subject to variable numbers of participants and churn. E.g. in the Amazon Dynamo system old entries on version vectors are pruned to conserve space, and errors can be introduced. 4 | 5 | Interval Tree Clocks (ITC) is a new clock mechanism that can be used in scenarios with a dynamic number of participants, allowing a completely decentralized creation of processes/replicas without need for global identifiers. The mechanism has a variable size representation that adapts automatically to the number of existing entities, growing or shrinking appropriately. 6 | 7 | This repository gives a usable implementation in Erlang forked from the [original tri-lingual reference implementation](https://github.com/ricardobcl/Interval-Tree-Clocks). Further information and full details can be found in the [Conference Paper, published in Opodis 2009](http://gsd.di.uminho.pt/members/cbm/ps/itc2008.pdf). 8 | 9 | ## Simple demo run 10 | 11 | ### Demo run 12 | 13 | This image shows a run from ITC which is divided in sections. Each section represents the state of the system between operations (_fork_, _event_ or _join_) and is labeled with a letter. This letter maps the state of the operations presented in both demo programs. 14 | 15 | ![Image of demo run](https://web.archive.org/web/20160113065320if_/http://lh3.ggpht.com/_tR0W8QwQsQY/S4ULQBCxDKI/AAAAAAAAAfQ/XW4C9AwOmJc/s800/execFlow.png) 16 | 17 | ### Demo code 18 | 19 | ```erlang 20 | A0 = itc:seed(), % a 21 | 22 | {A1, B0} = itc:fork(A0), % b, A1 = top, B0 = bottom 23 | 24 | A2 = itc:event(A1), % c (top) 25 | B1 = itc:event(B0), % c (bottom) 26 | 27 | {A3, C0} = itc:fork(A2), % d (top), A3 = top fork, C0 = bottom fork 28 | B2 = itc:event(B1), % d (bottom) 29 | 30 | A4 = itc:event(A3), % e (top) 31 | BC0 = itc:join(B2, C0), % e (bottom) 32 | 33 | {BC1, D0} = itc:fork(BC0), % f, BC1 = top fork, D0 = bottom fork 34 | 35 | ABC0 = itc:join(A4, BC1), % g 36 | 37 | ABC1 = itc:event(ABC0). % h 38 | ``` 39 | 40 | ## Summary High level presentation of ITCs and its use. 41 | 42 | ### Introduction 43 | 44 | Interval Tree Clocks can be used as a substitute for both [Version Vectors](http://en.wikipedia.org/wiki/Version_vector) and [Vector Clocks](http://en.wikipedia.org/wiki/Vector_clock). 45 | 46 | Version Vectors are used to track data dependency among replicas. They are used in replicated file systems (such as [Coda](http://en.wikipedia.org/wiki/Coda_(file_system)) and in Cloud engines (such as [Amazon Dynamo](http://en.wikipedia.org/wiki/Dynamo_(storage_system)) and [Cassandra](https://en.wikipedia.org/wiki/Apache_Cassandra)). 47 | 48 | Vector Clocks track causality dependency between events in distributed processes. They are used in are used in group communication protocols (such as in the [Spread toolkit](https://en.wikipedia.org/wiki/Spread_Toolkit)), in [consistent snapshots algorithms](https://en.wikipedia.org/wiki/Snapshot_algorithm), etc. 49 | 50 | ITCs can be used in all of these settings and will excel in dynamic settings, i.e. whenever the number and set of active entities varies during the system execution, since it allows localized introduction and removal of entities. Before ITCs, the typical strategy to address these dynamic settings was to implement classical vectors as mappings from a globally unique id to an integer counter. The drawback is that unique id's are not space efficient and if the active entities change over time (under churn) the state dedicated to the mapping will keep growing. This has lead to ad-hoc pruning solutions (e.g. in Dynamo) that can introduce errors and compromise causality tracking. 51 | 52 | ITCs encode the state needed to track causality in a stamp, composed of an event and id component, and introduces 3 basic operations: 53 | 54 | - *Fork* is used to introduce new stamps. It allows for the cloning of the causal past of a stamp, resulting in a pair of stamps that have identical copies of the event component and distinct id's. E.g. it can be used to introduce new replicas to a system. 55 | 56 | - *Join* is used to merge two stamps. It produces a new stamp that incorporates both causal pasts. E.g. it can be used to retire replicas or receive causal information from messages. 57 | 58 | - *Event* is used to add causal information to a stamp, "incrementing" the event component and keeping the id. 59 | 60 | (*Peek* is a special case of *Fork* that only copies the event component and creates a new stamp with a null id. It can be used to make messages that transport causal information.) 61 | 62 | ### Simulating Version Vectors 63 | 64 | First replicas need to be created. A seed stamp (with a special id component) is first created and the desired number of replicas can be created by forking this initial seed. Bellow we create 4 replicas (`A`, `B`, `C`, and `D`): 65 | 66 | ```erlang 67 | {A0,Tmp0} = itc:fork(itc:seed()), 68 | {B0,Tmp1} = itc:fork(Tmp0), 69 | {C0,D0} = itc:fork(Tmp1), 70 | ``` 71 | 72 | Since no events have been registered, these stamps all compare as equal. Since a stamp function `leq/2` (less or equal) is provided, stamps `X` and `Y` are equivalent when both `itc:leq(X,Y)` and `itc:leq(Y,X)` are true. 73 | 74 | Now, suppose that stamp `B` is associated to a ReplicaB and this replica was modified. We note this by doing: 75 | 76 | ```erlang 77 | B1 = itc:event(B0), 78 | ``` 79 | 80 | Now stamp `B` is greater than all the others. We can do the same in stamp `D` to denote an update on ReplicaD: 81 | 82 | ```erlang 83 | D1 = itc:event(D0), 84 | ``` 85 | 86 | These two stamps are now concurrent. Thus `itc:leq(B1,D1)` is false and `itc:leq(D1,B1)` is also false. 87 | 88 | Now suppose that we want to merge the updates in ReplicaB and ReplicaD. One way is to create a replica that reflects both updates: 89 | 90 | ```erlang 91 | E0 = itc:join(B1,D1), 92 | ``` 93 | 94 | This stamp `E` will now have an id that joins the ids in `B` and `D`, and has an event component that holds both issued events. An alternative way, that keeps the number of replicas/stamps and does not form new ids, is to exchange events between both replicas: 95 | 96 | ```erlang 97 | B2 = itc:join(B1, itc:peek(D1)), 98 | D2 = itc:join(D1, itc:peek(B1)), 99 | ``` 100 | 101 | Now, stamps `B` and `D` are no longer concurrent and will compare as equivalent, since they depict the same events. 102 | 103 | ## License 104 | 105 | This work is licensed under the Lesser General Public License (LGPL), version 106 | 3. See the License for details about distribution rights, and the specific 107 | rights regarding derivate works. 108 | 109 | You may obtain a copy of the License at: 110 | 111 | - http://choosealicense.com/licenses/lgpl-v3/ 112 | 113 | - http://www.gnu.org/licenses/lgpl.html 114 | 115 | ## Using 116 | 117 | For Rebar3 usage with hex packages: 118 | 119 | ```erlang 120 | {deps, [ 121 | {itc, "1.0.1", {pkg, interval_tree_clocks}} 122 | ]}. 123 | ``` 124 | 125 | And for usage with the git repository: 126 | 127 | ```erlang 128 | {deps, [ 129 | {itc, {git, "https://github.com/ferd/interval-tree-clocks", {branch, "main"}}} 130 | ]}. 131 | ``` 132 | 133 | For Mix usage with hex packages: 134 | 135 | ```elixir 136 | defp deps do 137 | [ 138 | {:itc, "~> 1.0", hex: :interval_tree_clocks} 139 | ] 140 | end 141 | ``` 142 | 143 | And for use with the git repository: 144 | 145 | ```elixir 146 | defp deps do 147 | [ 148 | {:itc, git: "https://github.com/ferd/interval-tree-clocks", branch: "main"} 149 | ] 150 | end 151 | ``` 152 | 153 | ## Changelog 154 | 155 | ### 1.0.1 156 | 157 | - [add rebar.confg to fix mix source builds](https://github.com/ferd/Interval-Tree-Clocks/pull/2) 158 | - [Minor grammar and consistency fixes in documentation](https://github.com/ferd/Interval-Tree-Clocks/pull/1) 159 | 160 | ### 1.0.0 161 | 162 | - Initial fork off the multilanguage ITC repository, and reworking for Erlang idiomatic usage. 163 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ferd/Interval-Tree-Clocks/fe57cc4cf54653b806adfa685671238550847276/rebar.config -------------------------------------------------------------------------------- /src/itc.app.src: -------------------------------------------------------------------------------- 1 | {application, itc, 2 | [{description, "Interval Tree Clocks"}, 3 | {vsn, "1.0.1"}, 4 | {modules, [itc]}, 5 | {registered, []}, 6 | {applications, [kernel, stdlib]}, 7 | 8 | {licenses, ["LGPL 3.0"]}, 9 | {pkg_name, interval_tree_clocks}, 10 | {links, [{"Github", "https://github.com/ferd/interval-tree-clocks"}, 11 | {"paper", "https://gsd.di.uminho.pt/members/cbm/ps/itc2008.pdf"}]} 12 | ]}. 13 | -------------------------------------------------------------------------------- /src/itc.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Erlang Public License, 2 | %% Version 1.1, (the "License"); you may not use this file except in 3 | %% compliance with the License. You should have received a copy of the 4 | %% Erlang Public License along with this software. If not, it can be 5 | %% retrieved via the world wide web at http://www.erlang.org/. 6 | %% 7 | %% Software distributed under the License is distributed on an "AS IS" 8 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 9 | %% the License for the specific language governing rights and limitations 10 | %% under the License. 11 | %% 12 | -module(itc). 13 | -author("Paulo Sergio Almeida "). 14 | 15 | -export([explode/1, rebuild/2]). 16 | -export([seed/0, event/1, join/2, fork/1, peek/1, leq/2]). 17 | -export([len/1, str/1, encode/1, decode/1]). 18 | 19 | -compile({no_auto_import,[min/2, max/2]}). 20 | -compile({inline, [{min,2}, {max,2}, {drop,2}, {lift,2}, {base,1}, 21 | {height,1}]}). 22 | 23 | -type id() :: non_neg_integer() 24 | | {Left::id(), Right::id()}. 25 | -type event() :: non_neg_integer() 26 | | {Base::non_neg_integer(), Left::event(), Right::event()}. 27 | -opaque clock() :: {id(), event()}. 28 | -export_type([id/0, event/0, clock/0]). 29 | 30 | %%%%%%%%%%%%%% 31 | %%% PUBLIC %%% 32 | %%%%%%%%%%%%%% 33 | 34 | %% @doc Splits the Id from the Event part of the clock. 35 | -spec explode(clock()) -> {id(), event()}. 36 | explode({Id, Event}) -> {Id, Event}. 37 | 38 | %% @doc Takes an id and an event counter and combines them 39 | %% into a clock. 40 | -spec rebuild(id(), 'undefined' | event()) -> clock(). 41 | rebuild(Id, undefined) -> {Id, 0}; 42 | rebuild(Id, Event) -> {Id, Event}. 43 | 44 | %% @doc Initial seed. 45 | -spec seed() -> clock(). 46 | seed() -> {1, 0}. 47 | 48 | %% @doc Merge two forked trees. 49 | -spec join(clock(), clock()) -> clock(). 50 | join({I1, E1}, {I2, E2}) -> {sum(I1,I2), join_ev(E1, E2)}. 51 | 52 | %% @doc Split an Id into two related ones. This is used when you want 53 | %% to add a new replica to the entire set. 54 | -spec fork(clock()) -> {clock(), clock()}. 55 | fork({I, E}) -> 56 | {I1, I2} = split(I), 57 | {{I1, E}, {I2, E}}. 58 | 59 | %% @doc Special case of a fork that creates an anonymous tree with 60 | %% events to transmit causal information without registering new events, 61 | %% meaning it should be used for replication. 62 | %% 63 | %% Because the peek fork returns an anonymous tree and the original tree 64 | %% itself, the function only returns the modified one. 65 | -spec peek(clock()) -> ReadOnly::clock(). 66 | peek({_I, E}) -> 67 | {0, E}. 68 | 69 | %% @doc Adds a new event to the event component, i.e. marking that an item 70 | %% has been written to or updated. 71 | -spec event(clock()) -> clock(). 72 | event({I, E}) -> 73 | {I, 74 | case fill(I, E) of 75 | E -> 76 | {_, E1} = grow(I, E), 77 | E1; 78 | E1 -> 79 | E1 80 | end}. 81 | 82 | %% @doc Compares events from two trees to figure out if 83 | %% the first one is smaller than or equal to the second one. 84 | -spec leq(clock(), clock()) -> boolean(). 85 | leq({_, E1}, {_, E2}) -> leq_ev(E1, E2). 86 | 87 | %%%%%%%%%%%%%%% 88 | %%% PRIVATE %%% 89 | %%%%%%%%%%%%%%% 90 | 91 | %%% Comparison %%% 92 | 93 | leq_ev({N1, L1, R1}, {N2, L2, R2}) -> 94 | N1 =< N2 andalso 95 | leq_ev(lift(N1, L1), lift(N2, L2)) andalso 96 | leq_ev(lift(N1, R1), lift(N2, R2)); 97 | 98 | leq_ev({N1, L1, R1}, N2) -> 99 | N1 =< N2 andalso 100 | leq_ev(lift(N1, L1), N2) andalso 101 | leq_ev(lift(N1, R1), N2); 102 | 103 | leq_ev(N1, {N2, _, _}) -> N1 =< N2; 104 | 105 | leq_ev(N1, N2) -> N1 =< N2. 106 | 107 | %%% Normal form %%% 108 | 109 | norm_id({0, 0}) -> 0; 110 | norm_id({1, 1}) -> 1; 111 | norm_id(X) -> X. 112 | 113 | norm_ev({N, M, M}) when is_integer(M) -> 114 | N + M; 115 | norm_ev({N, L, R}) -> 116 | M = min(base(L), base(R)), 117 | {N + M, drop(M, L), drop(M, R)}. 118 | 119 | %%% fork/join helpers %%% 120 | 121 | sum(0, X) -> X; 122 | sum(X, 0) -> X; 123 | sum({L1,R1}, {L2, R2}) -> norm_id({sum(L1, L2), sum(R1, R2)}). 124 | 125 | split(0) -> {0, 0}; 126 | split(1) -> {{1, 0}, {0, 1}}; 127 | split({0, I}) -> {I1, I2} = split(I), {{0, I1}, {0, I2}}; 128 | split({I, 0}) -> {I1, I2} = split(I), {{I1, 0}, {I2, 0}}; 129 | split({I1, I2}) -> {{I1, 0}, {0, I2}}. 130 | 131 | join_ev(E1={N1, _, _}, E2={N2, _, _}) when N1 > N2 -> join_ev(E2, E1); 132 | join_ev({N1, L1, R1}, {N2, L2, R2}) when N1 =< N2 -> 133 | D = N2 - N1, 134 | norm_ev({N1, join_ev(L1, lift(D, L2)), join_ev(R1, lift(D, R2))}); 135 | join_ev(N1, {N2, L2, R2}) -> join_ev({N1, 0, 0}, {N2, L2, R2}); 136 | join_ev({N1, L1, R1}, N2) -> join_ev({N1, L1, R1}, {N2, 0, 0}); 137 | join_ev(N1, N2) -> max(N1, N2). 138 | 139 | %%% event helpers %%% 140 | 141 | fill(0, E) -> E; 142 | fill(1, E={_, _, _}) -> height(E); 143 | fill(_, N) when is_integer(N) -> N; 144 | fill({1, R}, {N, El, Er}) -> 145 | Er1 = fill(R, Er), 146 | D = max(height(El), base(Er1)), 147 | norm_ev({N, D, Er1}); 148 | fill({L, 1}, {N, El, Er}) -> 149 | El1 = fill(L, El), 150 | D = max(height(Er), base(El1)), 151 | norm_ev({N, El1, D}); 152 | fill({L, R}, {N, El, Er}) -> 153 | norm_ev({N, fill(L, El), fill(R, Er)}). 154 | 155 | grow(1, N) when is_integer(N)-> 156 | {0, N + 1}; 157 | grow({0, I}, {N, L, R}) -> 158 | {H, E1} = grow(I, R), 159 | {H + 1, {N, L, E1}}; 160 | grow({I, 0}, {N, L, R}) -> 161 | {H, E1} = grow(I, L), 162 | {H + 1, {N, E1, R}}; 163 | grow({Il, Ir}, {N, L, R}) -> 164 | {Hl, El} = grow(Il, L), 165 | {Hr, Er} = grow(Ir, R), 166 | if Hl < Hr -> {Hl + 1, {N, El, R}}; 167 | true -> {Hr + 1, {N, L, Er}} 168 | end; 169 | grow(I, N) when is_integer(N)-> 170 | {H, E} = grow(I, {N, 0, 0}), 171 | {H + 1000, E}. 172 | 173 | height({N, L, R}) -> N + max(height(L), height(R)); 174 | height(N) -> N. 175 | 176 | base({N, _, _}) -> N; 177 | base(N) -> N. 178 | 179 | lift(M, {N, L, R}) -> {N + M, L ,R}; 180 | lift(M, N) -> N + M. 181 | 182 | drop(M, {N, L, R}) when M =< N -> {N - M, L ,R}; 183 | drop(M, N) when M =< N -> N - M. 184 | 185 | max(X, Y) when X =< Y -> Y; 186 | max(X, _) -> X. 187 | 188 | min(X, Y) when X =< Y -> X; 189 | min(_, Y) -> Y. 190 | 191 | %%%%%%%%%%%%%%%%%%%%%%% 192 | %%% Serializing API %%% 193 | %%%%%%%%%%%%%%%%%%%%%%% 194 | 195 | encode({I, E}) -> << (enci(I))/bits, (ence(E))/bits >>. 196 | 197 | decode(B) -> {I, BE} = deci(B), {E, <<>>} = dece(BE), {I, E}. 198 | 199 | enci(0) -> <<0:2, 0:1>>; 200 | enci(1) -> <<0:2, 1:1>>; 201 | enci({0, I}) -> <<1:2, (enci(I))/bits>>; 202 | enci({I, 0}) -> <<2:2, (enci(I))/bits>>; 203 | enci({L, R}) -> <<3:2, (enci(L))/bits, (enci(R))/bits>>. 204 | 205 | deci(<<0:2, 0:1, B/bits>>) -> {0, B}; 206 | deci(<<0:2, 1:1, B/bits>>) -> {1, B}; 207 | deci(<<1:2, B/bits>>) -> {I, B1} = deci(B), {{0, I}, B1}; 208 | deci(<<2:2, B/bits>>) -> {I, B1} = deci(B), {{I, 0}, B1}; 209 | deci(<<3:2, B/bits>>) -> {L, B1} = deci(B), {R, B2} = deci(B1), {{L, R}, B2}. 210 | 211 | %ence({0, 0, R}) -> <<0:1, 0:2, (ence(R))/bits>>; 212 | %ence({0, L, 0}) -> <<0:1, 1:2, (ence(L))/bits>>; 213 | %ence({0, L, R}) -> <<0:1, 2:2, (ence(L))/bits, (ence(R))/bits>>; 214 | %ence({N, L, R}) -> <<0:1, 3:2, (ence(N))/bits, (ence(L))/bits, (ence(R))/bits>>; 215 | %ence(N) -> encn(N, 2, <<1:1>>). 216 | 217 | ence({0, 0, R}) -> 218 | <<0:1, 0:2, (ence(R))/bits>>; 219 | ence({0, L, 0}) -> 220 | <<0:1, 1:2, (ence(L))/bits>>; 221 | ence({0, L, R}) -> 222 | <<0:1, 2:2, (ence(L))/bits, (ence(R))/bits>>; 223 | ence({N, 0, R}) -> 224 | <<0:1, 3:2, 0:1, 0:1, (ence(N))/bits, (ence(R))/bits>>; 225 | ence({N, L, 0}) -> 226 | <<0:1, 3:2, 0:1, 1:1, (ence(N))/bits, (ence(L))/bits>>; 227 | ence({N, L, R}) -> 228 | <<0:1, 3:2, 1:1, (ence(N))/bits, (ence(L))/bits, (ence(R))/bits>>; 229 | ence(N) -> 230 | encn(N, 2). 231 | 232 | dece(<<0:1, 0:2, B/bits>>) -> 233 | {R, B1} = dece(B), 234 | {{0,0,R}, B1}; 235 | dece(<<0:1, 1:2, B/bits>>) -> 236 | {L, B1} = dece(B), 237 | {{0,L,0}, B1}; 238 | dece(<<0:1, 2:2, B/bits>>) -> 239 | {L, B1} = dece(B), 240 | {R, B2} = dece(B1), 241 | {{0,L,R}, B2}; 242 | dece(<<0:1, 3:2, 0:1, 0:1, B/bits>>) -> 243 | {N, B1} = dece(B), 244 | {R, B2} = dece(B1), 245 | {{N, 0, R}, B2}; 246 | dece(<<0:1, 3:2, 0:1, 1:1, B/bits>>) -> 247 | {N, B1} = dece(B), 248 | {L, B2} = dece(B1), 249 | {{N, L, 0}, B2}; 250 | dece(<<0:1, 3:2, 1:1, B/bits>>) -> 251 | {N, B1} = dece(B), 252 | {L, B2} = dece(B1), 253 | {R, B3} = dece(B2), 254 | {{N, L, R}, B3}; 255 | dece(<<1:1, B/bits>>) -> 256 | decn(B, 2, 0). 257 | 258 | encn(N, B) when N < (1 bsl B) -> 259 | <<((1 bsl B) - 2):B, N:B>>; 260 | encn(N, B) -> 261 | encn(N - (1 bsl B), B + 1). 262 | 263 | decn(<<0:1, R/bits>>, B, Acc) -> 264 | <> = R, {N+Acc, R1}; 265 | decn(<<1:1, R/bits>>, B, Acc) -> 266 | decn(R, B+1, Acc + (1 bsl B)). 267 | 268 | len(D) -> byte_size(encode(D)). 269 | 270 | str({I, E}) -> [lists:flatten(stri(I)), lists:flatten(stre(E))]. 271 | 272 | stri(0) -> "0"; 273 | stri(1) -> ""; 274 | stri({0, I}) -> "R"++stri(I); 275 | stri({I, 0}) -> "L"++stri(I); 276 | stri({L, R}) -> ["(L"++stri(L), "+", "R"++stri(R), ")"]. 277 | 278 | stre({N, L, 0}) -> [stre(N), "L", stre(L)]; 279 | stre({N, 0, R}) -> [stre(N), "R", stre(R)]; 280 | stre({N, L, R}) -> [stre(N), "(L", stre(L), "+R", stre(R), ")"]; 281 | stre(N) when N > 0 -> integer_to_list(N); 282 | stre(_) -> "". 283 | -------------------------------------------------------------------------------- /test/itc_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(itc_SUITE). 2 | -include_lib("common_test/include/ct.hrl"). 3 | -include_lib("eunit/include/eunit.hrl"). 4 | -compile([nowarn_export_all, export_all]). 5 | 6 | all() -> [{group, basic}, {group, replication}]. 7 | 8 | groups() -> 9 | [{basic, [parallel], [demo, version_vector, comparison]}, 10 | {replication, [parallel], [master_to_replicas, master_to_master]}]. 11 | 12 | %% Testing the demo code from README.md to make 13 | %% sure it fits the paper description. 14 | demo(_Config) -> 15 | A0 = itc:seed(), % a 16 | {A1, B0} = itc:fork(A0), % b, A1 = top, B0 = bottom 17 | A2 = itc:event(A1), % c (top) 18 | B1 = itc:event(B0), % c (bottom) 19 | {A3, C0} = itc:fork(A2), % d (top), A3 = top fork, C0 = bottom fork 20 | B2 = itc:event(B1), % d (bottom) 21 | A4 = itc:event(A3), % e (top) 22 | BC0 = itc:join(B2, C0), % e (bottom) 23 | {BC1, D0} = itc:fork(BC0), % f, BC1 = top fork, D0 = bottom fork 24 | ABC0 = itc:join(A4, BC1), % g 25 | ABC1 = itc:event(ABC0), % h 26 | %% Now we assert stuff. 27 | % base: 1, added: 0 28 | ?assertEqual(A0, {1,0}), 29 | % 1/2 left, added: 0 30 | ?assertEqual(A1, {{1,0}, 0}), 31 | % 1/2 left, added: base 0, left 1, right 0 32 | ?assertEqual(A2, {{1,0}, {0,1,0}}), 33 | % 1/4 left, added: base 0, left 1, right 0 34 | ?assertEqual(A3, {{{1,0},0},{0,1,0}}), 35 | % 1/4 left, added: base 0, left 1+1/2(left), right 0, left 1 36 | ?assertEqual(A4, {{{1,0},0},{0,{1,1,0},0}}), 37 | % 1/2 right, added: 0 38 | ?assertEqual(B0, {{0,1}, 0}), 39 | % 1/2 right, added: base 0, left 0, right 1 40 | ?assertEqual(B1, {{0,1}, {0,0,1}}), 41 | % 1/2 right, added: base 0, left 0, right 2 42 | ?assertEqual(B2, {{0,1}, {0,0,2}}), 43 | % 1/4 left, added: base 0, left 1, right 0 44 | ?assertEqual(C0, {{{0,1},0},{0,1,0}}), 45 | % 3/4 right, added: base 1, left 0, right +1 (2) 46 | ?assertEqual(BC0, {{{0,1},1},{1,0,1}}), 47 | % fork, keep left BC0, added: base 1, left 0, right 1 48 | ?assertEqual(BC1, {{{0,1},0},{1,0,1}}), 49 | % fork, keep 1/2 right, added: base 1, left 0, right 1 50 | ?assertEqual(D0, {{0,1},{1,0,1}}), 51 | % merge left for 1/2, base 1, left 1/2l, right 1 52 | ?assertEqual(ABC0, {{1,0},{1,{0,1,0},1}}), 53 | % fill gap, 1/2 left, added: base 2 54 | ?assertEqual(ABC1, {{1,0},2}). 55 | 56 | %% Test that the text from README about replicating Version Vectors 57 | %% with ITCs holds true to its description. 58 | version_vector(_Config) -> 59 | {A0,Tmp0} = itc:fork(itc:seed()), 60 | {B0,Tmp1} = itc:fork(Tmp0), 61 | {C0,D0} = itc:fork(Tmp1), 62 | %% Check comparisons. 63 | ?assert(equal(A0,B0)), 64 | ?assert(equal(A0,C0)), 65 | ?assert(equal(A0,D0)), 66 | ?assert(equal(B0,C0)), 67 | ?assert(equal(B0,D0)), 68 | ?assert(equal(C0,D0)), 69 | %% events to B & D. 70 | B1 = itc:event(B0), 71 | D1 = itc:event(D0), 72 | %% They clash. 73 | ?assertNot(itc:leq(B1,D1)), 74 | ?assertNot(itc:leq(D1,B1)), 75 | %% merged replica. 76 | E0 = itc:join(B1,D1), 77 | %% merge both states. 78 | B2 = itc:join(B1, itc:peek(D1)), 79 | D2 = itc:join(D1, itc:peek(B1)), 80 | %% They all compare equivalently. 81 | ?assert(equal(B2,D2)), 82 | ?assert(equal(B2,E0)), 83 | ?assert(equal(D2,E0)). 84 | 85 | %% The comparison is based on the sequences of events and using 86 | %% leq/2 as an operator. Forks themselves should have no impact. 87 | comparison(_Config) -> 88 | %% X = X. 89 | A0 = itc:seed(), 90 | ?assert(itc:leq(A0,A0)), 91 | %% Forks that are temporally different but have the same 92 | %% changes are all equal. 93 | {A1, B0} = itc:fork(A0), 94 | ?assert(itc:leq(A0,A1)), 95 | ?assert(itc:leq(A1,A0)), 96 | ?assert(itc:leq(B0,A0)), 97 | ?assert(itc:leq(B0,A1)), 98 | ?assert(itc:leq(A0, itc:join(B0,A1))), 99 | ?assert(itc:leq(itc:join(B0,A1), A0)), 100 | %% One event leads to a bigger lead everywhere, even if the leading 101 | %% term is an ancestor (uh-oh!). 102 | ?assert(itc:leq(A1, itc:event(A0))), 103 | ?assertNot(itc:leq(itc:event(A0), A1)), 104 | %% if events are added to both forks, the resulting branches conflict 105 | %% and are neither smaller, equal, nor greater than each other, 106 | %% representing concurrent results. They however remain joinable. 107 | ?assertNot(itc:leq(itc:event(B0), itc:event(A1))), 108 | ?assertNot(itc:leq(itc:event(A1), itc:event(B0))), 109 | itc:join(itc:event(A1), itc:event(B0)), 110 | %% An event added to a parent of a child that also had an event added 111 | %% could make the parent larger, or smaller. In these cases, joinability 112 | %% fails. 113 | ?assertNot(itc:leq(itc:event(A0), itc:event(A1))), 114 | ?assert(itc:leq(itc:event(A1), itc:event(A0))), 115 | ?assertError(_, itc:join(itc:event(A1), itc:event(A0))), 116 | {A2,C0} = itc:fork(A1), 117 | ?assertNot(itc:leq(itc:event(itc:event(A2)), itc:event(A1))), 118 | ?assertNot(itc:leq(itc:event(A1), itc:event(itc:event(A2)))), 119 | ?assertError(_, itc:join(itc:event(A1), itc:event(itc:event(A2)))), 120 | ?assertNot(itc:leq(itc:event(itc:event(A2)), itc:event(A0))), 121 | ?assertNot(itc:leq(itc:event(A0), itc:event(itc:event(A2)))), 122 | ?assertError(_, itc:join(itc:event(A0), itc:event(itc:event(A2)))), 123 | ?assertNot(itc:leq(itc:event(itc:event(C0)), itc:event(A1))), 124 | ?assertNot(itc:leq(itc:event(A1), itc:event(itc:event(C0)))), 125 | ?assertError(_, itc:join(itc:event(A1), itc:event(itc:event(C0)))). 126 | 127 | master_to_replicas(_Config) -> 128 | %% 3 cluster: 1 master, two replicas (A,B). 129 | {Master0, ReplicaBase} = itc:fork(itc:seed()), 130 | {ReplicaA0, ReplicaB0} = itc:fork(ReplicaBase), 131 | %% Simulate 3 unreplicated writes. 132 | Master1 = itc:event(Master0), 133 | Master2 = itc:event(Master1), 134 | Master3 = itc:event(Master2), 135 | %% Replication (peek) and merging (join) should work with the base 136 | %% state. 137 | ?assert(equal(itc:peek(Master0), itc:peek(ReplicaA0))), 138 | ?assert(equal(itc:peek(Master0), itc:join(ReplicaA0,Master0))), 139 | ?assert(equal(itc:peek(Master0), 140 | itc:join(ReplicaA0,itc:peek(Master0)))), 141 | %% Replicate them in order from peek and it should work. 142 | ReplicaA1 = itc:join(ReplicaA0, itc:peek(Master1)), 143 | ReplicaA2 = itc:join(ReplicaA1, itc:peek(Master2)), 144 | ReplicaA3 = itc:join(ReplicaA2, itc:peek(Master3)), 145 | ?assert(equal(itc:peek(Master1), itc:peek(ReplicaA1))), 146 | ?assert(equal(itc:peek(Master2), itc:peek(ReplicaA2))), 147 | ?assert(equal(itc:peek(Master3), itc:peek(ReplicaA3))), 148 | itc:join(Master3,ReplicaA3), 149 | itc:join(Master3,ReplicaA2), 150 | itc:join(Master3,ReplicaA1), 151 | ?assert(smaller(Master2,ReplicaA3)), 152 | ?assert(smaller(Master1,ReplicaA3)), 153 | ?assert(smaller(Master0,ReplicaA3)), 154 | %% Out of order replications for other replica, 155 | %% from possibly many sources. 156 | ?assert(smaller(ReplicaB0,ReplicaA3)), 157 | ?assert(larger(ReplicaA3,ReplicaB0)), 158 | ReplicaB1 = itc:join(ReplicaB0, itc:peek(ReplicaA3)), 159 | ?assert(equal(ReplicaB1, ReplicaA3)), 160 | ?assert(equal(ReplicaB1, Master3)), 161 | ?assertNot(smaller(ReplicaB1, Master3)), 162 | ?assertNot(larger(ReplicaB1, Master3)), 163 | ?assertNot(smaller(Master3, ReplicaB1)), 164 | ?assertNot(larger(Master3, ReplicaB1)), 165 | ok. 166 | 167 | master_to_master(_Config) -> 168 | %% 3 cluster: 1 master, two replicas (A,B). 169 | {MasterTmp, MasterB0} = itc:fork(itc:seed()), 170 | {MasterB1, MasterC0} = itc:fork(MasterB0), 171 | {MasterA0, MasterD0} = itc:fork(MasterTmp), 172 | %% Concurrent entries can't work, get invalid. 173 | MasterA1 = add_events(3, MasterA0), 174 | MasterB2 = add_events(2, MasterB1), 175 | MasterC1 = add_events(1, MasterC0), 176 | ?assert(clash(MasterA1, MasterB2)), 177 | ?assert(clash(MasterB2, MasterC1)), 178 | %% Merges from peek / join work, and supercede the previous values. 179 | ?assert(smaller(MasterC1, 180 | itc:join(MasterC1, itc:peek(MasterB2)))), 181 | ?assert(smaller(MasterC1, itc:join(MasterC1, MasterB2))), 182 | ?assert(smaller(MasterC1, 183 | itc:join(MasterC1, itc:peek(MasterA1)))), 184 | ?assert(smaller(MasterC1, itc:join(MasterC1, MasterA1))), 185 | ?assert(smaller(MasterB2, 186 | itc:join(MasterC1, itc:peek(MasterB2)))), 187 | ?assert(smaller(MasterB2, itc:join(MasterC1, MasterB2))), 188 | ?assert(smaller(MasterA1, 189 | itc:join(MasterC1, itc:peek(MasterA1)))), 190 | ?assert(smaller(MasterA1, itc:join(MasterC1, MasterA1))), 191 | %% Both merged entries are equal to each other, and still clash 192 | %% with conflicting ones, but are bigger than sane ones. 193 | MasterB3 = itc:join(MasterB2, itc:peek(MasterC1)), 194 | MasterC2 = itc:join(MasterC1, itc:peek(MasterB2)), 195 | ?assert(equal(MasterB3, MasterC2)), 196 | ?assert(clash(MasterA1, MasterB3)), 197 | ?assert(clash(MasterA1, MasterC2)), 198 | ?assert(smaller(MasterD0, MasterA1)), 199 | ?assert(smaller(MasterD0, MasterB3)), 200 | ?assert(smaller(MasterD0, MasterC2)). 201 | 202 | equal(ClockA,ClockB) -> 203 | itc:leq(ClockA,ClockB) andalso itc:leq(ClockB,ClockA). 204 | 205 | smaller(ClockA,ClockB) -> 206 | itc:leq(ClockA,ClockB) andalso not itc:leq(ClockB,ClockA). 207 | 208 | larger(ClockA,ClockB) -> 209 | not itc:leq(ClockA,ClockB) andalso itc:leq(ClockB,ClockA). 210 | 211 | clash(ClockA, ClockB) -> 212 | not equal(ClockA, ClockB) andalso 213 | not smaller(ClockA, ClockB) andalso 214 | not larger(ClockA, ClockB). 215 | 216 | add_events(0, Clock) -> Clock; 217 | add_events(N, Clock) -> add_events(N-1, itc:event(Clock)). 218 | --------------------------------------------------------------------------------