Packages

A vectorclock library for Erlang

Current section

Files

Jump to
vectorclock src vectorclock.erl
Raw

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([
get_clock_of_dc/2,
set_clock_of_dc/3,
from_list/1,
new/0,
eq/2,
all_dots_smaller/2,
all_dots_greater/2,
le/2,
ge/2,
gt/2,
lt/2,
max/1,
min/1,
conc/2]).
-type actor() :: any().
-type vectorclock() :: dict:dict(actor(), non_neg_integer()).
-export_type([vectorclock/0]).
-spec new() -> vectorclock().
new() ->
dict:new().
-spec get_clock_of_dc(any(), vectorclock()) -> non_neg_integer().
get_clock_of_dc(Key, VectorClock) ->
case dict:find(Key, VectorClock) of
{ok, Value} -> Value;
error -> 0
end.
-spec set_clock_of_dc(any(), non_neg_integer(), vectorclock()) -> vectorclock().
set_clock_of_dc(Key, Value, VectorClock) ->
dict:store(Key, Value, VectorClock).
-spec from_list([{any(), non_neg_integer()}]) -> vectorclock().
from_list(List) ->
dict:from_list(List).
-spec max([vectorclock()]) -> vectorclock().
max([]) -> new();
max([V]) -> V;
max([V1, V2|T]) -> max([merge(fun erlang:max/2, V1, V2)|T]).
-spec min([vectorclock()]) -> vectorclock().
min([]) -> new();
min([V]) -> V;
min([V1, V2|T]) -> min([merge(fun erlang:min/2, V1, V2)|T]).
-spec merge(fun((non_neg_integer(), non_neg_integer()) -> non_neg_integer()), vectorclock(), vectorclock()) -> vectorclock().
merge(F, V1, V2) ->
AllDCs = dict:fetch_keys(V1) ++ dict:fetch_keys(V2),
Func = fun(DC) ->
A = get_clock_of_dc(DC, V1),
B = get_clock_of_dc(DC, V2),
{DC, F(A, B)}
end,
from_list(lists:map(Func, AllDCs)).
-spec for_all_keys(fun((non_neg_integer(), non_neg_integer()) -> boolean()), vectorclock(), vectorclock()) -> boolean().
for_all_keys(F, V1, V2) ->
%% We could but do not care about duplicate DC keys - finding duplicates is not worth the effort
AllDCs = dict:fetch_keys(V1) ++ dict:fetch_keys(V2),
Func = fun(DC) ->
A = get_clock_of_dc(DC, V1),
B = get_clock_of_dc(DC, V2),
F(A, B)
end,
lists:all(Func, AllDCs).
-spec eq(vectorclock(), vectorclock()) -> boolean().
eq(V1, V2) -> for_all_keys(fun(A, B) -> A == B end, V1, V2).
-spec le(vectorclock(), vectorclock()) -> boolean().
le(V1, V2) -> for_all_keys(fun(A, B) -> A =< B end, V1, V2).
-spec ge(vectorclock(), vectorclock()) -> boolean().
ge(V1, V2) -> for_all_keys(fun(A, B) -> A >= B end, V1, V2).
-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) -> ge(V1, V2) and (not eq(V1, V2)).
-spec lt(vectorclock(), vectorclock()) -> boolean().
lt(V1, V2) -> le(V1, V2) and (not eq(V1, V2)).
-spec conc(vectorclock(), vectorclock()) -> boolean().
conc(V1, V2) -> (not ge(V1, V2)) andalso (not le(V1, V2)).
-ifdef(TEST).
vectorclock_test() ->
V1 = vectorclock:from_list([{1, 5}, {2, 4}, {3, 5}, {4, 6}]),
V2 = vectorclock:from_list([{1, 4}, {2, 3}, {3, 4}, {4, 5}]),
V3 = vectorclock:from_list([{1, 5}, {2, 4}, {3, 4}, {4, 5}]),
V4 = vectorclock:from_list([{1, 6}, {2, 3}, {3, 1}, {4, 7}]),
V5 = vectorclock:from_list([{1, 6}, {2, 7}]),
?assertEqual(all_dots_greater(V1, V2), true),
?assertEqual(all_dots_smaller(V2, V1), true),
?assertEqual(all_dots_greater(V1, V3), false),
?assertEqual(gt(V1, V3), true),
?assertEqual(gt(V1, V1), false),
?assertEqual(ge(V1, V4), false),
?assertEqual(le(V1, V4), false),
?assertEqual(eq(V1, V4), false),
?assertEqual(ge(V1, V5), false).
vectorclock_max_test() ->
V1 = vectorclock:from_list([{1, 5}, {2, 4}]),
V2 = vectorclock:from_list([{1, 6}, {2, 3}]),
V3 = vectorclock:from_list([{1, 3}, {3, 2}]),
Expected12 = vectorclock:from_list([{1, 6}, {2, 4}]),
Expected23 = vectorclock:from_list([{1, 6}, {2, 3}, {3, 2}]),
Expected13 = vectorclock:from_list([{1, 5}, {2, 4}, {3, 2}]),
Expected123 = vectorclock:from_list([{1, 6}, {2, 4}, {3, 2}]),
Unexpected123 = vectorclock: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 = vectorclock:from_list([{1, 5}, {2, 4}]),
V2 = vectorclock:from_list([{1, 6}, {2, 3}]),
V3 = vectorclock:from_list([{1, 3}, {3, 2}]),
Expected12 = vectorclock:from_list([{1, 5}, {2, 3}]),
Expected23 = vectorclock:from_list([{1, 3}]),
Expected13 = vectorclock:from_list([{1, 3}]),
Expected123 = vectorclock:from_list([{1, 3}]),
Unexpected123 = vectorclock:from_list([{1, 3}, {2, 3}, {3, 2}]),
?assertEqual(eq(min([V1, V2]), Expected12), true),
?assertEqual(eq(min([V2, V3]), Expected23), true),
?assertEqual(eq(min([V1, V3]), Expected13), true),
?assertEqual(eq(min([V1, V2, V3]), Expected123), true),
?assertEqual(eq(min([V1, V2, V3]), Unexpected123), false).
vectorclock_conc_test() ->
V1 = vectorclock:from_list([{1, 5}, {2, 4}]),
V2 = vectorclock:from_list([{1, 6}, {2, 3}]),
V3 = vectorclock:from_list([{1, 3}, {3, 2}]),
V4 = vectorclock:from_list([{1, 6}, {3, 3}]),
V5 = vectorclock:from_list([{1, 6}]),
?assertEqual(conc(V1, V2), true),
?assertEqual(conc(V2, V3), true),
?assertEqual(conc(V3, V4), false),
?assertEqual(conc(V5, V4), false).
-endif.