Current section
Files
Jump to
Current section
Files
src/vectorclock.erl
%% -------------------------------------------------------------------
%%
%% Copyright (c) 2014 SyncFree Consortium. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License. You may obtain
%% a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
%% -------------------------------------------------------------------
-module(vectorclock).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
-export([all_dots_greater/2, all_dots_smaller/2, conc/2,
eq/2, fold/3, from_list/1, ge/2, get/2,
gt/2, le/2, lt/2, map/2, max/1, min/1,
min_clock/2, new/0, set_all/2,
set/3, size/1, to_list/1, update_with/4]).
-type vc_node() :: term().
-type vectorclock() :: #{vc_node() => pos_integer()}.
-export_type([vectorclock/0]).
-spec new() -> vectorclock().
new() -> maps:new().
-spec get(vc_node(), vectorclock()) -> non_neg_integer().
get(Key, VectorClock) ->
maps:get(Key, VectorClock, 0).
-spec set(vc_node(), non_neg_integer(), vectorclock()) -> vectorclock().
set(Key, 0, VectorClock) ->
maps:remove(Key, VectorClock);
set(Key, Value, VectorClock) when Value > 0 ->
VectorClock#{Key => Value}.
-spec set_all(pos_integer(), vectorclock()) -> vectorclock().
set_all(0, _VectorClock) ->
new();
set_all(Value, VectorClock) when Value > 0 ->
Fun = fun(_K, _V) -> Value end,
map(Fun, VectorClock).
-spec from_list([{vc_node(), pos_integer()}]) -> vectorclock().
from_list(List) -> maps:from_list(List).
-spec to_list(vectorclock()) -> [{vc_node(), pos_integer()}].
to_list(VectorClock) -> maps:to_list(VectorClock).
-spec map(fun((vc_node(), pos_integer()) -> pos_integer()), vectorclock()) -> vectorclock().
map(Fun, VectorClock) -> maps:map(Fun, VectorClock).
-spec fold(fun ((vc_node(), pos_integer(), X) -> X), X, vectorclock()) -> X.
fold(Fun, Init, VectorClock) ->
maps:fold(Fun, Init, VectorClock).
-spec update_with(vc_node(), fun((pos_integer()) -> pos_integer()), pos_integer(), vectorclock()) -> vectorclock().
update_with(Key, Fun, Init, VectorClock) ->
maps:update_with(Key, Fun, Init, VectorClock).
-spec min_clock(vectorclock(), [vc_node()]) -> non_neg_integer().
min_clock(_VectorClock, []) ->
0;
min_clock(VectorClock, Nodes) ->
lists:min([get(Node, VectorClock) || Node <- Nodes]).
-spec max([vectorclock()]) -> vectorclock().
max([]) -> new();
max([V]) -> V;
max([V1, V2 | T]) -> max([max2(V1, V2) | T]).
%% component-wise maximum of two clocks
-spec max2(vectorclock(), vectorclock()) -> vectorclock().
max2(V1, V2) ->
FoldFun =
fun(DC, A, Acc) ->
B = get(DC, Acc),
case A > B of
true -> Acc#{DC => A};
false -> Acc
end
end,
maps:fold(FoldFun, V2, V1).
-spec min([vectorclock()]) -> vectorclock().
min([]) -> new();
min([V]) -> V;
min([V1, V2 | T]) ->
min([min2(V1, V2) | T]).
%% component-wise minimum of two clocks
-spec min2(vectorclock(), vectorclock()) -> vectorclock().
min2(V1, V2) ->
FoldFun = fun (DC, A, Acc) ->
B = get(DC, V2),
C = min(A, B),
case C of
0 -> Acc;
_ -> Acc#{DC => C}
end
end,
maps:fold(FoldFun, new(), V1).
-spec size(vectorclock()) -> non_neg_integer().
size(V) -> maps:size(V).
-spec for_all_keys(fun ((pos_integer(), pos_integer()) -> boolean()), vectorclock(), vectorclock()) -> boolean().
for_all_keys(F, V1, V2) ->
AllDCs = maps:keys(maps:merge(V1, V2)),
Func = fun (DC) ->
A = get(DC, V1),
B = get(DC, V2),
F(A, B)
end,
lists:all(Func, AllDCs).
-spec eq(vectorclock(), vectorclock()) -> boolean().
eq(V1, V2) -> le(V1, V2) andalso le(V2, V1).
-spec le(vectorclock(), vectorclock()) -> boolean().
le(V1, V2) ->
try
maps:fold(fun (DC, V, true) ->
case V =< get(DC, V2) of
true -> true;
false -> throw(false)
end
end, true, V1)
catch
false -> false
end .
-spec ge(vectorclock(), vectorclock()) -> boolean().
ge(V1, V2) -> le(V2, V1).
-spec all_dots_smaller(vectorclock(), vectorclock()) -> boolean().
all_dots_smaller(V1, V2) ->
for_all_keys(fun (A, B) -> A < B end, V1, V2).
-spec all_dots_greater(vectorclock(), vectorclock()) -> boolean().
all_dots_greater(V1, V2) ->
for_all_keys(fun (A, B) -> A > B end, V1, V2).
-spec gt(vectorclock(), vectorclock()) -> boolean().
gt(V1, V2) -> lt(V2, V1).
-spec lt(vectorclock(), vectorclock()) -> boolean().
lt(V1, V2) ->
try maps:fold(fun (DC, V, Acc) ->
X = get(DC, V2),
case V =< X of
true -> Acc orelse V < X;
false -> throw(false)
end
end,
false, V1)
orelse
maps:fold(fun (DC, V, _) ->
X = get(DC, V1),
case V > X of
true -> throw(true);
false -> false
end
end,
false, V2)
catch
R -> R
end.
-spec conc(vectorclock(), vectorclock()) -> boolean().
conc(V1, V2) -> not ge(V1, V2) andalso not le(V1, V2).
-ifdef(TEST).
vectorclock_empty_test() ->
V1 = new(),
V2 = from_list([]),
?assertEqual(V1, V2),
?assertEqual((eq(min([]), max([]))), true),
?assertEqual((to_list(V1)), []).
vectorclock_test() ->
V1 = from_list([{1, 5}, {2, 4}, {3, 5}, {4, 6}]),
V2 = from_list([{1, 4}, {2, 3}, {3, 4}, {4, 5}]),
V3 = from_list([{1, 5}, {2, 4}, {3, 4}, {4, 5}]),
V4 = from_list([{1, 6}, {2, 3}, {3, 1}, {4, 7}]),
V5 = from_list([{1, 6}, {2, 7}]),
?assert(all_dots_greater(V1, V2)),
?assert(all_dots_smaller(V2, V1)),
?assertNot(all_dots_greater(V1, V3)),
?assert(gt(V1, V3)),
?assertNot(gt(V1, V1)),
?assertNot(ge(V1, V4)),
?assertNot(le(V1, V4)),
?assertNot(eq(V1, V4)),
?assertNot(ge(V1, V5)).
vectorclock_lt_test() ->
?assertEqual((lt(from_list([{a, 1}]), from_list([{a, 1}, {b, 1}]))), true),
?assertEqual((lt(from_list([{a, 1}]), from_list([{a, 1}]))), false),
?assertEqual((lt(from_list([{a, 2}]), from_list([{a, 1}]))), false).
vectorclock_max_test() ->
V1 = from_list([{1, 5}, {2, 4}]),
V2 = from_list([{1, 6}, {2, 3}]),
V3 = from_list([{1, 3}, {3, 2}]),
Expected12 = from_list([{1, 6}, {2, 4}]),
Expected23 = from_list([{1, 6}, {2, 3}, {3, 2}]),
Expected13 = from_list([{1, 5}, {2, 4}, {3, 2}]),
Expected123 = from_list([{1, 6}, {2, 4}, {3, 2}]),
Unexpected123 = from_list([{1, 5}, {2, 5}, {3, 5}]),
?assertEqual((eq(max([V1, V2]), Expected12)), true),
?assertEqual((eq(max([V2, V3]), Expected23)), true),
?assertEqual((eq(max([V1, V3]), Expected13)), true),
?assertEqual((eq(max([V1, V2, V3]), Expected123)), true),
?assertEqual((eq(max([V1, V2, V3]), Unexpected123)), false).
vectorclock_min_test() ->
V1 = from_list([{1, 5}, {2, 4}]),
V2 = from_list([{1, 6}, {2, 3}]),
V3 = from_list([{1, 3}, {3, 2}]),
Expected12 = from_list([{1, 5}, {2, 3}]),
Expected23 = from_list([{1, 3}]),
Expected13 = from_list([{1, 3}]),
Expected123 = from_list([{1, 3}]),
Unexpected123 = from_list([{1, 3}, {2, 3}, {3, 2}]),
?assert(eq(min([V1, V2]), Expected12)),
?assert(eq(min([V2, V3]), Expected23)),
?assert(eq(min([V1, V3]), Expected13)),
?assert(eq(min([V1, V2, V3]), Expected123)),
?assertNot(eq(min([V1, V2, V3]), Unexpected123)),
?assert(eq(vectorclock:min([V1]), vectorclock:max([V1]))).
vectorclock_conc_test() ->
V1 = from_list([{1, 5}, {2, 4}]),
V2 = from_list([{1, 6}, {2, 3}]),
V3 = from_list([{1, 3}, {3, 2}]),
V4 = from_list([{1, 6}, {3, 3}]),
V5 = from_list([{1, 6}]),
?assert(conc(V1, V2)),
?assert(conc(V2, V3)),
?assertNot(conc(V3, V4)),
?assertNot(conc(V5, V4)).
vectorclock_set_test() ->
V1 = from_list([{1, 1}, {2, 2}]),
V2 = from_list([{1, 1}, {2, 2}, {3, 3}]),
V3 = from_list([{1, 1}, {2, 4}]),
?assertEqual(V2, set(3, 3, V1)),
?assertEqual(V3, set(2, 4, V1)),
?assertEqual(V1, set(3, 0, V2)).
vectorclock_setall_test() ->
V1 = from_list([{1, 5}, {8, 4}, {3, 5}, {9, 6}]),
V2 = from_list([{1, 7}, {8, 7}, {3, 7}, {9, 7}]),
?assertEqual(V2, set_all(7, V1)),
?assertEqual(new(), set_all(0, V1)).
vectorclock_minclock_test() ->
V1 = from_list([{1, 5}, {8, 4}, {3, 5}, {9, 6}]),
?assertEqual(4, min_clock(V1, [1, 8, 3, 9])),
?assertEqual(0, min_clock(V1, [])),
?assertEqual(0, min_clock(V1, [1, 8, 3, 9, 10])).
vectorclock_size_test() ->
V1 = from_list([{1, 5}, {8, 4}, {3, 5}, {9, 6}]),
V2 = new(),
?assertEqual(4, vectorclock:size(V1)),
?assertEqual(0, vectorclock:size(V2)).
vectorclock_update_test() ->
V1 = from_list([{1, 5}, {8, 4}, {3, 5}, {9, 6}]),
V2 = from_list([{1, 5}, {8, 8}, {3, 5}, {9, 6}]),
?assertEqual(V2, update_with(8, fun (X) -> X * 2 end, 0, V1)).
vectorclock_fold_test() ->
V1 = from_list([{1, 5}, {8, 4}, {3, 5}, {9, 6}]),
?assertEqual(20, fold(fun (_Node, X, Acc) -> X + Acc end, 0, V1)).
-endif.