├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── doc ├── erlang.png ├── index.html ├── modules-frame.html ├── overview-summary.html ├── packages-frame.html ├── stylesheet.css ├── swc_kv.html ├── swc_node.html └── swc_vv.html ├── include └── swc.hrl ├── makefile ├── rebar.config ├── rebar.lock ├── rebar3 └── src ├── swc.app.src ├── swc_dotkeymap.erl ├── swc_kv.erl ├── swc_node.erl ├── swc_vv.erl └── swc_watermark.erl /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | *.swp 3 | *.dump 4 | edoc-info 5 | .DS_Store 6 | deps/ 7 | .eunit 8 | .rebar 9 | test/temp 10 | log 11 | .eunit 12 | .rebar 13 | test/sst* 14 | test/LOG* 15 | test/MANIFEST* 16 | test/*.log 17 | test/LOCK 18 | test/CURRENT 19 | _build/* 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 17.4 4 | - 18.0 5 | script: "make all" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, ricardobcl . 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | * The names of its contributors may not be used to endorse or promote 16 | products derived from this software without specific prior written 17 | permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Server Wide Clocks (swc) [![Build Status](https://travis-ci.org/ricardobcl/ServerWideClocks.svg)](https://travis-ci.org/ricardobcl/ServerWideClocks) 2 | ===== 3 | 4 | This is an Erlang OTP library for Server Wide Clocks. 5 | 6 | Your can find the paper here: http://haslab.uminho.pt/tome/files/global_logical_clocks.pdf 7 | 8 | There are 2 main files: 9 | 10 | - the **node clock** -> implemented in swc_node.erl 11 | - the **key-value clock** -> implemented in swc_kv.erl 12 | 13 | Build 14 | ----- 15 | 16 | ```shell 17 | > make all 18 | ``` 19 | -------------------------------------------------------------------------------- /doc/erlang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardobcl/ServerWideClocks/84642ab0e4db73ceb79240e8abe3bd92058b375a/doc/erlang.png -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The swc application 5 | 6 | 7 | 8 | 9 | 10 | 11 | <h2>This page uses frames</h2> 12 | <p>Your browser does not accept frames. 13 | <br>You should go to the <a href="overview-summary.html">non-frame version</a> instead. 14 | </p> 15 | 16 | 17 | -------------------------------------------------------------------------------- /doc/modules-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The swc application 5 | 6 | 7 | 8 |

Modules

9 | 10 | 11 | 12 |
swc_kv
swc_node
swc_vv
13 | 14 | -------------------------------------------------------------------------------- /doc/overview-summary.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | The swc application 6 | 7 | 8 | 9 | 10 |

The swc application

11 | 12 |
13 | 14 |

Generated by EDoc, Oct 29 2015, 16:20:19.

15 | 16 | 17 | -------------------------------------------------------------------------------- /doc/packages-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The swc application 5 | 6 | 7 | 8 |

Packages

9 |
10 | 11 | -------------------------------------------------------------------------------- /doc/stylesheet.css: -------------------------------------------------------------------------------- 1 | /* standard EDoc style sheet */ 2 | body { 3 | font-family: Verdana, Arial, Helvetica, sans-serif; 4 | margin-left: .25in; 5 | margin-right: .2in; 6 | margin-top: 0.2in; 7 | margin-bottom: 0.2in; 8 | color: #000000; 9 | background-color: #ffffff; 10 | } 11 | h1,h2 { 12 | margin-left: -0.2in; 13 | } 14 | div.navbar { 15 | background-color: #add8e6; 16 | padding: 0.2em; 17 | } 18 | h2.indextitle { 19 | padding: 0.4em; 20 | background-color: #add8e6; 21 | } 22 | h3.function,h3.typedecl { 23 | background-color: #add8e6; 24 | padding-left: 1em; 25 | } 26 | div.spec { 27 | margin-left: 2em; 28 | background-color: #eeeeee; 29 | } 30 | a.module,a.package { 31 | text-decoration:none 32 | } 33 | a.module:hover,a.package:hover { 34 | background-color: #eeeeee; 35 | } 36 | ul.definitions { 37 | list-style-type: none; 38 | } 39 | ul.index { 40 | list-style-type: none; 41 | background-color: #eeeeee; 42 | } 43 | 44 | /* 45 | * Minor style tweaks 46 | */ 47 | ul { 48 | list-style-type: square; 49 | } 50 | table { 51 | border-collapse: collapse; 52 | } 53 | td { 54 | padding: 3 55 | } 56 | -------------------------------------------------------------------------------- /doc/swc_kv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module swc_kv 6 | 7 | 8 | 9 | 10 |
11 | 12 |

Module swc_kv

13 | 14 | An Erlang implementation of Key-Value Logical Clock, 15 | in this case a Dotted Causal Container. 16 | 17 |

Authors: Ricardo Gonçalves (tome.wave@gmail.com).

18 | 19 |

Description

20 | An Erlang implementation of Key-Value Logical Clock, 21 | in this case a Dotted Causal Container. 22 |

Function Index

23 | 27 | 31 | 33 | 35 | 37 | 39 | 41 | 44 | 50 | 51 |
add/2Adds the dots corresponding to each version in the DCC to the BVV; this 24 | is accomplished by using the standard fold higher-order function, passing 25 | the function swc_node:add/2 defined over BVV and dots, the BVV, and the list of 26 | dots in the DCC.
add/3This function is to be used at node I after dcc:discard/2, and adds a 28 | mapping, from the Dot (I, N) (which should be obtained by previously applying 29 | swc_node:event/2 to the BVV at node I) to the Value, to the DCC, and also advances 30 | the i component of the VV in the DCC to N.
context/1Returns the causal context of a DCC, which is representable as a 32 | Version Vector.
discard/2It discards versions in DCC {D,V} which are made obsolete by a causal 34 | context (a version vector) C, and also merges C into DCC causal context V.
fill/2Function fill adds back causality information to a stripped DCC, before 36 | any operation is performed.
fill/3Same as fill/2 but only adds entries that are elements of a list of Ids, 38 | instead of adding all entries in the BVV.
new/0Constructs a new clock set without causal history, 40 | and receives one value that goes to the anonymous list.
strip/2It discards all entries from the version vector V in the DCC that are 42 | covered by the corresponding base component of the BVV B; only entries with 43 | greater sequence numbers are kept.
sync/2Performs the synchronization of two DCCs; it discards versions ( 45 | {dot,value} pairs) made obsolete by the other DCC, by preserving the 46 | versions that are present in both, together with the versions in either of 47 | them that are not covered by the relevant entry in the other's causal 48 | context; the causal context is obtained by a standard version vector merge 49 | function (performing the pointwise maximum).
values/1Returns the set of values held in the DCC.
52 | 53 |

Function Details

54 | 55 |

add/2

56 |
57 |

add(BVV::bvv(), X2::dcc()) -> bvv()

58 |

Adds the dots corresponding to each version in the DCC to the BVV; this 59 | is accomplished by using the standard fold higher-order function, passing 60 | the function swc_node:add/2 defined over BVV and dots, the BVV, and the list of 61 | dots in the DCC.

62 | 63 |

add/3

64 |
65 |

add(X1::dcc(), Dot::{id(), counter()}, Value::value()) -> dcc()

66 |

This function is to be used at node I after dcc:discard/2, and adds a 67 | mapping, from the Dot (I, N) (which should be obtained by previously applying 68 | swc_node:event/2 to the BVV at node I) to the Value, to the DCC, and also advances 69 | the i component of the VV in the DCC to N.

70 | 71 |

context/1

72 |
73 |

context(X1::dcc()) -> vv()

74 |

Returns the causal context of a DCC, which is representable as a 75 | Version Vector.

76 | 77 |

discard/2

78 |
79 |

discard(X1::dcc(), C::vv()) -> dcc()

80 |

It discards versions in DCC {D,V} which are made obsolete by a causal 81 | context (a version vector) C, and also merges C into DCC causal context V.

82 | 83 |

fill/2

84 |
85 |

fill(X1::dcc(), BVV::bvv()) -> dcc()

86 |

Function fill adds back causality information to a stripped DCC, before 87 | any operation is performed.

88 | 89 |

fill/3

90 |
91 |

fill(X1::dcc(), BVV::bvv(), Ids::[id()]) -> dcc()

92 |

Same as fill/2 but only adds entries that are elements of a list of Ids, 93 | instead of adding all entries in the BVV.

94 | 95 |

new/0

96 |
97 |

new() -> dcc()

98 |

Constructs a new clock set without causal history, 99 | and receives one value that goes to the anonymous list.

100 | 101 |

strip/2

102 |
103 |

strip(X1::dcc(), B::bvv()) -> dcc()

104 |

It discards all entries from the version vector V in the DCC that are 105 | covered by the corresponding base component of the BVV B; only entries with 106 | greater sequence numbers are kept. The idea is that DCCs are stored after 107 | being stripped of their causality information that is already present in the 108 | node clock BVV.

109 | 110 |

sync/2

111 |
112 |

sync(X1::dcc(), X2::dcc()) -> dcc()

113 |

Performs the synchronization of two DCCs; it discards versions ( 114 | {dot,value} pairs) made obsolete by the other DCC, by preserving the 115 | versions that are present in both, together with the versions in either of 116 | them that are not covered by the relevant entry in the other's causal 117 | context; the causal context is obtained by a standard version vector merge 118 | function (performing the pointwise maximum).

119 | 120 |

values/1

121 |
122 |

values(X1::dcc()) -> [value()]

123 |

Returns the set of values held in the DCC.

124 |
125 | 126 | 127 |

Generated by EDoc, Oct 29 2015, 16:20:19.

128 | 129 | 130 | -------------------------------------------------------------------------------- /doc/swc_node.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module swc_node 6 | 7 | 8 | 9 | 10 |
11 | 12 |

Module swc_node

13 | 14 | An Erlang implementation of a Server Wide Logical Clock, 15 | in this case a Bitmapped Version Vector. 16 | 17 |

Authors: Ricardo Gonçalves (tome.wave@gmail.com).

18 | 19 |

Description

20 | An Erlang implementation of a Server Wide Logical Clock, 21 | in this case a Bitmapped Version Vector. 22 |

Function Index

23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 39 | 40 |
add/2Adds a dot (ID, Counter) to a BVV.
base/1Takes and returns a BVV where in each entry, the bitmap is reset to zero.
event/2Takes a BVV at node Id and returns a pair with sequence number for a new 26 | event (dot) at node Id and the original BVV with the new dot added; this 27 | function makes use of the invariant that the node BVV for node Id knows all 28 | events generated at Id, i.e., the first component of the pair denotes the 29 | last event, and the second component, the bitmap, is always zero.
get/2Returns the entry of a BVV associated with a given ID.
ids/1Returns all IDs from the entries in a BVV.
join/2Joins entries from BVV2 that are also IDs in BVV1, into BVV1.
merge/2Merges all entries from the two BVVs.
new/0Constructs an empty BVV (an orddict in Erlang).
norm/1Normalizes an entry pair, by removing dots and adding them to the base 36 | if they are contiguous with the base.
store_entry/3Stores an Id-Entry pair in a BVV; if the id already exists, the 38 | associated entry is replaced by the new one.
values/1Returns the sequence numbers for the dots represented by an entry.
41 | 42 |

Function Details

43 | 44 |

add/2

45 |
46 |

add(BVV::bvv(), X2::{id(), counter()}) -> bvv()

47 |

Adds a dot (ID, Counter) to a BVV.

48 | 49 |

base/1

50 |
51 |

base(BVV::bvv()) -> bvv()

52 |

Takes and returns a BVV where in each entry, the bitmap is reset to zero.

53 | 54 |

event/2

55 |
56 |

event(BVV::bvv(), Id::id()) -> {counter(), bvv()}

57 |

Takes a BVV at node Id and returns a pair with sequence number for a new 58 | event (dot) at node Id and the original BVV with the new dot added; this 59 | function makes use of the invariant that the node BVV for node Id knows all 60 | events generated at Id, i.e., the first component of the pair denotes the 61 | last event, and the second component, the bitmap, is always zero.

62 | 63 |

get/2

64 |
65 |

get(K::id(), B::bvv()) -> entry()

66 |

Returns the entry of a BVV associated with a given ID.

67 | 68 |

ids/1

69 |
70 |

ids(B::bvv()) -> [id()]

71 |

Returns all IDs from the entries in a BVV.

72 | 73 |

join/2

74 |
75 |

join(BVV1::bvv(), BVV2::bvv()) -> bvv()

76 |

Joins entries from BVV2 that are also IDs in BVV1, into BVV1.

77 | 78 |

merge/2

79 |
80 |

merge(BVV1::bvv(), BVV2::bvv()) -> bvv()

81 |

Merges all entries from the two BVVs.

82 | 83 |

new/0

84 |
85 |

new() -> bvv()

86 |

Constructs an empty BVV (an orddict in Erlang).

87 | 88 |

norm/1

89 |
90 |

norm(X1::entry()) -> entry()

91 |

Normalizes an entry pair, by removing dots and adding them to the base 92 | if they are contiguous with the base.

93 | 94 |

store_entry/3

95 |
96 |

store_entry(Id, Entry, BVV) -> any()

97 |

Stores an Id-Entry pair in a BVV; if the id already exists, the 98 | associated entry is replaced by the new one.

99 | 100 |

values/1

101 |
102 |

values(X1::entry()) -> [counter()]

103 |

Returns the sequence numbers for the dots represented by an entry.

104 |
105 | 106 | 107 |

Generated by EDoc, Oct 29 2015, 16:20:19.

108 | 109 | 110 | -------------------------------------------------------------------------------- /doc/swc_vv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module swc_vv 6 | 7 | 8 | 9 | 10 |
11 | 12 |

Module swc_vv

13 | 14 | An Erlang implementation of a Version Vector. 15 | 16 |

Authors: Ricardo Gonçalves (tome.wave@gmail.com).

17 | 18 |

Description

19 | An Erlang implementation of a Version Vector. 20 |

Function Index

21 | 23 | 24 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 34 |
add/2Adds an entry {Id, Counter} to the VV, performing the maximum between 22 | both counters, if the entry already exists.
delete_key/2Returns the VV without the entry with a given key.
filter/2It applies some boolean function F to all entries in the VV, removing 25 | those that return False when F is used.
get/2Returns the counter associated with an id K.
ids/1Returns all the keys (ids) from a VV.
join/2Merges or joins two VVs, taking the maximum counter if an entry is 29 | present in both VVs.
min/1Returns the minimum counters from all the entries in the VV.
min_key/1Returns the key with the minimum counter associated.
new/0Initializes a new empty version vector.
reset_with_same_ids/1Returns the VV with the same entries, but with counters at zero.
35 | 36 |

Function Details

37 | 38 |

add/2

39 |
40 |

add(VV::vv(), X2::{id(), counter()}) -> vv()

41 |

Adds an entry {Id, Counter} to the VV, performing the maximum between 42 | both counters, if the entry already exists.

43 | 44 |

delete_key/2

45 |
46 |

delete_key(VV::vv(), Key::id()) -> vv()

47 |

Returns the VV without the entry with a given key.

48 | 49 |

filter/2

50 |
51 |

filter(F::fun((id(), counter()) -> boolean()), V::vv()) -> vv()

52 |

It applies some boolean function F to all entries in the VV, removing 53 | those that return False when F is used.

54 | 55 |

get/2

56 |
57 |

get(K::id(), V::vv()) -> {ok, counter()} | error

58 |

Returns the counter associated with an id K. If the key is not present 59 | in the VV, it returns 0.

60 | 61 |

ids/1

62 |
63 |

ids(V::vv()) -> [id()]

64 |

Returns all the keys (ids) from a VV.

65 | 66 |

join/2

67 |
68 |

join(A::vv(), B::vv()) -> vv()

69 |

Merges or joins two VVs, taking the maximum counter if an entry is 70 | present in both VVs.

71 | 72 |

min/1

73 |
74 |

min(VV::vv()) -> counter()

75 |

Returns the minimum counters from all the entries in the VV.

76 | 77 |

min_key/1

78 |
79 |

min_key(VV::vv()) -> id()

80 |

Returns the key with the minimum counter associated.

81 | 82 |

new/0

83 |
84 |

new() -> vv()

85 |

Initializes a new empty version vector.

86 | 87 |

reset_with_same_ids/1

88 |
89 |

reset_with_same_ids(VV::vv()) -> vv()

90 |

Returns the VV with the same entries, but with counters at zero.

91 |
92 | 93 | 94 |

Generated by EDoc, Oct 29 2015, 16:20:19.

95 | 96 | 97 | -------------------------------------------------------------------------------- /include/swc.hrl: -------------------------------------------------------------------------------- 1 | 2 | -type vv() :: [{Key :: id(), Entry :: counter()}]. % orddict(). 3 | 4 | -type dcc() :: {dots(), vv()}. 5 | 6 | -type bvv() :: [{Key :: id(), Entry :: entry()}]. % orddict(). 7 | 8 | -type key_matrix() :: [{Node :: id(), DotKey :: [{counter(), id()}]}]. % orddict(). 9 | 10 | -type vv_matrix() :: {orddict:orddict(), orddict:orddict()}. 11 | 12 | -type dots() :: [{dot(), value()}]. % orddict(dot -> value). 13 | -type dot() :: {id(), counter()}. 14 | -type entry() :: {counter(), counter()}. 15 | -type id() :: term(). 16 | -type counter() :: non_neg_integer(). 17 | -type value() :: any(). 18 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | REBAR3_URL=https://s3.amazonaws.com/rebar3/rebar3 2 | 3 | # If there is a rebar in the current directory, use it 4 | ifeq ($(wildcard rebar3),rebar3) 5 | REBAR3 = $(CURDIR)/rebar3 6 | endif 7 | 8 | # Fallback to rebar on PATH 9 | REBAR3 ?= $(shell test -e `which rebar3` 2>/dev/null && which rebar3 || echo "./rebar3") 10 | 11 | # And finally, prep to download rebar if all else fails 12 | ifeq ($(REBAR3),) 13 | REBAR3 = $(CURDIR)/rebar3 14 | endif 15 | 16 | all: $(REBAR3) 17 | @$(REBAR3) do clean, deps, compile, eunit, ct, dialyzer 18 | 19 | rel: all 20 | @$(REBAR3) release 21 | 22 | clean: 23 | @$(REBAR3) clean 24 | 25 | compile: 26 | @$(REBAR3) compile 27 | 28 | deps: 29 | @$(REBAR3) deps 30 | 31 | doc: compile 32 | @$(REBAR3) edoc 33 | 34 | test: deps compile 35 | @$(REBAR3) do eunit, ct, dialyzer 36 | 37 | 38 | $(REBAR3): 39 | curl -Lo rebar3 $(REBAR3_URL) || wget $(REBAR3_URL) 40 | chmod a+x rebar3 41 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, []}. -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardobcl/ServerWideClocks/84642ab0e4db73ceb79240e8abe3bd92058b375a/rebar3 -------------------------------------------------------------------------------- /src/swc.app.src: -------------------------------------------------------------------------------- 1 | {application, 'swc', 2 | [{description, "An OTP library for Server Wide Clocks"}, 3 | {vsn, "1.0.0"}, 4 | {registered, []}, 5 | {applications, [kernel, stdlib]}, 6 | {env,[]}, 7 | {modules, []}, 8 | {contributors, ["Ricardo Goncalves "]}, 9 | {licenses, []}, 10 | {links, []} 11 | ]}. 12 | -------------------------------------------------------------------------------- /src/swc_dotkeymap.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Ricardo Gonçalves 3 | %%% @copyright (C) 2016, Ricardo Gonçalves 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%%------------------------------------------------------------------- 8 | -module(swc_dotkeymap). 9 | 10 | -author('Ricardo Gonçalves '). 11 | 12 | -compile({no_auto_import,[size/1]}). 13 | 14 | -include_lib("swc/include/swc.hrl"). 15 | 16 | -ifdef(TEST). 17 | -include_lib("eunit/include/eunit.hrl"). 18 | -endif. 19 | 20 | %% API exports 21 | -export([ new/0 22 | , empty/1 23 | , add_key/4 24 | , add_objects/2 25 | , size/1 26 | , size/2 27 | , prune/2 28 | , get_keys/2 29 | , is_key/2 30 | ]). 31 | 32 | -spec new() -> key_matrix(). 33 | new() -> 34 | orddict:new(). 35 | 36 | -spec empty(key_matrix()) -> boolean(). 37 | empty(D) -> 38 | size(D) == 0. 39 | 40 | -spec is_key(key_matrix(), id()) -> boolean(). 41 | is_key(D, Id) -> 42 | orddict:is_key(Id, D). 43 | 44 | -spec add_key(key_matrix(), id(), id(), counter()) -> key_matrix(). 45 | add_key(D, Id, Key, Counter) -> 46 | orddict:update( Id, 47 | fun (OldOrdDict) -> orddict:store(Counter, Key, OldOrdDict) end, 48 | orddict:store(Counter, Key, orddict:new()), 49 | D). 50 | 51 | -spec add_objects(key_matrix(), [{Key::id(), Object::dcc()}]) -> key_matrix(). 52 | add_objects(DKM, []) -> DKM; 53 | add_objects(DKM, [{Key,Obj}|T]) -> 54 | {Dots,_} = Obj, 55 | DKM2 = orddict:fold( 56 | fun (_Dot={Id,Counter},_,Acc) -> 57 | add_key(Acc, Id, Key, Counter) 58 | end, 59 | DKM, Dots), 60 | add_objects(DKM2, T). 61 | 62 | 63 | -spec size(key_matrix()) -> non_neg_integer(). 64 | size(D) -> 65 | orddict:fold(fun (_K,V,Acc) -> Acc + orddict:size(V) end, 0, D). 66 | 67 | -spec size(key_matrix(), id()) -> non_neg_integer(). 68 | size(D, Id) -> 69 | case orddict:find(Id, D) of 70 | error -> 0; 71 | {ok, V} -> orddict:size(V) 72 | end. 73 | 74 | -spec prune(key_matrix(), vv_matrix()) -> {key_matrix(), RemovedKeys :: [{id(), [{counter(),id()}]}]}. 75 | prune(D, M) -> 76 | orddict:fold( 77 | fun (Peer, V, {KeepDic, RemoveDic}) -> 78 | Min = swc_watermark:min(M, Peer), 79 | Keep = orddict:filter(fun (Counter,_) -> Counter > Min end, V), 80 | Remove = orddict:filter(fun (Counter,_) -> Counter =< Min end, V), 81 | case {orddict:is_empty(Keep), orddict:is_empty(Remove)} of 82 | {true ,true} -> {KeepDic, RemoveDic}; 83 | {false,true} -> {orddict:store(Peer, Keep, KeepDic), RemoveDic}; 84 | {true ,false} -> {KeepDic, orddict:store(Peer, Remove, RemoveDic)}; 85 | {false,false} -> {orddict:store(Peer, Keep, KeepDic), orddict:store(Peer, Remove, RemoveDic)} 86 | end 87 | end, 88 | {orddict:new(), orddict:new()}, D). 89 | 90 | 91 | -spec get_keys(key_matrix(), [{id(),[counter()]}]) -> {FoundKeys::[id()], MissingKeys::[{id(), [counter()]}]}. 92 | get_keys(D, L) -> get_keys(D, L, {[],[]}). 93 | 94 | get_keys(_, [], Acc) -> Acc; 95 | get_keys(D, [{Id, Dots}|T], {FoundKeys, MissingKeys}) -> 96 | Acc2 = case orddict:find(Id, D) of 97 | error -> 98 | {FoundKeys, [{Id,Dots}|MissingKeys]}; 99 | {ok, DotKey} -> 100 | case get_keys_aux(DotKey, Dots) of 101 | {FK, []} -> {FK ++ FoundKeys, MissingKeys}; 102 | {FK, MK} -> {FK ++ FoundKeys, [{Id,MK}|MissingKeys]} 103 | end 104 | end, 105 | get_keys(D, T, Acc2). 106 | 107 | -spec get_keys_aux(orddict:orddict(), [counter()]) -> {Found::[id()], NotFound::[counter()]}. 108 | get_keys_aux(O, L) -> 109 | lists:foldl( 110 | fun (Dot, {FK, MK}) -> 111 | case orddict:find(Dot, O) of 112 | error -> {FK, [Dot|MK]}; 113 | {ok, Key} -> {[Key|FK], MK} 114 | end 115 | end, {[],[]}, L). 116 | 117 | 118 | %%=================================================================== 119 | %% EUnit tests 120 | %%=================================================================== 121 | 122 | -ifdef(TEST). 123 | 124 | add_test() -> 125 | K1 = add_key(new(), "a", "k1", 1), 126 | K2 = add_key(K1, "a", "k2", 2), 127 | K3 = add_key(K2, "b", "kb", 4), 128 | K4 = add_key(K3, "b", "kb2", 2), 129 | K5 = add_key(K4, "c", "kc", 20), 130 | O1 = { [{{"z",8}, "red"}, {{"z",11}, "purple"}, {{"b",3}, "green"}] , [{"a",11}] }, 131 | O2 = { [{{"b",11}, "purple"}] , [{"a",4}, {"b",20}] }, 132 | K6 = add_objects(K1, [{"k100",O1}, {"k200",O2}]), 133 | K7 = add_objects(K1, [{"k200",O2}, {"k100",O1}]), 134 | K8 = add_objects(K5, [{"k200",O2}, {"k100",O1}]), 135 | ?assertEqual( K1 , [{"a", [{1, "k1"}]}]), 136 | ?assertEqual( K2 , [{"a", [{1, "k1"}, {2, "k2"}]}]), 137 | ?assertEqual( K3 , [{"a", [{1, "k1"}, {2, "k2"}]}, {"b",[{4,"kb"}]}]), 138 | ?assertEqual( K4 , [{"a", [{1, "k1"}, {2, "k2"}]}, {"b",[{2,"kb2"},{4,"kb"}]}]), 139 | ?assertEqual( K5 , [{"a", [{1, "k1"}, {2, "k2"}]}, {"b",[{2,"kb2"},{4,"kb"}]}, {"c",[{20,"kc"}]}]), 140 | ?assertEqual( K6 , [{"a", [{1, "k1"}]}, {"b",[{3,"k100"},{11,"k200"}]}, {"z",[{8,"k100"}, {11,"k100"}]}]), 141 | ?assertEqual( K6 , K7), 142 | ?assertEqual( K8 , [{"a", [{1, "k1"}, {2, "k2"}]}, {"b",[{2,"kb2"},{3,"k100"},{4,"kb"},{11,"k200"}]}, {"c",[{20,"kc"}]}, {"z",[{8,"k100"}, {11,"k100"}]}]). 143 | 144 | 145 | empty_test() -> 146 | K1 = add_key(new(), "a", "k1", 1), 147 | K2 = add_key(K1, "a", "k2", 2), 148 | K3 = add_key(K2, "b", "kb", 4), 149 | K4 = add_key(K3, "b", "kb2", 2), 150 | ?assertEqual( empty(new()), true), 151 | ?assertEqual( empty(K1), false), 152 | ?assertEqual( empty(K2), false), 153 | ?assertEqual( empty(K3), false), 154 | ?assertEqual( empty(K4), false). 155 | 156 | size_test() -> 157 | K1 = add_key(new(), "a", "k1", 1), 158 | K2 = add_key(K1, "a", "k2", 2), 159 | K3 = add_key(K2, "b", "kb", 4), 160 | K4 = add_key(K3, "b", "kb2", 2), 161 | K5 = add_key(K4, "c", "kc", 20), 162 | ?assertEqual( size(K1) , 1), 163 | ?assertEqual( size(K2) , 2), 164 | ?assertEqual( size(K3) , 3), 165 | ?assertEqual( size(K4) , 4), 166 | ?assertEqual( size(K5) , 5), 167 | ?assertEqual( size(K5, "a") , 2), 168 | ?assertEqual( size(K5, "b") , 2), 169 | ?assertEqual( size(K5, "c") , 1), 170 | ?assertEqual( size(K5, "d") , 0). 171 | 172 | prune_test() -> 173 | K1 = add_key(new(), "a", "k1", 1), 174 | K2 = add_key(K1, "a", "k2", 2), 175 | K3 = add_key(K2, "b", "kb", 4), 176 | K4 = add_key(K3, "b", "kb2", 2), 177 | K5 = add_key(K4, "c", "kc", 20), 178 | M1 = swc_watermark:new(), 179 | M2 = swc_watermark:update_cell(M1, "a", "c",1), 180 | M3 = swc_watermark:update_cell(M2, "a", "a",2), 181 | M4 = swc_watermark:update_cell(M3, "a", "c",2), 182 | M5 = swc_watermark:update_cell(M4, "b", "x",10), 183 | M6 = swc_watermark:update_cell(M5, "z", "c",190), 184 | M7 = swc_watermark:update_cell(M6, "c", "c",200), 185 | ?assertEqual( prune(K5, M1) , {[{"a", [{1, "k1"}, {2, "k2"}]}, {"b",[{2,"kb2"},{4,"kb"}]}, {"c",[{20,"kc"}]}], []}), 186 | ?assertEqual( prune(K5, M2) , {[{"a", [{2, "k2"}]}, {"b",[{2,"kb2"},{4,"kb"}]}, {"c",[{20,"kc"}]}], [{"a",[{1,"k1"}]}]}), 187 | ?assertEqual( prune(K5, M3) , {[{"a", [{2, "k2"}]}, {"b",[{2,"kb2"},{4,"kb"}]}, {"c",[{20,"kc"}]}], [{"a",[{1,"k1"}]}]}), 188 | ?assertEqual( prune(K5, M4) , {[{"b", [{2,"kb2"},{4,"kb"}]}, {"c",[{20,"kc"}]}], [{"a",[{1,"k1"},{2,"k2"}]}]}), 189 | ?assertEqual( prune(K5, M5) , {[{"c", [{20,"kc"}]}], [{"a",[{1,"k1"}, {2,"k2"}]}, {"b",[{2,"kb2"},{4,"kb"}]}]}), 190 | ?assertEqual( prune(K5, M6) , {[{"c", [{20,"kc"}]}], [{"a",[{1,"k1"}, {2,"k2"}]}, {"b",[{2,"kb2"},{4,"kb"}]}]}), 191 | ?assertEqual( prune(K5, M7) , {[], [{"a",[{1,"k1"}, {2,"k2"}]}, {"b",[{2,"kb2"},{4,"kb"}]}, {"c",[{20,"kc"}]}]}). 192 | 193 | get_keys_test() -> 194 | K1 = add_key(new(), "a", "k1", 1), 195 | K2 = add_key(K1, "a", "k2", 2), 196 | K3 = add_key(K2, "b", "kb", 4), 197 | K4 = add_key(K3, "b", "kb2", 2), 198 | K5 = add_key(K4, "c", "kc", 20), 199 | ?assertEqual( get_keys(K5,[{"a",[1]}]), {["k1"],[]}), 200 | ?assertEqual( get_keys(K5,[{"a",[2]}]), {["k2"],[]}), 201 | ?assertEqual( get_keys(K5,[{"a",[1,2]}]), {["k2","k1"],[]}), 202 | ?assertEqual( get_keys(K5,[{"a",[1,2,5,6]}]), {["k2","k1"],[{"a",[6,5]}]}), 203 | ?assertEqual( get_keys(K5,[{"a",[1,2]}, {"b",[4,5,11]}]), {["kb","k2","k1"],[{"b",[11,5]}]}), 204 | ?assertEqual( get_keys(K5,[{"a",[1,2,22]}, {"b",[4,5,11]}]), {["kb","k2","k1"],[{"b",[11,5]}, {"a",[22]}]}), 205 | ?assertEqual( get_keys(K1,[{"a",[2]}, {"b",[4,5]}]), {[],[{"b",[4,5]}, {"a",[2]}]}). 206 | 207 | -endif. 208 | -------------------------------------------------------------------------------- /src/swc_kv.erl: -------------------------------------------------------------------------------- 1 | %% @author Ricardo Gonçalves 2 | %% @doc 3 | %% An Erlang implementation of Key-Value Logical Clock, 4 | %% in this case a Dotted Causal Container. 5 | %% @en 6 | 7 | -module('swc_kv'). 8 | -author('Ricardo Gonçalves '). 9 | 10 | -ifdef(TEST). 11 | -include_lib("eunit/include/eunit.hrl"). 12 | -endif. 13 | 14 | -include_lib("swc/include/swc.hrl"). 15 | 16 | %% API exports 17 | -export([ new/0 18 | , values/1 19 | , context/1 20 | , sync/2 21 | , discard/2 22 | , strip/2 23 | , fill/2 24 | , fill/3 25 | , add/2 26 | , add/3 27 | ]). 28 | 29 | -export_type([dcc/0]). 30 | 31 | 32 | %% @doc Constructs a new clock set without causal history, 33 | %% and receives one value that goes to the anonymous list. 34 | -spec new() -> dcc(). 35 | new() -> {orddict:new(), swc_vv:new()}. 36 | 37 | 38 | %% @doc Returns the set of values held in the DCC. 39 | -spec values(dcc()) -> [value()]. 40 | values({D,_V}) -> 41 | [ Value || {_, Value} <- D]. 42 | 43 | 44 | %% @doc Returns the causal context of a DCC, which is representable as a 45 | %% Version Vector. 46 | -spec context(dcc()) -> vv(). 47 | context({_D,V}) -> V. 48 | 49 | 50 | %% @doc Performs the synchronization of two DCCs; it discards versions ( 51 | %% {dot,value} pairs) made obsolete by the other DCC, by preserving the 52 | %% versions that are present in both, together with the versions in either of 53 | %% them that are not covered by the relevant entry in the other's causal 54 | %% context; the causal context is obtained by a standard version vector merge 55 | %% function (performing the pointwise maximum). 56 | -spec sync(dcc(), dcc()) -> dcc(). 57 | sync({D1,V1}, {D2,V2}) -> 58 | % if two versions have the same dot, they must have the same value also 59 | FunMerge = fun (_Dot, Val, Val) -> Val end, 60 | % merge the two DCCs 61 | Dm = orddict:merge(FunMerge, D1, D2), 62 | % filter the outdated versions 63 | FunFilter = fun ({Id,Counter}, _Val) -> Counter > min(swc_vv:get(Id,V1), swc_vv:get(Id,V2)) end, 64 | Df = orddict:filter(FunFilter, Dm), 65 | % calculate versions that are in both DCCs 66 | K1 = orddict:fetch_keys(D1), 67 | Pred = fun (Dot,_Val) -> lists:member(Dot, K1) end, 68 | Db = orddict:filter(Pred, D2), 69 | % add these versions to the filtered list of versions 70 | D = orddict:merge(FunMerge, Df, Db), 71 | % return the new list of version and the merged VVs 72 | {D, swc_vv:join(V1,V2)}. 73 | 74 | %% @doc Adds the dots corresponding to each version in the DCC to the BVV; this 75 | %% is accomplished by using the standard fold higher-order function, passing 76 | %% the function swc_node:add/2 defined over BVV and dots, the BVV, and the list of 77 | %% dots in the DCC. 78 | -spec add(bvv(), dcc()) -> bvv(). 79 | add(BVV, {Versions,_VV}) -> 80 | Dots = orddict:fetch_keys(Versions), 81 | lists:foldl(fun(Dot,Acc) -> swc_node:add(Acc,Dot) end, BVV, Dots). 82 | 83 | %% @doc This function is to be used at node I after dcc:discard/2, and adds a 84 | %% mapping, from the Dot (I, N) (which should be obtained by previously applying 85 | %% swc_node:event/2 to the BVV at node I) to the Value, to the DCC, and also advances 86 | %% the i component of the VV in the DCC to N. 87 | -spec add(dcc(), {id(),counter()}, value()) -> dcc(). 88 | add({D,V}, Dot, Value) -> 89 | {orddict:store(Dot, Value, D), swc_vv:add(V,Dot)}. 90 | 91 | %% @doc It discards versions in DCC {D,V} which are made obsolete by a causal 92 | %% context (a version vector) C, and also merges C into DCC causal context V. 93 | -spec discard(dcc(), vv()) -> dcc(). 94 | discard({D,V}, C) -> 95 | FunFilter = fun ({Id,Counter}, _Val) -> Counter > swc_vv:get(Id,C) end, 96 | {orddict:filter(FunFilter, D), swc_vv:join(V,C)}. 97 | 98 | 99 | %% @doc It discards all entries from the version vector V in the DCC that are 100 | %% covered by the corresponding base component of the BVV B; only entries with 101 | %% greater sequence numbers are kept. The idea is that DCCs are stored after 102 | %% being stripped of their causality information that is already present in the 103 | %% node clock BVV. 104 | -spec strip(dcc(), bvv()) -> dcc(). 105 | strip({D,V}, B) -> 106 | FunFilter = 107 | fun (Id,Counter) -> 108 | {Base,_Dots} = swc_node:get(Id,B), 109 | Counter > Base 110 | end, 111 | {D, swc_vv:filter(FunFilter, V)}. 112 | 113 | 114 | %% @doc Function fill adds back causality information to a stripped DCC, before 115 | %% any operation is performed. 116 | -spec fill(dcc(), bvv()) -> dcc(). 117 | fill({D,VV}, BVV) -> 118 | FunFold = 119 | fun(Id, Acc) -> 120 | {Base,_D} = swc_node:get(Id,BVV), 121 | swc_vv:add(Acc,{Id,Base}) 122 | end, 123 | {D, lists:foldl(FunFold, VV, swc_node:ids(BVV))}. 124 | 125 | 126 | %% @doc Same as fill/2 but only adds entries that are elements of a list of Ids, 127 | %% instead of adding all entries in the BVV. 128 | -spec fill(dcc(), bvv(), [id()]) -> dcc(). 129 | fill({D,VV}, BVV, Ids) -> 130 | % only consider ids that belong to both the list of ids received and the BVV 131 | Ids2 = sets:to_list(sets:intersection( 132 | sets:from_list(swc_node:ids(BVV)), 133 | sets:from_list(Ids))), 134 | FunFold = 135 | fun(Id, Acc) -> 136 | {Base,_D} = swc_node:get(Id,BVV), 137 | swc_vv:add(Acc,{Id,Base}) 138 | end, 139 | {D, lists:foldl(FunFold, VV, Ids2)}. 140 | 141 | 142 | 143 | 144 | %% =================================================================== 145 | %% EUnit tests 146 | %% =================================================================== 147 | 148 | -ifdef(TEST). 149 | 150 | d1() -> { [{{"a",8}, "red"}, {{"b",2}, "green"}] , [] }. 151 | d2() -> { [] , [{"a",4}, {"b",20}] }. 152 | d3() -> { [{{"a",1}, "black"}, {{"a",3}, "red"}, {{"b",1}, "green"}, {{"b",2}, "green"}] , 153 | [{"a",4}, {"b",7}] }. 154 | d4() -> { [{{"a",2}, "gray"}, {{"a",3}, "red"}, {{"a",5}, "red"}, {{"b",2}, "green"}] , 155 | [{"a",5}, {"b",5}] }. 156 | d5() -> { [{{"a",5}, "gray"}] , [{"a",5}, {"b",5}, {"c",4}] }. 157 | 158 | 159 | values_test() -> 160 | ?assertEqual( values(d1()), ["red","green"]), 161 | ?assertEqual( values(d2()), []). 162 | 163 | context_test() -> 164 | ?assertEqual( context(d1()), []), 165 | ?assertEqual( context(d2()), [{"a",4}, {"b",20}] ). 166 | 167 | sync_test() -> 168 | D34 = { [{{"a",3}, "red"}, {{"a",5}, "red"}, {{"b",2}, "green"}] , 169 | [{"a",5}, {"b",7}] }, 170 | ?assertEqual( sync(d3(), d3()), d3()), 171 | ?assertEqual( sync(d4(), d4()), d4()), 172 | ?assertEqual( sync(d3(), d4()), D34). 173 | 174 | add2_test() -> 175 | ?assertEqual( add([{"a",{5,3}}], d1()) , [{"a",{8,0}}, {"b",{0,2}}] ). 176 | 177 | discard_test() -> 178 | ?assertEqual( discard(d3(), [] ) , d3()), 179 | ?assertEqual( discard(d3(), [{"a",2}, {"b",15}, {"c",15}] ) , 180 | { [{{"a",3}, "red"}] , [{"a",4}, {"b",15}, {"c",15}] }), 181 | ?assertEqual( discard(d3(), [{"a",3}, {"b",15}, {"c",15}] ) , 182 | { [] , [{"a",4}, {"b",15}, {"c",15}] }). 183 | 184 | strip_test() -> 185 | ?assertEqual( strip(d5(), [{"a",{4,4}}] ) , d5()), 186 | ?assertEqual( strip(d5(), [{"a",{5,0}}] ) , { [{{"a",5}, "gray"}] , [{"b",5}, {"c", 4}] }), 187 | ?assertEqual( strip(d5(), [{"a",{15,0}}] ) , { [{{"a",5}, "gray"}] , [{"b",5}, {"c", 4}] }), 188 | ?assertEqual( strip(d5(), [{"a",{15,4}}, {"b", {1,2}}] ) , { [{{"a",5}, "gray"}] , [{"b",5}, {"c", 4}] }), 189 | ?assertEqual( strip(d5(), [{"b",{15,4}}, {"c", {1,2}}] ) , { [{{"a",5}, "gray"}] , [{"a",5}, {"c", 4}] }), 190 | ?assertEqual( strip(d5(), [{"a",{15,4}}, {"b",{15,4}}, {"c", {5,2}}] ) , { [{{"a",5}, "gray"}] , [] }). 191 | 192 | fill_test() -> 193 | ?assertEqual( fill(d5(), [{"a",{4,4}}] ) , d5()), 194 | ?assertEqual( fill(d5(), [{"a",{5,0}}] ) , d5()), 195 | ?assertEqual( fill(d5(), [{"a",{6,0}}] ) , { [{{"a",5}, "gray"}] , [{"a",6}, {"b",5}, {"c",4}]}), 196 | ?assertEqual( fill(d5(), [{"a",{15,12}}] ) , { [{{"a",5}, "gray"}] , [{"a",15}, {"b",5}, {"c",4}]}), 197 | ?assertEqual( fill(d5(), [{"b",{15,12}}] ) , { [{{"a",5}, "gray"}] , [{"a",5}, {"b",15}, {"c",4}]}), 198 | ?assertEqual( fill(d5(), [{"d",{15,12}}] ) , { [{{"a",5}, "gray"}] , [{"a",5}, {"b",5}, {"c",4}, {"d",15}]}), 199 | ?assertEqual( fill(d5(), [{"a",{9,6}},{"d",{15,12}}] ) , { [{{"a",5},"gray"}], [{"a",9}, {"b",5}, {"c",4}, {"d",15}]}), 200 | ?assertEqual( fill(d5(), [{"a",{9,6}},{"d",{15,12}}], ["a"]) , { [{{"a",5},"gray"}], [{"a",9}, {"b",5}, {"c",4}]}), 201 | ?assertEqual( fill(d5(), [{"a",{9,6}},{"d",{15,12}}], ["b","a"]) , { [{{"a",5},"gray"}], [{"a",9}, {"b",5}, {"c",4}]}), 202 | ?assertEqual( fill(d5(), [{"a",{9,6}},{"d",{15,12}}], ["d","a"]) , { [{{"a",5},"gray"}], [{"a",9}, {"b",5}, {"c",4}, {"d",15}]}), 203 | ?assertEqual( fill(d5(), [{"a",{9,6}},{"d",{15,12}}], ["b"]) , d5()), 204 | ?assertEqual( fill(d5(), [{"a",{9,6}},{"d",{15,12}}], ["f"]) , d5()). 205 | 206 | add3_test() -> 207 | ?assertEqual( add(d1(),{"a",11}, "purple") , { [{{"a",8}, "red"}, {{"a",11}, "purple"}, {{"b",2}, "green"}] , [{"a",11}] } ), 208 | ?assertEqual( add(d2(),{"b",11}, "purple") , { [{{"b",11}, "purple"}] , [{"a",4}, {"b",20}] } ). 209 | 210 | -endif. 211 | -------------------------------------------------------------------------------- /src/swc_node.erl: -------------------------------------------------------------------------------- 1 | %% @author Ricardo Gonçalves 2 | %% @doc 3 | %% An Erlang implementation of a Server Wide Logical Clock, 4 | %% in this case a Bitmapped Version Vector. 5 | %% @end 6 | 7 | -module('swc_node'). 8 | -author('Ricardo Gonçalves '). 9 | 10 | -ifdef(TEST). 11 | -include_lib("eunit/include/eunit.hrl"). 12 | -endif. 13 | 14 | -include_lib("swc/include/swc.hrl"). 15 | 16 | %% API exports 17 | -export([ new/0 18 | , ids/1 19 | , get/2 20 | , norm/1 21 | , values/1 22 | , missing_dots/3 23 | , add/2 24 | , merge/2 25 | , join/2 26 | , base/1 27 | , event/2 28 | , store_entry/3 29 | ]). 30 | 31 | -export_type([bvv/0]). 32 | 33 | %%==================================================================== 34 | %% API functions 35 | %%==================================================================== 36 | 37 | %% @doc Constructs an empty BVV (an orddict in Erlang). 38 | -spec new() -> bvv(). 39 | new() -> orddict:new(). 40 | 41 | %% @doc Returns all IDs from the entries in a BVV. 42 | -spec ids(bvv()) -> [id()]. 43 | ids(B) -> 44 | orddict:fetch_keys(B). 45 | 46 | %% @doc Returns the entry of a BVV associated with a given ID. 47 | -spec get(id(), bvv()) -> entry(). 48 | get(K,B) -> 49 | case orddict:find(K,B) of 50 | error -> {0,0}; 51 | {ok, E} -> E 52 | end. 53 | 54 | %% @doc Normalizes an entry pair, by removing dots and adding them to the base 55 | %% if they are contiguous with the base. 56 | -spec norm(entry()) -> entry(). 57 | norm({N,B}) -> 58 | case B rem 2 of 59 | 0 -> {N,B}; 60 | 1 -> norm({N+1, B bsr 1}) 61 | end. 62 | 63 | %% @doc Normalizes all entries in the BVV, using norm/2. 64 | -spec norm_bvv(bvv()) -> bvv(). 65 | norm_bvv(BVV) -> 66 | % normalize all entries 67 | FunMap = fun (_Id, E) -> norm(E) end, 68 | BVV1 = orddict:map(FunMap, BVV), 69 | % remove `{0,0}` entries 70 | FunFilter = fun (_Id, E) -> E =/= {0,0} end, 71 | orddict:filter(FunFilter, BVV1). 72 | 73 | %% @doc Returns the dots in the first clock that are missing from the second clock, 74 | %% but only from entries in the list of ids received as argument. 75 | -spec missing_dots(bvv(), bvv(), [id()]) -> [{id(),[counter()]}]. 76 | missing_dots(B1, B2, Ids) -> 77 | Fun = 78 | fun (K,V,Acc) -> 79 | case lists:member(K, Ids) of 80 | false -> Acc; 81 | true -> 82 | case orddict:find(K,B2) of 83 | error -> 84 | [{K,values(V)} | Acc]; 85 | {ok, V2} -> 86 | case subtract_dots(V,V2) of 87 | [] -> Acc; 88 | X -> [{K,X} | Acc] 89 | end 90 | end 91 | end 92 | end, 93 | orddict:fold(Fun,[],B1). 94 | 95 | 96 | -spec subtract_dots(entry(), entry()) -> [counter()]. 97 | subtract_dots({N1,B1}, {N2,B2}) when N1 > N2 -> 98 | Dots1 = lists:seq(N2+1,N1) ++ values_aux(N1,B1,[]), 99 | Dots2 = values_aux(N2,B2,[]), 100 | ordsets:subtract(Dots1, Dots2); 101 | subtract_dots({N1,B1}, {N2,B2}) when N1 =< N2 -> 102 | Dots1 = values_aux(N1,B1,[]), 103 | Dots2 = lists:seq(N1+1,N2) ++ values_aux(N2,B2,[]), 104 | ordsets:subtract(Dots1, Dots2). 105 | 106 | %% @doc Returns the sequence numbers for the dots represented by an entry. 107 | -spec values(entry()) -> [counter()]. 108 | values({N,B}) -> 109 | lists:seq(1,N) ++ values_aux(N,B,[]). 110 | 111 | %% @doc Returns the sequence numbers for the dots represented by a bitmap. It's 112 | %% an auxiliary function used by values/1. 113 | -spec values_aux(counter(), counter(), [counter()]) -> [counter()]. 114 | values_aux(_,0,L) -> lists:reverse(L); 115 | values_aux(N,B,L) -> 116 | M = N + 1, 117 | case B rem 2 of 118 | 0 -> values_aux(M, B bsr 1, L); 119 | 1 -> values_aux(M, B bsr 1, [ M | L ]) 120 | end. 121 | 122 | %% @doc Adds a dot (ID, Counter) to a BVV. 123 | -spec add(bvv(), {id(), counter()}) -> bvv(). 124 | add(BVV, {Id, Counter}) -> 125 | Initial = add_aux({0,0}, Counter), 126 | Fun = fun (Entry) -> add_aux(Entry, Counter) end, 127 | orddict:update(Id, Fun, Initial, BVV). 128 | 129 | %% @doc Adds a dot to a BVV entry, returning the normalized entry. 130 | -spec add_aux(entry(), counter()) -> entry(). 131 | add_aux({N,B}, M) -> 132 | case N < M of 133 | false -> norm({N,B}); 134 | true -> M2 = B bor (1 bsl (M-N-1)), 135 | norm({N,M2}) 136 | end. 137 | 138 | %% @doc Merges all entries from the two BVVs. 139 | -spec merge(bvv(), bvv()) -> bvv(). 140 | merge(BVV1, BVV2) -> 141 | FunMerge = fun (_Id, E1, E2) -> join_aux(E1, E2) end, 142 | norm_bvv(orddict:merge(FunMerge, BVV1, BVV2)). 143 | 144 | %% @doc Joins entries from BVV2 that are also IDs in BVV1, into BVV1. 145 | -spec join(bvv(), bvv()) -> bvv(). 146 | join(BVV1, BVV2) -> 147 | % filter keys from BVV2 that are not in BVV1 148 | K1 = orddict:fetch_keys(BVV1), 149 | Pred = fun (Id,_E) -> lists:member(Id, K1) end, 150 | BVV2b = orddict:filter(Pred, BVV2), 151 | % merge BVV1 with filtered BVV2b 152 | FunMerge = fun (_Id, E1, E2) -> join_aux(E1, E2) end, 153 | norm_bvv(orddict:merge(FunMerge, BVV1, BVV2b)). 154 | 155 | %% @doc Returns a (normalized) entry that results from the union of dots from 156 | %% two other entries. Auxiliary function used by join/2. 157 | -spec join_aux(entry(), entry()) -> entry(). 158 | join_aux({N1,B1}, {N2,B2}) -> 159 | case N1 >= N2 of 160 | true -> {N1, B1 bor (B2 bsr (N1-N2))}; 161 | false -> {N2, B2 bor (B1 bsr (N2-N1))} 162 | end. 163 | 164 | %% @doc Takes and returns a BVV where in each entry, the bitmap is reset to zero. 165 | -spec base(bvv()) -> bvv(). 166 | base(BVV) -> 167 | % normalize all entries 168 | BVV1 = norm_bvv(BVV), 169 | % remove all non-contiguous counters w.r.t the base 170 | Fun = fun (_Id, {N,_B}) -> {N,0} end, 171 | orddict:map(Fun, BVV1). 172 | 173 | %% @doc Takes a BVV at node Id and returns a pair with sequence number for a new 174 | %% event (dot) at node Id and the original BVV with the new dot added; this 175 | %% function makes use of the invariant that the node BVV for node Id knows all 176 | %% events generated at Id, i.e., the first component of the pair denotes the 177 | %% last event, and the second component, the bitmap, is always zero. 178 | -spec event(bvv(), id()) -> {counter(), bvv()}. 179 | event(BVV, Id) -> 180 | % find the next counter for Id 181 | C = case orddict:find(Id, BVV) of 182 | % since nodes call event with their Id, their entry always matches {N,0} 183 | {ok, {N,0}} -> N + 1; 184 | error -> 1 185 | end, 186 | % return the new counter and the updated BVV 187 | {C, add(BVV, {Id,C})}. 188 | 189 | %% @doc Stores an Id-Entry pair in a BVV; if the id already exists, the 190 | %% associated entry is replaced by the new one. 191 | store_entry(_Id, {0,0}, BVV) -> BVV; 192 | store_entry(Id, Entry={N,0}, BVV) -> 193 | case orddict:find(Id, BVV) of 194 | {ok, {N2,_}} when N2 >= N -> BVV; 195 | {ok, {N2,_}} when N2 < N -> orddict:store(Id, Entry, BVV); 196 | error -> orddict:store(Id, Entry, BVV) 197 | end. 198 | 199 | 200 | 201 | %%=================================================================== 202 | %% EUnit tests 203 | %%=================================================================== 204 | 205 | -ifdef(TEST). 206 | 207 | norm_test() -> 208 | ?assertEqual( norm({5,3}), {7,0} ), 209 | ?assertEqual( norm({5,2}), {5,2} ), 210 | ?assertEqual( norm_bvv( [{"a",{0,0}}] ), [] ), 211 | ?assertEqual( norm_bvv( [{"a",{5,3}}] ), [{"a",{7,0}}] ). 212 | 213 | values_test() -> 214 | ?assertEqual( lists:sort( values({0,0}) ), lists:sort( [] )), 215 | ?assertEqual( lists:sort( values({5,3}) ), lists:sort( [1,2,3,4,5,6,7] )), 216 | ?assertEqual( lists:sort( values({2,5}) ), lists:sort( [1,2,3,5] )). 217 | 218 | missing_dots_test() -> 219 | B1 = [{"a",{12,0}}, {"b",{7,0}}, {"c",{4,0}}, {"d",{5,0}}, {"e",{5,0}}, {"f",{7,10}}, {"g",{5,10}}, {"h",{5,14}}], 220 | B2 = [{"a",{5,14}}, {"b",{5,14}}, {"c",{5,14}}, {"d",{5,14}}, {"e",{15,0}}, {"f",{5,14}}, {"g",{7,10}}, {"h",{7,10}}], 221 | ?assertEqual( lists:sort(missing_dots(B1,B2,[])), []), 222 | ?assertEqual( lists:sort(missing_dots(B1,B2,["a","b","c","d","e","f","g","h"])), [{"a",[6,10,11,12]}, {"b",[6]}, {"f",[6,11]}, {"h",[8]}]), 223 | ?assertEqual( lists:sort(missing_dots(B1,B2,["a","c","d","e","f","g","h"])), [{"a",[6,10,11,12]}, {"f",[6,11]}, {"h",[8]}]), 224 | ?assertEqual( lists:sort(missing_dots([{"a",{2,2}}, {"b",{3,0}}], [], ["a"])), [{"a",[1,2,4]}]), 225 | ?assertEqual( lists:sort(missing_dots([{"a",{2,2}}, {"b",{3,0}}], [], ["a","b"])), [{"a",[1,2,4]}, {"b",[1,2,3]}]), 226 | ?assertEqual( missing_dots([], B1, ["a","b","c","d","e","f","g","h"]), []). 227 | 228 | 229 | subtract_dots_test() -> 230 | ?assertEqual( subtract_dots({12,0},{5,14}), [6,10,11,12]), 231 | ?assertEqual( subtract_dots({7,0},{5,14}), [6]), 232 | ?assertEqual( subtract_dots({4,0},{5,14}), []), 233 | ?assertEqual( subtract_dots({5,0},{5,14}), []), 234 | ?assertEqual( subtract_dots({5,0},{15,0}), []), 235 | ?assertEqual( subtract_dots({7,10},{5,14}), [6,11]), 236 | ?assertEqual( subtract_dots({5,10},{7,10}), []), 237 | ?assertEqual( subtract_dots({5,14},{7,10}), [8]). 238 | 239 | add_test() -> 240 | ?assertEqual( add( [{"a",{5,3}}] , {"b",0} ), [{"a",{5,3}}, {"b",{0,0}}] ), 241 | ?assertEqual( add( [{"a",{5,3}}] , {"a",1} ), [{"a",{7,0}}] ), 242 | ?assertEqual( add( [{"a",{5,3}}] , {"a",8} ), [{"a",{8,0}}] ), 243 | ?assertEqual( add( [{"a",{5,3}}] , {"b",8} ), [{"a",{5,3}}, {"b",{0,128}}] ). 244 | 245 | add_aux_test() -> 246 | ?assertEqual( add_aux({5,3}, 8), {8,0} ), 247 | ?assertEqual( add_aux({5,3}, 7), {7,0} ), 248 | ?assertEqual( add_aux({5,3}, 4), {7,0} ), 249 | ?assertEqual( add_aux({2,5}, 4), {5,0} ), 250 | ?assertEqual( add_aux({2,5}, 6), {3,6} ), 251 | ?assertEqual( add_aux({2,4}, 6), {2,12} ). 252 | 253 | merge_test() -> 254 | ?assertEqual( merge( [{"a",{5,3}}] , [{"a",{2,4}}] ), [{"a",{7,0}}] ), 255 | ?assertEqual( merge( [{"a",{5,3}}] , [{"b",{2,4}}] ), [{"a",{7,0}}, {"b",{2,4}}] ), 256 | ?assertEqual( merge( [{"a",{5,3}}, {"c",{1,2}}] , [{"b",{2,4}}, {"d",{5,3}}] ), 257 | [{"a",{7,0}}, {"b",{2,4}}, {"c",{1,2}}, {"d",{7,0}}] ), 258 | ?assertEqual( merge( [{"a",{5,3}}, {"c",{1,2}}] , [{"b",{2,4}}, {"c",{5,3}}] ), 259 | [{"a",{7,0}}, {"b",{2,4}}, {"c",{7,0}}]). 260 | 261 | join_test() -> 262 | ?assertEqual( join( [{"a",{5,3}}] , [{"a",{2,4}}] ), [{"a",{7,0}}] ), 263 | ?assertEqual( join( [{"a",{5,3}}] , [{"b",{2,4}}] ), [{"a",{7,0}}] ), 264 | ?assertEqual( join( [{"a",{5,3}}, {"c",{1,2}}] , [{"b",{2,4}}, {"d",{5,3}}] ), [{"a",{7,0}}, {"c",{1,2}}] ), 265 | ?assertEqual( join( [{"a",{5,3}}, {"c",{1,2}}] , [{"b",{2,4}}, {"c",{5,3}}] ), [{"a",{7,0}}, {"c",{7,0}}] ). 266 | 267 | join_aux_test() -> 268 | ?assertEqual( join_aux({5,3}, {2,4}), join_aux({2,4}, {5,3}) ), 269 | ?assertEqual( join_aux({5,3}, {2,4}), {5,3} ), 270 | ?assertEqual( join_aux({2,2}, {3,0}), {3,1} ), 271 | ?assertEqual( join_aux({2,2}, {3,1}), {3,1} ), 272 | ?assertEqual( join_aux({2,2}, {3,2}), {3,3} ), 273 | ?assertEqual( join_aux({2,2}, {3,4}), {3,5} ), 274 | ?assertEqual( join_aux({3,2}, {1,4}), {3,3} ), 275 | ?assertEqual( join_aux({3,2}, {1,16}), {3,6} ). 276 | 277 | base_test() -> 278 | ?assertEqual( base( [{"a",{5,3}}] ), [{"a",{7,0}}] ), 279 | ?assertEqual( base( [{"a",{5,2}}] ), [{"a",{5,0}}] ), 280 | ?assertEqual( base( [{"a",{5,3}}, {"b",{2,4}}, {"c",{1,2}}, {"d",{5,2}}] ), 281 | [{"a",{7,0}}, {"b",{2,0}}, {"c",{1,0}}, {"d",{5,0}}] ). 282 | 283 | 284 | event_test() -> 285 | ?assertEqual( event( [{"a",{7,0}}] , "a"), {8, [{"a",{8,0}}]} ), 286 | ?assertEqual( event( [{"a",{5,3}}] , "b"), {1, [{"a",{5,3}}, {"b",{1,0}}]} ), 287 | ?assertEqual( event( [{"a",{5,3}}, {"b",{2,0}}, {"c",{1,2}}, {"d",{5,3}}] , "b"), 288 | {3, [{"a",{5,3}}, {"b",{3,0}}, {"c",{1,2}}, {"d",{5,3}}]} ). 289 | 290 | 291 | store_entry_test() -> 292 | ?assertEqual( store_entry( "a", {0,0}, [{"a",{7,0}}]), [{"a",{7,0}}] ), 293 | ?assertEqual( store_entry( "b", {0,0}, [{"a",{7,0}}]), [{"a",{7,0}}] ), 294 | ?assertEqual( store_entry( "a", {9,0}, [{"a",{7,0}}]), [{"a",{9,0}}] ), 295 | ?assertEqual( store_entry( "a", {90,0}, [{"a",{7,1234}}]), [{"a",{90,0}}] ), 296 | ?assertEqual( store_entry( "b", {9,0}, [{"a",{7,0}}]), [{"a",{7,0}}, {"b",{9,0}}] ). 297 | 298 | -endif. 299 | -------------------------------------------------------------------------------- /src/swc_vv.erl: -------------------------------------------------------------------------------- 1 | %% @author Ricardo Gonçalves 2 | %% @doc 3 | %% An Erlang implementation of a Version Vector. 4 | %% @end 5 | 6 | -module('swc_vv'). 7 | -author('Ricardo Gonçalves '). 8 | 9 | -ifdef(TEST). 10 | -include_lib("eunit/include/eunit.hrl"). 11 | -endif. 12 | 13 | -include_lib("swc/include/swc.hrl"). 14 | 15 | %% API exports 16 | -export([ new/0 17 | , ids/1 18 | , is_key/2 19 | , get/2 20 | , join/2 21 | , left_join/2 22 | , filter/2 23 | , add/2 24 | , min/1 25 | , min_key/1 26 | , reset_counters/1 27 | , delete_key/2 28 | ]). 29 | 30 | -export_type([vv/0]). 31 | 32 | %% @doc Initializes a new empty version vector. 33 | -spec new() -> vv(). 34 | new() -> orddict:new(). 35 | 36 | %% @doc Returns all the keys (ids) from a VV. 37 | -spec ids(vv()) -> [id()]. 38 | ids(V) -> 39 | orddict:fetch_keys(V). 40 | 41 | -spec is_key(vv(), id()) -> boolean(). 42 | is_key(VV, Id) -> 43 | orddict:is_key(Id, VV). 44 | 45 | %% @doc Returns the counter associated with an id K. If the key is not present 46 | %% in the VV, it returns 0. 47 | -spec get(id(), vv()) -> counter(). 48 | get(K,V) -> 49 | case orddict:find(K,V) of 50 | error -> 0; 51 | {ok, C} -> C 52 | end. 53 | 54 | %% @doc Merges or joins two VVs, taking the maximum counter if an entry is 55 | %% present in both VVs. 56 | -spec join(vv(), vv()) -> vv(). 57 | join(A,B) -> 58 | FunMerge = fun (_Id, C1, C2) -> max(C1, C2) end, 59 | orddict:merge(FunMerge, A, B). 60 | 61 | %% @doc Left joins two VVs, taking the maximum counter if an entry is 62 | %% present in both VVs, and also taking the entrie in A and not in B. 63 | -spec left_join(vv(), vv()) -> vv(). 64 | left_join(A,B) -> 65 | PeersA = orddict:fetch_keys(A), 66 | FunFilter = fun (Id,_) -> lists:member(Id, PeersA) end, 67 | B2 = orddict:filter(FunFilter, B), 68 | orddict:merge(fun (_,C1,C2) -> max(C1,C2) end, A, B2). 69 | 70 | 71 | %% @doc It applies some boolean function F to all entries in the VV, removing 72 | %% those that return False when F is used. 73 | -spec filter(fun((id(), counter()) -> boolean()), vv()) -> vv(). 74 | filter(F,V) -> 75 | orddict:filter(F, V). 76 | 77 | %% @doc Adds an entry {Id, Counter} to the VV, performing the maximum between 78 | %% both counters, if the entry already exists. 79 | -spec add(vv(), {id(), counter()}) -> vv(). 80 | add(VV, {Id, Counter}) -> 81 | Fun = fun (C) -> max(C, Counter) end, 82 | orddict:update(Id, Fun, Counter, VV). 83 | 84 | %% @doc Returns the minimum counters from all the entries in the VV. 85 | -spec min(vv()) -> counter(). 86 | min(VV) -> 87 | Keys = orddict:fetch_keys(VV), 88 | Values = [orddict:fetch(Key, VV) || Key <- Keys], 89 | lists:min(Values). 90 | 91 | %% @doc Returns the key with the minimum counter associated. 92 | -spec min_key(vv()) -> id(). 93 | min_key(VV) -> 94 | Fun = fun (Key, Value, {MKey, MVal}) -> 95 | case Value < MVal of 96 | true -> {Key, Value}; 97 | false -> {MKey, MVal} 98 | end 99 | end, 100 | [Head | Tail] = VV, 101 | {MinKey, _MinValue} = orddict:fold(Fun, Head, Tail), 102 | MinKey. 103 | 104 | %% @doc Returns the VV with the same entries, but with counters at zero. 105 | -spec reset_counters(vv()) -> vv(). 106 | reset_counters(VV) -> 107 | orddict:map(fun (_Id,_Counter) -> 0 end, VV). 108 | 109 | 110 | %% @doc Returns the VV without the entry with a given key. 111 | -spec delete_key(vv(), id()) -> vv(). 112 | delete_key(VV, Key) -> 113 | orddict:erase(Key, VV). 114 | 115 | %% =================================================================== 116 | %% EUnit tests 117 | %% =================================================================== 118 | 119 | -ifdef(TEST). 120 | 121 | min_key_test() -> 122 | A0 = [{"a",2}], 123 | A1 = [{"a",2}, {"b",4}, {"c",4}], 124 | A2 = [{"a",5}, {"b",4}, {"c",4}], 125 | A3 = [{"a",4}, {"b",4}, {"c",4}], 126 | A4 = [{"a",5}, {"b",14}, {"c",4}], 127 | ?assertEqual( "a", min_key(A0)), 128 | ?assertEqual( "a", min_key(A1)), 129 | ?assertEqual( "b", min_key(A2)), 130 | ?assertEqual( "a", min_key(A3)), 131 | ?assertEqual( "c", min_key(A4)), 132 | ok. 133 | 134 | reset_counters_test() -> 135 | E = [], 136 | A0 = [{"a",2}], 137 | A1 = [{"a",2}, {"b",4}, {"c",4}], 138 | ?assertEqual(reset_counters(E), []), 139 | ?assertEqual(reset_counters(A0), [{"a",0}]), 140 | ?assertEqual(reset_counters(A1), [{"a",0}, {"b",0}, {"c",0}]), 141 | ok. 142 | 143 | delete_key_test() -> 144 | E = [], 145 | A0 = [{"a",2}], 146 | A1 = [{"a",2}, {"b",4}, {"c",4}], 147 | ?assertEqual(delete_key(E, "a"), []), 148 | ?assertEqual(delete_key(A0, "a"), []), 149 | ?assertEqual(delete_key(A0, "b"), [{"a",2}]), 150 | ?assertEqual(delete_key(A1, "a"), [{"b",4}, {"c",4}]), 151 | ok. 152 | 153 | join_test() -> 154 | A0 = [{"a",4}], 155 | A1 = [{"a",2}, {"b",4}, {"c",4}], 156 | A2 = [{"a",1}, {"z",10}], 157 | ?assertEqual(join(A0,A1), [{"a",4}, {"b",4}, {"c",4}]), 158 | ?assertEqual(left_join(A0,A1), A0), 159 | ?assertEqual(left_join(A0,A2), A0). 160 | 161 | 162 | 163 | -endif. 164 | -------------------------------------------------------------------------------- /src/swc_watermark.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Ricardo Gonçalves 3 | %%% @copyright (C) 2016, Ricardo Gonçalves 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%%------------------------------------------------------------------- 8 | -module(swc_watermark). 9 | 10 | -author('Ricardo Gonçalves '). 11 | 12 | -compile({no_auto_import,[min/2]}). 13 | 14 | -include_lib("swc/include/swc.hrl"). 15 | 16 | -ifdef(TEST). 17 | -include_lib("eunit/include/eunit.hrl"). 18 | -endif. 19 | 20 | %% API exports 21 | -export([ new/0 22 | , add_peer/3 23 | , left_join/2 24 | , replace_peer/3 25 | , retire_peer/3 26 | , update_peer/3 27 | , update_cell/4 28 | , min/2 29 | , peers/1 30 | , get/3 31 | , reset_counters/1 32 | , delete_peer/2 33 | , prune_retired_peers/3 34 | ]). 35 | 36 | -spec new() -> vv_matrix(). 37 | new() -> 38 | {orddict:new(), orddict:new()}. 39 | 40 | 41 | -spec add_peer(vv_matrix(), id(), [id()]) -> vv_matrix(). 42 | add_peer({M,R}, NewPeer, ItsPeers) -> 43 | % CurrentPeers = orddict:fetch_keys(M), 44 | NewEntry = lists:foldl( 45 | fun (Id, Acc) -> swc_vv:add(Acc, {Id,0}) end, 46 | swc_vv:new(), 47 | [NewPeer | ItsPeers]), 48 | {orddict:store(NewPeer, NewEntry, M), R}. 49 | 50 | 51 | -spec update_peer(vv_matrix(), id(), bvv()) -> vv_matrix(). 52 | update_peer({M,R}, EntryId, NodeClock) -> 53 | {update_peer_aux(M, EntryId, NodeClock), 54 | update_peer_aux(R, EntryId, NodeClock)}. 55 | 56 | update_peer_aux(M, EntryId, NodeClock) -> 57 | orddict:map(fun (Id, OldVV) -> 58 | case swc_vv:is_key(OldVV, EntryId) of 59 | false -> OldVV; 60 | true -> 61 | {Base,_} = swc_node:get(Id, NodeClock), 62 | swc_vv:add(OldVV, {EntryId, Base}) 63 | end 64 | end, 65 | M). 66 | 67 | -spec replace_peer(vv_matrix(), Old::id(), New::id()) -> vv_matrix(). 68 | replace_peer({M,R}, Old, New) -> 69 | M3 = case orddict:is_key(Old, M) of 70 | true -> 71 | OldPeers0 = swc_vv:ids(orddict:fetch(Old,M)), 72 | OldPeers = lists:delete(Old, OldPeers0), 73 | {M2,R} = add_peer({M,R}, New, OldPeers), 74 | orddict:erase(Old, M2); 75 | false -> M 76 | end, 77 | Fun = fun(_K,V) -> 78 | case orddict:find(Old, V) of 79 | error -> V; 80 | {ok, _} -> 81 | V2 = swc_vv:delete_key(V, Old), 82 | swc_vv:add(V2, {New, 0}) 83 | end 84 | end, 85 | {orddict:map(Fun, M3), orddict:map(Fun, R)}. 86 | 87 | -spec retire_peer(vv_matrix(), Old::id(), New::id()) -> vv_matrix(). 88 | retire_peer({M,R}, Old, New) -> 89 | case orddict:find(Old, M) of 90 | error -> 91 | replace_peer({M,R}, Old, New); 92 | {ok, OldEntry} -> 93 | % CurrentCounter = swc_vv:get(Old, OldEntry), 94 | % OldEntry2 = swc_vv:add(OldEntry, {Old, CurrentCounter+Jump}), 95 | R1 = orddict:store(Old, OldEntry, R), 96 | replace_peer({M,R1}, Old, New) 97 | end. 98 | 99 | 100 | -spec left_join(vv_matrix(), vv_matrix()) -> vv_matrix(). 101 | left_join({MA,RA},{MB,RB}) -> 102 | {left_join_aux(MA,MB), left_join_aux(RA,RB)}. 103 | 104 | left_join_aux(A,B) -> 105 | % filter entry peers from B that are not in A 106 | PeersA = orddict:fetch_keys(A), 107 | FunFilter = fun (Id,_) -> lists:member(Id, PeersA) end, 108 | B2 = orddict:filter(FunFilter, B), 109 | orddict:merge(fun (_,V1,V2) -> swc_vv:left_join(V1,V2) end, A, B2). 110 | 111 | -spec update_cell(vv_matrix(), id(), id(), counter()) -> vv_matrix(). 112 | update_cell({M,R}, EntryId, PeerId, Counter) -> 113 | Top = {PeerId, Counter}, 114 | {orddict:update( 115 | EntryId, 116 | fun (OldVV) -> swc_vv:add(OldVV, Top) end, 117 | swc_vv:add(swc_vv:new(), Top), 118 | M), R}. 119 | 120 | -spec min(vv_matrix(), id()) -> counter(). 121 | min({M,R}, Id) -> 122 | max(min_aux(M, Id), min_aux(R, Id)). 123 | 124 | min_aux(M, Id) -> 125 | case orddict:find(Id, M) of 126 | error -> 0; 127 | {ok, VV} -> swc_vv:min(VV) 128 | end. 129 | 130 | -spec peers(vv_matrix()) -> [id()]. 131 | peers({M,_}) -> 132 | orddict:fetch_keys(M). 133 | 134 | -spec get(vv_matrix(), id(), id()) -> counter(). 135 | get({M,_}, P1, P2) -> 136 | case orddict:find(P1, M) of 137 | error -> 0; 138 | {ok, VV} -> swc_vv:get(P2, VV) 139 | end. 140 | 141 | -spec reset_counters(vv_matrix()) -> vv_matrix(). 142 | reset_counters({M,R}) -> 143 | {orddict:map(fun (_Id,VV) -> swc_vv:reset_counters(VV) end, M), 144 | orddict:map(fun (_Id,VV) -> swc_vv:reset_counters(VV) end, R)}. 145 | 146 | -spec delete_peer(vv_matrix(), id()) -> vv_matrix(). 147 | delete_peer({M,R}, Id) -> 148 | M2 = orddict:erase(Id, M), 149 | {orddict:map(fun (_Id,VV) -> swc_vv:delete_key(VV, Id) end, M2), R}. 150 | 151 | -spec prune_retired_peers(vv_matrix(), key_matrix(), [id()]) -> vv_matrix(). 152 | prune_retired_peers({M,R}, DKM, DontRemotePeers) -> 153 | {M, orddict:filter(fun (Peer,_) -> 154 | swc_dotkeymap:is_key(DKM, Peer) orelse 155 | lists:member(Peer, DontRemotePeers) 156 | end, R)}. 157 | 158 | %%=================================================================== 159 | %% EUnit tests 160 | %%=================================================================== 161 | 162 | -ifdef(TEST). 163 | 164 | update_test() -> 165 | C1 = [{"a",{12,0}}, {"b",{7,0}}, {"c",{4,0}}, {"d",{5,0}}, {"e",{5,0}}, {"f",{7,10}}, {"g",{5,10}}, {"h",{5,14}}], 166 | C2 = [{"a",{5,14}}, {"b",{5,14}}, {"c",{50,14}}, {"d",{5,14}}, {"e",{15,0}}, {"f",{5,14}}, {"g",{7,10}}, {"h",{7,10}}], 167 | M = new(), 168 | M1 = update_cell(M, "a", "b",4), 169 | M2 = update_cell(M1, "a", "c",10), 170 | M3 = update_cell(M2, "c", "c",2), 171 | M4 = update_cell(M3, "c", "c",20), 172 | M5 = update_cell(M4, "c", "c",15), 173 | M6 = update_peer(M5, "c", C1), 174 | M7 = update_peer(M5, "c", C2), 175 | M8 = update_peer(M5, "a", C1), 176 | M9 = update_peer(M5, "a", C2), 177 | M10 = update_peer(M5, "b", C1), 178 | M11 = update_peer(M5, "b", C2), 179 | N = {[{"c",[{"c",4},{"d",3},{"z",0}]}, {"d",[{"c",0},{"d",1},{"e",2}]}, {"z", [{"a",0},{"c",0},{"z",0}]}], [{"b",[{"a",2},{"b",2},{"c",3}]}]}, 180 | ?assertEqual( update_peer(N,"a",C1), {[{"c",[{"c",4},{"d",3},{"z",0}]}, {"d",[{"c",0},{"d",1},{"e",2}]}, {"z", [{"a",0},{"c",0},{"z",0}]}], [{"b",[{"a",7},{"b",2},{"c",3}]}]}), 181 | ?assertEqual( update_peer(N,"c",C2), {[{"c",[{"c",50},{"d",3},{"z",0}]}, {"d",[{"c",5},{"d",1},{"e",2}]}, {"z", [{"a",0},{"c",0},{"z",0}]}], [{"b",[{"a",2},{"b",2},{"c",5}]}]}), 182 | ?assertEqual( M1, {[{"a",[{"b",4}]}], []}), 183 | ?assertEqual( M2, {[{"a",[{"b",4}, {"c",10}]}], []}), 184 | ?assertEqual( M3, {[{"a",[{"b",4}, {"c",10}]}, {"c",[{"c",2}]}], []}), 185 | ?assertEqual( M4, {[{"a",[{"b",4}, {"c",10}]}, {"c",[{"c",20}]}], []}), 186 | ?assertEqual( M4, M5), 187 | ?assertEqual( M6, {[{"a",[{"b",4}, {"c",12}]}, {"c",[{"c",20}]}], []}), 188 | ?assertEqual( M7, {[{"a",[{"b",4}, {"c",10}]}, {"c",[{"c",50}]}], []}), 189 | ?assertEqual( M8, {[{"a",[{"b",4}, {"c",10}]}, {"c",[{"c",20}]}], []}), 190 | ?assertEqual( M9, {[{"a",[{"b",4}, {"c",10}]}, {"c",[{"c",20}]}], []}), 191 | ?assertEqual( M10, {[{"a",[{"b",12}, {"c",10}]}, {"c",[{"c",20}]}], []}), 192 | ?assertEqual( M11, {[{"a",[{"b",5}, {"c",10}]}, {"c",[{"c",20}]}], []}). 193 | 194 | left_join_test() -> 195 | A = {[{"a",[{"b",4}, {"c",10}]}, {"c",[{"c",20}]}, {"z",[{"t1",0},{"t2",0},{"z",0}]}], []}, 196 | Z = {[{"a",[{"b",5}, {"c",8}, {"z",2}]}, {"c",[{"c",20}]}, {"z",[{"t1",0},{"t2",0},{"z",0}]}], []}, 197 | B = {[{"a",[{"b",2}, {"c",10}]}, {"b",[]}, {"c",[{"c",22}]}], []}, 198 | C = {[{"z",[{"a",1}, {"b",0}, {"z",4}]}], []}, 199 | ?assertEqual( left_join(A,B), {[{"a",[{"b",4},{"c",10}]}, {"c",[{"c",22}]}, {"z",[{"t1",0},{"t2",0},{"z",0}]}], []}), 200 | ?assertEqual( left_join(A,Z), {[{"a",[{"b",5},{"c",10}]}, {"c",[{"c",20}]}, {"z",[{"t1",0},{"t2",0},{"z",0}]}], []}), 201 | ?assertEqual( left_join(A,C), {[{"a",[{"b",4},{"c",10}]}, {"c",[{"c",20}]}, {"z",[{"t1",0},{"t2",0},{"z",4}]}], []}), 202 | ?assertEqual( left_join(B,A), {[{"a",[{"b",4},{"c",10}]}, {"b",[]}, {"c",[{"c",22}]}], []}), 203 | ?assertEqual( left_join(B,C), B), 204 | ?assertEqual( left_join(C,A), C), 205 | ?assertEqual( left_join(C,B), C). 206 | 207 | 208 | add_peers_test() -> 209 | M = new(), 210 | M1 = update_cell(M, "a", "b",4), 211 | M2 = update_cell(M1, "a", "c",10), 212 | M3 = update_cell(M2, "c", "c",2), 213 | M4 = update_cell(M3, "c", "c",20), 214 | ?assertEqual( add_peer(add_peer(M, "z", ["b","a"]), "l", ["z","y"]), 215 | add_peer(add_peer(M, "l", ["y","z"]), "z", ["a","b"])), 216 | ?assertEqual( add_peer(M, "z",["a","b"]), {[{"z",[{"a",0},{"b",0},{"z",0}]}], []}), 217 | ?assertEqual( add_peer(M4, "z",["t2","t1"]), {[{"a",[{"b",4}, {"c",10}]}, {"c",[{"c",20}]}, {"z",[{"t1",0},{"t2",0},{"z",0}]}], []}). 218 | 219 | min_test() -> 220 | M = new(), 221 | M1 = update_cell(M, "a", "b",4), 222 | M2 = update_cell(M1, "a", "c",10), 223 | M3 = update_cell(M2, "c", "c",2), 224 | M4 = update_cell(M3, "c", "c",20), 225 | ?assertEqual( min(M, "a"), 0), 226 | ?assertEqual( min(M1, "a"), 4), 227 | ?assertEqual( min(M1, "b"), 0), 228 | ?assertEqual( min(M4, "a"), 4), 229 | ?assertEqual( min(M4, "c"), 20), 230 | ?assertEqual( min(M4, "b"), 0). 231 | 232 | peers_test() -> 233 | M = new(), 234 | M1 = update_cell(M, "a", "b",4), 235 | M2 = update_cell(M1, "a", "c",10), 236 | M3 = update_cell(M2, "c", "c",2), 237 | M4 = update_cell(M3, "c", "c",20), 238 | M5 = update_cell(M4, "c", "c",15), 239 | ?assertEqual( peers(M), []), 240 | ?assertEqual( peers(M1), ["a"]), 241 | ?assertEqual( peers(M5), ["a", "c"]). 242 | 243 | 244 | get_test() -> 245 | M = new(), 246 | M1 = update_cell(M, "a", "b",4), 247 | M2 = update_cell(M1, "a", "c",10), 248 | M3 = update_cell(M2, "c", "c",2), 249 | M4 = update_cell(M3, "c", "c",20), 250 | ?assertEqual( get(M, "a", "a"), 0), 251 | ?assertEqual( get(M1, "a", "a"), 0), 252 | ?assertEqual( get(M1, "b", "a"), 0), 253 | ?assertEqual( get(M4, "c", "c"), 20), 254 | ?assertEqual( get(M4, "a", "c"), 10). 255 | 256 | reset_counters_test() -> 257 | M = new(), 258 | M1 = update_cell(M, "a", "b",4), 259 | M2 = update_cell(M1, "a", "c",10), 260 | M3 = update_cell(M2, "c", "c",2), 261 | M4 = update_cell(M3, "c", "c",20), 262 | ?assertEqual( reset_counters(M), M), 263 | ?assertEqual( reset_counters(M1), {[{"a",[{"b",0}]}], []}), 264 | ?assertEqual( reset_counters(M2), {[{"a",[{"b",0}, {"c",0}]}], []}), 265 | ?assertEqual( reset_counters(M3), {[{"a",[{"b",0}, {"c",0}]}, {"c",[{"c",0}]}], []}), 266 | ?assertEqual( reset_counters(M4), {[{"a",[{"b",0}, {"c",0}]}, {"c",[{"c",0}]}], []}). 267 | 268 | delete_peer_test() -> 269 | M = new(), 270 | M1 = update_cell(M, "a", "b",4), 271 | M2 = update_cell(M1, "a", "c",10), 272 | M3 = update_cell(M2, "c", "c",2), 273 | M4 = update_cell(M3, "c", "c",20), 274 | ?assertEqual( delete_peer(M1, "a"), {[], []}), 275 | ?assertEqual( delete_peer(M1, "b"), {[{"a",[]}], []}), 276 | ?assertEqual( delete_peer(M1, "c"), {[{"a",[{"b",4}]}], []}), 277 | ?assertEqual( delete_peer(M4, "a"), {[{"c",[{"c",20}]}], []}), 278 | ?assertEqual( delete_peer(M4, "c"), {[{"a",[{"b",4}]}], []}). 279 | 280 | replace_peer_test() -> 281 | A = add_peer(new(), "a", ["b","c"]), 282 | B = add_peer(A, "b", ["a","c"]), 283 | C = add_peer(B, "c", ["a","b"]), 284 | Z = {[{"a",[{"a",9},{"c",2},{"z",3}]}, {"c",[{"a",1},{"c",4},{"z",3}]}, {"z", [{"a",0},{"c",1},{"z",2}]}], []}, 285 | W = {[{"b",[{"a",9},{"b",2},{"c",3}]}, {"c",[{"b",1},{"c",4},{"d",3}]}, {"d", [{"c",0},{"d",1},{"e",2}]}], []}, 286 | ?assertEqual( replace_peer(C,"b","z"), {[{"a",[{"a",0},{"c",0},{"z",0}]}, {"c",[{"a",0},{"c",0},{"z",0}]}, {"z", [{"a",0},{"c",0},{"z",0}]}], []}), 287 | ?assertEqual( replace_peer(Z,"a","b"), {[{"b",[{"b",0},{"c",0},{"z",0}]}, {"c",[{"b",0},{"c",4},{"z",3}]}, {"z", [{"b",0},{"c",1},{"z",2}]}], []}), 288 | ?assertEqual( replace_peer(W,"b","z"), {[{"c",[{"c",4},{"d",3},{"z",0}]}, {"d",[{"c",0},{"d",1},{"e",2}]}, {"z", [{"a",0},{"c",0},{"z",0}]}], []}), 289 | ?assertEqual( replace_peer(W,"a","z"), {[{"b",[{"b",2},{"c",3},{"z",0}]}, {"c",[{"b",1},{"c",4},{"d",3}]}, {"d", [{"c",0},{"d",1},{"e",2}]}], []}). 290 | 291 | retire_peer_test() -> 292 | A = add_peer(new(), "a", ["b","c"]), 293 | B = add_peer(A, "b", ["a","c"]), 294 | C = add_peer(B, "c", ["a","b"]), 295 | Z = {[{"a",[{"a",9},{"c",2},{"z",3}]}, {"c",[{"a",1},{"c",4},{"z",3}]}, {"z", [{"a",0},{"c",1},{"z",2}]}], []}, 296 | W = {[{"b",[{"a",9},{"b",2},{"c",3}]}, {"c",[{"b",1},{"c",4},{"d",3}]}, {"d", [{"c",0},{"d",1},{"e",2}]}], []}, 297 | ?assertEqual( retire_peer(C,"b","z"), 298 | {[{"a",[{"a",0},{"c",0},{"z",0}]}, {"c",[{"a",0},{"c",0},{"z",0}]}, {"z", [{"a",0},{"c",0},{"z",0}]}], [{"b",[{"a",0},{"c",0},{"z",0}]}]}), 299 | ?assertEqual( retire_peer(Z,"a","b"), 300 | {[{"b",[{"b",0},{"c",0},{"z",0}]}, {"c",[{"b",0},{"c",4},{"z",3}]}, {"z", [{"b",0},{"c",1},{"z",2}]}], [{"a",[{"b",0},{"c",2},{"z",3}]}]}), 301 | ?assertEqual( retire_peer(W,"b","z"), 302 | {[{"c",[{"c",4},{"d",3},{"z",0}]}, {"d",[{"c",0},{"d",1},{"e",2}]}, {"z", [{"a",0},{"c",0},{"z",0}]}], [{"b",[{"a",9},{"c",3},{"z",0}]}]}), 303 | ?assertEqual( retire_peer(W,"a","z"), 304 | {[{"b",[{"b",2},{"c",3},{"z",0}]}, {"c",[{"b",1},{"c",4},{"d",3}]}, {"d", [{"c",0},{"d",1},{"e",2}]}], []}). 305 | 306 | prune_retired_peers_test() -> 307 | D1 = [{"a",[1,2,22]}, {"b",[4,5,11]}], 308 | D2 = [{"a",[1,2,22]}, {"z",[4,5,11]}], 309 | A = {[{"a",[{"a",0},{"c",0},{"z",0}]}, {"c",[{"a",0},{"c",0},{"z",0}]}, {"z", [{"a",0},{"c",0},{"z",0}]}], [{"b",[{"a",0},{"b",0},{"c",0}]}]}, 310 | A2 = {[{"a",[{"a",0},{"c",0},{"z",0}]}, {"c",[{"a",0},{"c",0},{"z",0}]}, {"z", [{"a",0},{"c",0},{"z",0}]}], []}, 311 | ?assertEqual( prune_retired_peers(A, D1, []), A), 312 | ?assertEqual( prune_retired_peers(A, D1, ["a", "b","c","z"]), A), 313 | ?assertEqual( prune_retired_peers(A, D2, []), A2), 314 | ?assertEqual( prune_retired_peers(A, D2, ["b"]), A), 315 | ?assertEqual( prune_retired_peers(A, [], []), A2). 316 | 317 | 318 | 319 | -endif. 320 | 321 | --------------------------------------------------------------------------------