Skip to content

Commit cd297fb

Browse files
committed
Add test peer and test peer set modules.
Make PeerSet much nicer UX, test suite should pass more consistently now.
1 parent f4f63da commit cd297fb

17 files changed

+1871
-93
lines changed

apps/merge_raft/src/merge_raft.app.src

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,4 @@
6969
"GitHub" => "https://github.com/WhatsApp/merge_raft"
7070
}},
7171
{pkg_name, "merge_raft"}
72-
]}.
72+
]}.

apps/merge_raft/src/merge_raft.erl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
-module(merge_raft).
44
-compile(warn_missing_spec_all).
5+
-author("[email protected]").
6+
-oncall("whatsapp_clr").
57
-moduledoc """
68
merge_raft behaviour
79
""".
@@ -1283,6 +1285,7 @@ now_ns() ->
12831285
peer_send({_StartNs, Pid}, Msg) ->
12841286
% This required dist_auto_connect
12851287
% Future: can be replaced by callback transports
1288+
% true = net_kernel:connect_node(node(Pid)),
12861289
gen_server:cast(Pid, Msg).
12871290

12881291
-spec last_log_tenure(#state{}) -> tenure_id().

apps/merge_raft/src/merge_raft_kv.erl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
-module(merge_raft_kv).
44
-compile(warn_missing_spec_all).
5+
-author("[email protected]").
6+
-oncall("whatsapp_clr").
57
-moduledoc """
68
a simple key-value store with merge raft
79
""".

apps/merge_raft/src/merge_raft_nodes.erl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
-module(merge_raft_nodes).
44
-compile(warn_missing_spec_all).
5+
-author("[email protected]").
6+
-oncall("whatsapp_clr").
57
-moduledoc """
68
a simple gen_server implementing connect_all feature using merge raft
79
""".
@@ -150,7 +152,8 @@ handle_info({connect, Node, N}, State) ->
150152
{noreply, State};
151153
_ ->
152154
% Send a message to async connect to the node
153-
{undefined, Node} ! connect,
155+
true = net_kernel:connect_node(Node),
156+
% {undefined, Node} ! connect,
154157
{noreply, State#{Node => erlang:send_after((1 bsl N) * 1000, self(), {connect, Node, min(N + 1, 10)})}}
155158
end;
156159
handle_info(Nodes, State) ->
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
%%% % @format
2+
3+
-module(merge_raft_reg).
4+
-compile(warn_missing_spec_all).
5+
-moduledoc """
6+
a simple gen_server implementing pid register using merge raft
7+
""".
8+
9+
-behaviour(merge_raft).
10+
11+
%% OTP supervision
12+
-export([
13+
child_spec/0,
14+
start_link/0,
15+
start/0
16+
]).
17+
18+
%% API functions
19+
-export([
20+
reg/2,
21+
get/1,
22+
resolve/3
23+
]).
24+
25+
%% merge_raft callbacks
26+
-export([
27+
db_init/2,
28+
apply_custom/3,
29+
apply_merge/4,
30+
apply_leave/3,
31+
apply_replace/2,
32+
serialize/1,
33+
reset/2
34+
]).
35+
36+
%% gen_server callbacks
37+
-export([
38+
init/1,
39+
handle_call/3,
40+
handle_cast/2,
41+
handle_info/2
42+
]).
43+
44+
-type server_name() :: merge_raft:server_name().
45+
-type peer() :: merge_raft:peer().
46+
-type members() :: merge_raft:members().
47+
-type commit_metadata() :: merge_raft:commit_metadata().
48+
49+
-type name() :: atom().
50+
51+
-type custom_db() :: ets:table().
52+
-type custom_result() :: ok | boolean().
53+
-type custom_log() :: {reg, name(), pid()} | {unreg, name(), pid()}.
54+
-type custom_db_serialized() :: #{}.
55+
56+
-type state() :: #{name() => pid()}.
57+
58+
-define(RAFT_SERVER, merge_raft_reg_server).
59+
60+
%==============================================================================
61+
% OTP supervision
62+
%==============================================================================
63+
64+
-spec child_spec() -> supervisor:child_spec().
65+
child_spec() ->
66+
#{
67+
id => ?MODULE,
68+
start => {?MODULE, start_link, []},
69+
restart => transient,
70+
shutdown => 1000,
71+
modules => [?MODULE]
72+
}.
73+
74+
-spec start_link() -> gen_server:start_ret().
75+
start_link() ->
76+
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
77+
78+
-spec start() -> gen_server:start_ret().
79+
start() ->
80+
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
81+
82+
%==============================================================================
83+
% API functions
84+
%==============================================================================
85+
86+
-spec reg(name(), pid()) -> boolean().
87+
reg(Name, Pid) ->
88+
gen_server:call(?MODULE, {reg, Name, Pid}).
89+
90+
-spec get(name()) -> pid() | undefined.
91+
get(Name) ->
92+
case ets:lookup(?MODULE, Name) of
93+
[{_Name, Pid}] ->
94+
Pid;
95+
_ ->
96+
undefined
97+
end.
98+
99+
%==============================================================================
100+
% merge_raft callbacks
101+
%==============================================================================
102+
103+
-spec db_init(peer(), server_name()) -> {undefined | commit_metadata(), custom_db()}.
104+
db_init(_Me, _Name) ->
105+
{undefined, ets:new(?MODULE, [set, protected, named_table])}.
106+
107+
-spec apply_custom(commit_metadata(), custom_log(), custom_db()) -> {custom_result(), custom_db()}.
108+
apply_custom(_CommitMetadata, {reg, Name, Pid}, Db) ->
109+
case ets:lookup(Db, Name) of
110+
[{_Name, Pid1}] when Pid1 =:= Pid ->
111+
ok;
112+
[{_Name, Pid1}] ->
113+
ets:insert(Db, {Name, resolve(Name, Pid1, Pid)});
114+
_ ->
115+
ets:insert(Db, {Name, Pid})
116+
end,
117+
{ets:lookup_element(Db, Name, 2) =:= Pid, Db};
118+
apply_custom(_CommitMetadata, {unreg, Name, Pid}, Db) ->
119+
case ets:lookup(Db, Name) of
120+
[{_Name, Pid}] ->
121+
ets:delete(Db, Name);
122+
_ ->
123+
ok
124+
end,
125+
{ok, Db}.
126+
127+
-spec apply_merge(commit_metadata(), members(), custom_db_serialized(), custom_db()) -> custom_db().
128+
apply_merge(_CommitMetadata, _Members, Data, Db) ->
129+
[
130+
case ets:lookup(Db, Name) of
131+
[{_Name, Pid1}] when Pid1 =:= Pid ->
132+
ok;
133+
[{_Name, Pid1}] ->
134+
ets:insert(Db, {Name, resolve(Name, Pid1, Pid)});
135+
_ ->
136+
ets:insert(Db, {Name, Pid})
137+
end
138+
|| Name := Pid <- Data
139+
],
140+
Db.
141+
142+
-spec apply_leave(commit_metadata(), peer(), custom_db()) -> custom_db().
143+
apply_leave(_CommitMetadata, _Peer, Db) ->
144+
% remove names related to the peer
145+
% however we should not remove the names new generation of peer is in members
146+
Db.
147+
148+
-spec apply_replace(custom_db_serialized(), custom_db()) -> custom_db().
149+
apply_replace(Data, Db) ->
150+
% can do incremental update to have less churn
151+
ets:delete_all_objects(Db),
152+
ets:insert(Db, maps:to_list(Data)),
153+
Db.
154+
155+
-spec serialize(custom_db()) -> custom_db_serialized().
156+
serialize(Db) ->
157+
maps:from_list(ets:tab2list(Db)).
158+
159+
-spec reset(peer(), custom_db()) -> custom_db().
160+
reset(_Me, Db) ->
161+
[
162+
ets:delete(Db, Name)
163+
|| {Name, Pid} <- ets:tab2list(Db),
164+
node(Pid) =:= node(),
165+
is_process_alive(Pid)
166+
],
167+
Db.
168+
169+
%==============================================================================
170+
% gen_server callbacks
171+
%==============================================================================
172+
173+
-spec init([]) -> {ok, state()}.
174+
init([]) ->
175+
merge_raft:start_link(#{name => ?RAFT_SERVER, module => ?MODULE}),
176+
{ok, #{}}.
177+
178+
-spec handle_call(dynamic(), gen_server:from(), state()) -> {reply, boolean(), state()}.
179+
handle_call({reg, Name, Pid}, _From, State) ->
180+
case merge_raft:commit_sync(?RAFT_SERVER, {reg, Name, Pid}) of
181+
{ok, true} ->
182+
monitor(process, Pid, [{tag, Name}]),
183+
{reply, true, State};
184+
_ ->
185+
{reply, false, State}
186+
end.
187+
188+
-spec handle_cast(dynamic(), state()) -> no_return().
189+
handle_cast(_Request, _State) ->
190+
error(badarg).
191+
192+
-spec handle_info({name(), reference(), process, pid(), term()}, state()) -> {noreply, state()}.
193+
handle_info({Name, _Mon, process, Pid, _Reason}, State) ->
194+
% should keep retry when failed
195+
% code example only, don't hanlde failures
196+
{ok, ok} = merge_raft:commit_sync(?RAFT_SERVER, {unreg, Name, Pid}),
197+
{noreply, State}.
198+
199+
%==============================================================================
200+
% internal functions
201+
%==============================================================================
202+
203+
-spec resolve(name(), pid(), pid()) -> pid().
204+
resolve(_Name, Pid1, Pid2) when Pid1 < Pid2 ->
205+
exit(Pid2, conflict),
206+
Pid1;
207+
resolve(_Name, Pid1, Pid2) ->
208+
exit(Pid1, conflict),
209+
Pid2.

apps/merge_raft/src/merge_raft_trans.erl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
-module(merge_raft_trans).
44
-compile(warn_missing_spec_all).
5+
-author("[email protected]").
6+
-oncall("whatsapp_clr").
57
-moduledoc """
68
a simple transaction implementation with merge raft
79
""".

apps/merge_raft_test/rebar.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[{<<"proper">>,
2+
{git,"https://github.com/proper-testing/proper.git",
3+
{ref,"db4ac35639e0ce8504ff3f07517dae49412a8674"}},
4+
0}].

apps/merge_raft_test/src/merge_raft_test.app.src

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
{application, merge_raft_test, [
2626
{description, "merge_raft_test: test modules for merge_raft"},
2727
{vsn, "1.0.0"},
28+
{mod, {merge_raft_test_app, []}},
2829
{modules, []},
2930
{registered, []},
3031
%% NOTE: Remember to sync changes to `applications` to

apps/merge_raft_test/src/merge_raft_test.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,4 @@
9696
%%%=============================================================================
9797

9898
-spec dynamic_cast(term()) -> dynamic().
99-
dynamic_cast(X) -> X.
99+
dynamic_cast(X) -> X.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
%%%-----------------------------------------------------------------------------
2+
%%% %CopyrightBegin%
3+
%%%
4+
%%% SPDX-License-Identifier: Apache-2.0
5+
%%%
6+
%%% Copyright (c) Meta Platforms, Inc. and affiliates.
7+
%%% Copyright (c) WhatsApp LLC
8+
%%%
9+
%%% Licensed under the Apache License, Version 2.0 (the "License");
10+
%%% you may not use this file except in compliance with the License.
11+
%%% You may obtain a copy of the License at
12+
%%%
13+
%%% http://www.apache.org/licenses/LICENSE-2.0
14+
%%%
15+
%%% Unless required by applicable law or agreed to in writing, software
16+
%%% distributed under the License is distributed on an "AS IS" BASIS,
17+
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
%%% See the License for the specific language governing permissions and
19+
%%% limitations under the License.
20+
%%%
21+
%%% %CopyrightEnd%
22+
%%%-----------------------------------------------------------------------------
23+
%%% % @format
24+
-module(merge_raft_test_app).
25+
-moduledoc """
26+
""".
27+
-moduledoc #{author => ["Andrew Bennett <[email protected]>"]}.
28+
-moduledoc #{created => "2025-08-26", modified => "2025-08-26"}.
29+
-moduledoc #{copyright => "Meta Platforms, Inc. and affiliates."}.
30+
-compile(warn_missing_spec_all).
31+
-oncall("whatsapp_clr").
32+
33+
-behaviour(application).
34+
35+
%% application callbacks
36+
-export([
37+
start/2,
38+
stop/1
39+
]).
40+
41+
%%%=============================================================================
42+
%%% application callbacks
43+
%%%=============================================================================
44+
45+
-spec start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason} when
46+
StartType :: application:start_type(),
47+
StartArgs :: term(),
48+
Pid :: pid(),
49+
State :: term(),
50+
Reason :: term().
51+
start(_StartType, _StartArgs) ->
52+
{ok, SupPid} = merge_raft_test_sup:start_link(),
53+
{ok, SupPid}.
54+
55+
-spec stop(State) -> Ignored when
56+
State :: term(),
57+
Ignored :: term().
58+
stop(_State) ->
59+
ok.
60+
61+
%%%-----------------------------------------------------------------------------
62+
%%% Internal functions
63+
%%%-----------------------------------------------------------------------------

0 commit comments

Comments
 (0)