Browse Source

Start adding registry with no mnesia sync mechanisms.

Roberto Ostinelli 5 years ago
parent
commit
60d8425bff

+ 1 - 1
LICENSE.md

@@ -1,6 +1,6 @@
 # The MIT License (MIT)
 
-Copyright (c) 2015 Roberto Ostinelli and Neato Robotics, Inc.
+Copyright (c) 2015-2019 Roberto Ostinelli and Neato Robotics, Inc.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 26 - 7
Makefile

@@ -3,25 +3,44 @@ PROJECT_DIR:=$(strip $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))))
 all:
 	@rebar3 compile
 
+compile_test:
+	@rebar3 as test compile
+
 clean:
 	@rebar3 clean
 	@find $(PROJECT_DIR)/. -name "erl_crash\.dump" | xargs rm -f
+	@find $(PROJECT_DIR)/. -name "*\.beam" | xargs rm -f
+	@find $(PROJECT_DIR)/. -name "*\.so" | xargs rm -f
 
-dialyze:
+dialyzer:
 	@rebar3 dialyzer
 
-run:
+run: all
 	@erl -pa `rebar3 path` \
 	-name syn@127.0.0.1 \
 	+K true \
 	-mnesia schema_location ram \
-	-eval 'syn:start(),syn:init().'
+	-eval 'syn:start().'
 
-test: all
+console: all
+	@# 'make console sname=syn1'
+	@erl -pa `rebar3 path` \
+	-name $(sname)@127.0.0.1 \
+	+K true \
+	-mnesia schema_location ram
+
+test: compile_test
+ifdef suite
+	@# 'make test suite=syn_registry_SUITE'
+	ct_run -dir $(PROJECT_DIR)/test -logdir $(PROJECT_DIR)/test/results \
+	-suite $(suite) \
+	-pa `rebar3 as test path`
+else
 	ct_run -dir $(PROJECT_DIR)/test -logdir $(PROJECT_DIR)/test/results \
-	-pa `rebar3 path`
+	-pa `rebar3 as test path`
+endif
 
 travis:
-	@$(PROJECT_DIR)/rebar3 compile
+	@$(PROJECT_DIR)/rebar3 as test compile
 	ct_run -dir $(PROJECT_DIR)/test -logdir $(PROJECT_DIR)/test/results \
-	-pa `$(PROJECT_DIR)/rebar3 path`
+	-pa `$(PROJECT_DIR)/rebar3 as test path`

+ 0 - 499
README.md

@@ -39,502 +39,3 @@ In addition, write speeds were a determining factor in the architecture of Syn.
 
 Therefore, Availability has been chosen over Consistency and Syn is [eventually consistent](http://en.wikipedia.org/wiki/Eventual_consistency).
 
-
-## Install
-
-### Rebar3
-If you're using [rebar3](https://github.com/erlang/rebar3), add `syn` as a dependency in your project's `rebar.config` file:
-
-```erlang
-{syn, {git, "git://github.com/ostinelli/syn.git", {tag, "1.6.3"}}}
-```
-
-Or, if you're using [Hex.pm](https://hex.pm/) as package manager (with the [rebar3_hex](https://github.com/hexpm/rebar3_hex) plugin):
-
-```erlang
-{syn, "1.6.3"}
-```
-
-Then, compile:
-
-```bash
-$ rebar3 compile
-```
-
-
-## Usage
-
-### Setup
-Ensure that your application starts Syn. This can be done by either providing it as a dependency in your `.app` file, or by starting it manually:
-
-```erlang
-syn:start().
-```
-
-Your application will have its own logic on how to connect to the other nodes in the cluster. Once it is connected, ensure that Syn gets initialized (this will set up the underlying mnesia backend):
-
-```erlang
-syn:init().
-```
-
-> Ensure that Syn is initialized **only once** on a node. Even if the node were to disconnect from the cluster and reconnect again, do not re-initialize it. This would disable Syn from being able to handle conflict resolution automatically.
-
-A possible place to initialize Syn is in the `start/2` function in your main application module, something along the lines of:
-
-```erlang
--module(myapp_app).
--behaviour(application).
-
--export([start/2, stop/1]).
-
-start(_StartType, _StartArgs) ->
-    %% connect to nodes
-    connect_nodes(),
-    %% init syn
-    syn:init(),
-    %% start sup
-    myapp_sup:start_link().
-
-connect_nodes() ->
-	%% list of nodes contained in ENV variable `nodes`
-	Nodes = application:get_env(nodes),
-	%% connect to nodes
-	[net_kernel:connect_node(Node) || Node <- Nodes].
-```
-
-Syn is then ready.
-
-> You may prefer to initialize Syn inside of the root supervisor instead. This is particularly true if you are using OTP's `included_applications` feature.
-
-
-### Process Registry
-
-To register a process:
-
-```erlang
-syn:register(Key, Pid) ->
-    syn:register(Key, Pid, undefined).
-```
-
-```erlang
-syn:register(Key, Pid, Meta) -> ok | {error, Error}.
-
-Types:
-	Key = any()
-	Pid = pid()
-	Meta = any()
-	Error = taken | pid_already_registered
-```
-
-| ERROR | DESC
-|-----|-----
-| taken | The Key is already taken by another process.
-| pid_already_registered | The Pid is already registered with another Key.
-
-> You may re-register a process multiple times, for example if you need to update its metadata.
-> When a process gets registered, Syn will automatically monitor it.
-
-Processes can also be registered as `gen_server` names, by usage of via-tuples.
-This way, you can use the gen_server API with these tuples without referring to the Pid directly.
-
-```erlang
-Tuple = {via, syn, <<"your process name">>}.
-gen_server:start_link(Tuple, your_module, []).
-gen_server:call(Tuple, your_message).
-```
-
-To retrieve a Pid from a Key:
-
-```erlang
-syn:find_by_key(Key) -> Pid | undefined.
-
-Types:
-	Key = any()
-	Pid = pid()
-```
-
-To retrieve a Pid from a Key with its metadata:
-
-```erlang
-syn:find_by_key(Key, with_meta) -> {Pid, Meta} | undefined.
-
-Types:
-	Key = any()
-	Pid = pid()
-	Meta = any()
-```
-
-To retrieve a Key from a Pid:
-
-```erlang
-syn:find_by_pid(Pid) -> Key | undefined.
-
-Types:
-	Pid = pid()
-	Key = any()
-```
-
-To retrieve a Key from a Pid with its metadata:
-
-```erlang
-syn:find_by_pid(Pid, with_meta) -> {Key, Meta} | undefined.
-
-Types:
-	Pid = pid()
-	Key = any()
-	Meta = any()
-```
-
-To unregister a previously registered Key:
-
-```erlang
-syn:unregister(Key) -> ok | {error, Error}.
-
-Types:
-	Key = any()
-	Error = undefined
-```
-
-> You don't need to unregister keys of processes that are about to die, since they are monitored by Syn and they will be removed automatically. If you manually unregister a process just before it dies, the callback on process exit (see here below) might not get called.
-
-To retrieve the count of total registered processes running in the cluster:
-
-```erlang
-syn:registry_count() -> non_neg_integer().
-```
-
-To retrieve the count of total registered processes running on a specific node:
-
-```erlang
-syn:registry_count(Node) -> non_neg_integer().
-
-Types:
-	Node = atom()
-```
-
-
-### Process Groups
-
-> There's no need to manually create / delete Process Groups, Syn will take care of managing those for you.
-
-To add a process to a group:
-
-```erlang
-syn:join(Name, Pid) ->
-	syn:join(Name, Pid, undefined).
-```
-
-```erlang
-syn:join(Name, Pid, Meta) -> ok.
-
-Types:
-	Name = any()
-	Pid = pid()
-	meta = any()
-```
-
-> A process can join multiple groups. When a process joins a group, Syn will automatically monitor it.
->
-> A process may join the same group multiple times, for example if you need to update its metadata, though it will still be listed only once in it.
-
-To remove a process from a group:
-
-```erlang
-syn:leave(Name, Pid) -> ok | {error, Error}.
-
-Types:
-	Name = any()
-	Pid = pid()
-	Error = pid_not_in_group
-```
-
-> You don't need to remove processes that are about to die, since they are monitored by Syn and they will be removed automatically from their groups. If you manually remove a process from a group just before it dies, the callback on process exit (see here below) might not get called.
-
-To get a list of the members of a group:
-
-```erlang
-syn:get_members(Name) -> [pid()].
-
-Types:
-	Name = any()
-```
-
-To get a list of the members of a group with their metadata:
-
-```erlang
-syn:get_members(Name, with_meta) ->
-	[{pid(), Meta}].
-
-Types:
-	Name = any()
-	Meta = any()
-```
-
-> The order of member pids in the returned array is guaranteed to be the same on every node, however it is not guaranteed to match the order of joins.
-
-To know if a process is a member of a group:
-
-```erlang
-syn:member(Pid, Name) -> boolean().
-
-Types:
-	Pid = pid()
-	Name = any()
-```
-
-To publish a message to all group members:
-
-```erlang
-syn:publish(Name, Message) -> {ok, RecipientCount}.
-
-Types:
-	Name = any()
-	Message = any()
-	RecipientCount = non_neg_integer()
-```
-
-> `RecipientCount` is the count of the intended recipients.
-
-To call all group members and get their replies:
-
-```erlang
-syn:multi_call(Name, Message) ->
-    syn:multi_call(Name, Message, 5000).
-```
-
-```erlang
-syn:multi_call(Name, Message, Timeout) -> {Replies, BadPids}.
-
-Types:
-	Name = any()
-	Message = any()
-	Timeout = non_neg_integer()
-	Replies = [{MemberPid, Reply}]
-	BadPids = [MemberPid]
-	  MemberPid = pid()
-	  Reply = any()
-```
-
-> Syn will wait up to the value specified in `Timeout` to receive all replies from the members. The members that do not reply in time or that crash before sending a reply will be added to the `BadPids` list.
-
-When this call is issued, all members will receive a tuple in the format:
-
-```erlang
-{syn_multi_call, CallerPid, Message}
-
-Types:
-	CallerPid = pid()
-	Message = any()
-```
-
-To reply, every member must use the method:
-
-```erlang
-syn:multi_call_reply(CallerPid, Reply) -> ok.
-
-Types:
-	CallerPid = pid()
-	Reply = any()
-```
-
-
-To get a list of the local members of a group (running on the node):
-
-```erlang
-syn:get_local_members(Name) -> [pid()].
-
-Types:
-	Name = any()
-```
-
-To get a list of the local members of a group with their metadata:
-
-```erlang
-syn:get_local_members(Name, with_meta) ->
-	[{pid(), Meta}].
-
-Types:
-	Name = any()
-	Meta = any()
-```
-
-> The order of member pids in the returned array is guaranteed to be the same on every node, however it is not guaranteed to match the order of joins.
-
-To publish a message to all local group members:
-
-```erlang
-syn:publish_to_local(Name, Message) -> {ok, RecipientCount}.
-
-Types:
-	Name = any()
-	Message = any()
-	RecipientCount = non_neg_integer()
-```
-
-> `RecipientCount` is the count of the intended recipients.
-
-
-## Options
-Options can be set in the environment variable `syn`. You're probably best off using an application configuration file (in releases, `sys.config`). The list of all available options is:
-
-```erlang
-{syn, [
-    %% define callback function on process exit for registry
-    {registry_process_exit_callback, [module1, function1]},
-
-    %% define callback function on conflicting process (instead of kill)
-    {registry_conflicting_process_callback, [module2, function2]},
-
-    %% define callback function on process exit for groups
-    {process_groups_process_exit_callback, [module3, function3]}
-]}
-```
-These options are explained here below.
-
-### Registry options
-These allow setting the Process Registry options, and are:
-
- * `registry_process_exit_callback`
- * `registry_conflicting_process_callback`
-
-#### Callback on process exit
-The `registry_process_exit_callback` option allows you to specify the `module` and the `function` of the callback that will be triggered when a registered process exits. This callback will be called only on the node where the process was running.
-
-The callback function is defined as:
-```erlang
-CallbackFun = fun(Key, Pid, Meta, Reason) -> any().
-
-Types:
-	Key = any()
-	Pid = pid()
-	Meta = any()
-	Reason = any()
-```
-The `Key`, `Pid` and `Meta` are the ones of the process that exited with `Reason`.
-
-For instance, if you want to print a log when a registered process exited:
-
-```erlang
--module(my_callback).
--export([callback_on_process_exit/4]).
-
-callback_on_process_exit(Key, Pid, Meta, Reason) ->
-	error_logger:info_msg(
-		"Process with Key ~p, Pid ~p and Meta ~p exited with reason ~p~n",
-		[Key, Pid, Meta, Reason]
-	)
-```
-
-Set it in the options:
-```erlang
-{syn, [
-    %% define callback function
-    {registry_process_exit_callback, [my_callback, callback_on_process_exit]}
-]}
-```
-If you don't set this option, no callback will be triggered.
-
-> If a process dies as a consequence of a conflict resolution, the process exit callback will still be called but the Key and Meta values will both be `undefined`.
-
-
-#### Conflict resolution by callback
-In case of race conditions, or during net splits, a specific Key might be registered simultaneously on two different nodes. In this case, the cluster experiences a registry naming conflict.
-
-When this happens, Syn will resolve this Process Registry conflict by choosing a single process. Syn will discard the processes running on the node the conflict is being resolved on, and by default will kill it by sending a `kill` signal with `exit(Pid, kill)`.
-
-If this is not desired, you can set the `registry_conflicting_process_callback` option to instruct Syn to trigger a callback, so that you can perform custom operations (such as a graceful shutdown). In this case, the process will not be killed by Syn, and you'll have to decide what to do with it. This callback will be called only on the node where the process is running.
-
-The callback function is defined as:
-```erlang
-CallbackFun = fun(Key, Pid, Meta) -> any().
-
-Types:
-	Key = any()
-	Pid = pid()
-	Meta = any()
-```
-The `Key`, `Pid` and `Meta` are the ones of the process that is to be discarded.
-
-For instance, if you want to send a `shutdown` message to the discarded process:
-
-```erlang
--module(my_callback).
--export([callback_on_conflicting_process/3]).
-
-callback_on_conflicting_process(_Key, Pid, _Meta) ->
-	Pid ! shutdown
-```
-
-Set it in the options:
-```erlang
-{syn, [
-	%% define callback function
-	{registry_conflicting_process_callback, [my_callback, callback_on_conflicting_process]}
-]}
-```
-
-> Important Note: The conflict resolution method SHOULD be defined in the same way across all nodes of the cluster. Having different conflict resolution options on different nodes can have unexpected results.
-
-### Process Groups options
-These allow setting the Process Groups options, and are:
-
- * `process_groups_process_exit_callback`
-
-#### Callback on process exit
-The `process_groups_process_exit_callback` option allows you to specify the `module` and the `function` of the callback that will be triggered when a member process of a group exits. This callback will be called only on the node where the process was running.
-
-The callback function is defined as:
-```erlang
-CallbackFun = fun(Name, Pid, Meta, Reason) -> any().
-
-Types:
-	Name = any()
-	Pid = pid()
-	Meta = any()
-	Reason = any()
-```
-`Name` is the Process Group that the process with `Pid` and `Meta` that exited with `Reason` was a member of.
-
-For instance, if you want to print a log when a member process of a group exited:
-
-```erlang
--module(my_callback).
--export([callback_on_process_exit/4]).
-
-callback_on_process_exit(Name, Pid, Meta, Reason) ->
-	error_logger:info_msg(
-		"Process with Pid ~p and Meta ~p of Group ~p exited with reason ~p~n",
-		[Pid, Meta, Name, Reason]
-	)
-```
-
-Set it in the options:
-```erlang
-{syn, [
-    %% define callback function
-    {process_groups_process_exit_callback, [my_callback, callback_on_process_exit]}
-]}
-```
-If you don't set this option, no callback will be triggered.
-
-> This callback will be called for every Process Group that the process was a member of.
-
-
-## Internals
-Under the hood, Syn performs dirty reads and writes into distributed in-memory Mnesia tables, replicated across all the nodes of the cluster.
-
-To automatically handle conflict resolution, Syn implements a specialized and simplified version of the mechanisms used in Ulf Wiger's [unsplit](https://github.com/uwiger/unsplit) framework.
-
-
-## Contributing
-So you want to contribute? That's great! Please follow the guidelines below. It will make it easier to get merged in.
-
-Before implementing a new feature, please submit a ticket to discuss what you intend to do. Your feature might already be in the works, or an alternative implementation might have already been discussed.
-
-Do not commit to master in your fork. Provide a clean branch without merge commits. Every pull request should have its own topic branch. In this way, every additional adjustments to the original pull request might be done easily, and squashed with `git rebase -i`. The updated branch will be visible in the same pull request, so there will be no need to open new pull requests when there are changes to be applied.
-
-Ensure that proper testing is included. To run Syn tests you simply have to be in the project's root directory and run:
-
-```bash
-$ make test
-```

+ 1 - 3
rebar.config

@@ -1,3 +1 @@
-{erl_opts, [
-    {i, "include"}
-]}.
+

+ 2 - 2
rebar.lock

@@ -1,10 +1,10 @@
 {application, syn,
     [
         {description, "A global Process Registry and Process Group manager."},
-        {vsn, "1.6.3"},
+        {vsn, "2.0.0"},
         {registered, [
+            syn_backbone,
             syn_consistency,
-            syn_groups,
             syn_registry,
             syn_sup
         ]},

+ 18 - 143
src/syn.erl

@@ -3,7 +3,7 @@
 %%
 %% The MIT License (MIT)
 %%
-%% Copyright (c) 2015 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
+%% Copyright (c) 2015-2019 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
 %%
 %% Permission is hereby granted, free of charge, to any person obtaining a copy
 %% of this software and associated documentation files (the "Software"), to deal
@@ -27,76 +27,42 @@
 
 %% API
 -export([start/0, stop/0]).
--export([init/0]).
-
-%% registry
+-export([whereis/1, whereis/2]).
 -export([register/2, register/3]).
 -export([unregister/1]).
--export([find_by_key/1, find_by_key/2]).
--export([find_by_pid/1, find_by_pid/2]).
 -export([registry_count/0, registry_count/1]).
 
-%% registry for gen_server name via-tuples
--export([register_name/2]).
--export([unregister_name/1]).
--export([whereis_name/1]).
--export([send/2]).
-
-%% groups
--export([join/2, join/3]).
--export([leave/2]).
--export([member/2]).
--export([get_members/1, get_members/2]).
--export([get_local_members/1, get_local_members/2]).
--export([publish/2]).
--export([publish_to_local/2]).
--export([multi_call/2, multi_call/3]).
--export([multi_call_reply/2]).
-
 %% ===================================================================
 %% API
 %% ===================================================================
 -spec start() -> ok.
 start() ->
-    ok = start_application(mnesia),
-    ok = start_application(syn),
+    {ok, _} = application:ensure_all_started(syn),
     ok.
 
 -spec stop() -> ok.
 stop() ->
     ok = application:stop(syn).
 
--spec init() -> ok.
-init() ->
-    ok = syn_backbone:initdb().
-
--spec register(Key :: any(), Pid :: pid()) -> ok | {error, taken | pid_already_registered}.
-register(Key, Pid) ->
-    syn_registry:register(Key, Pid).
+-spec whereis(Name :: term()) -> pid() | undefined.
+whereis(Name) ->
+    syn_registry:whereis(Name).
 
--spec register(Key :: any(), Pid :: pid(), Meta :: any()) -> ok | {error, taken | pid_already_registered}.
-register(Key, Pid, Meta) ->
-    syn_registry:register(Key, Pid, Meta).
+-spec whereis(Name :: term(), with_meta) -> {pid(), Meta :: term()} | undefined.
+whereis(Name, with_meta) ->
+    syn_registry:whereis(Name, with_meta).
 
--spec unregister(Key :: any()) -> ok | {error, undefined}.
-unregister(Key) ->
-    syn_registry:unregister(Key).
+-spec register(Name :: term(), Pid :: pid()) -> ok | {error, Reason :: term()}.
+register(Name, Pid) ->
+    syn_registry:register(Name, Pid).
 
--spec find_by_key(Key :: any()) -> pid() | undefined.
-find_by_key(Key) ->
-    syn_registry:find_by_key(Key).
+-spec register(Name :: term(), Pid :: pid(), Meta :: term()) -> ok | {error, Reason :: term()}.
+register(Name, Pid, Meta) ->
+    syn_registry:register(Name, Pid, Meta).
 
--spec find_by_key(Key :: any(), with_meta) -> {pid(), Meta :: any()} | undefined.
-find_by_key(Key, with_meta) ->
-    syn_registry:find_by_key(Key, with_meta).
-
--spec find_by_pid(Pid :: pid()) -> Key :: any() | undefined.
-find_by_pid(Pid) ->
-    syn_registry:find_by_pid(Pid).
-
--spec find_by_pid(Pid :: pid(), with_meta) -> {Key :: any(), Meta :: any()} | undefined.
-find_by_pid(Pid, with_meta) ->
-    syn_registry:find_by_pid(Pid, with_meta).
+-spec unregister(Name :: term()) -> ok | {error, Reason :: term()}.
+unregister(Name) ->
+    syn_registry:unregister(Name).
 
 -spec registry_count() -> non_neg_integer().
 registry_count() ->
@@ -105,94 +71,3 @@ registry_count() ->
 -spec registry_count(Node :: atom()) -> non_neg_integer().
 registry_count(Node) ->
     syn_registry:count(Node).
-
--spec register_name(Name :: term(), Pid :: pid()) -> 'yes' | 'no'.
-register_name(Name, Pid) when is_pid(Pid) ->
-    case syn_registry:register(Name, Pid) of
-        ok -> yes;
-        {error, _} -> no;
-        _ -> no
-    end.
-
--spec unregister_name(Name :: term()) -> _.
-unregister_name(Name) ->
-    case syn_registry:unregister(Name) of
-        ok -> Name;
-        {error, _} -> nil;
-        _ -> nil
-    end.
-
--spec whereis_name(Name :: term()) -> pid() | 'undefined'.
-whereis_name(Name) -> syn_registry:find_by_key(Name).
-
--spec send(Name :: term(), Message :: term()) -> pid().
-send(Name, Message) ->
-    case whereis_name(Name) of
-        undefined -> {badarg, {Name, Message}};
-        Pid -> Pid ! Message, Pid
-    end.
-
--spec join(Name :: any(), Pid :: pid()) -> ok.
-join(Name, Pid) ->
-    syn_groups:join(Name, Pid).
-
--spec join(Name :: any(), Pid :: pid(), Meta :: any()) -> ok.
-join(Name, Pid, Meta) ->
-    syn_groups:join(Name, Pid, Meta).
-
--spec leave(Name :: any(), Pid :: pid()) -> ok | {error, pid_not_in_group}.
-leave(Name, Pid) ->
-    syn_groups:leave(Name, Pid).
-
--spec member(Pid :: pid(), Name :: any()) -> boolean().
-member(Pid, Name) ->
-    syn_groups:member(Pid, Name).
-
--spec get_members(Name :: any()) -> [pid()].
-get_members(Name) ->
-    syn_groups:get_members(Name).
-
--spec get_members(Name :: any(), with_meta) -> [{pid(), Meta :: any()}].
-get_members(Name, with_meta) ->
-    syn_groups:get_members(Name, with_meta).
-
--spec get_local_members(Name :: any()) -> [pid()].
-get_local_members(Name) ->
-    syn_groups:get_local_members(Name).
-
--spec get_local_members(Name :: any(), with_meta) -> [{pid(), Meta :: any()}].
-get_local_members(Name, with_meta) ->
-    syn_groups:get_local_members(Name, with_meta).
-
--spec publish(Name :: any(), Message :: any()) -> {ok, RecipientCount :: non_neg_integer()}.
-publish(Name, Message) ->
-    syn_groups:publish(Name, Message).
-
--spec publish_to_local(Name :: any(), Message :: any()) -> {ok, RecipientCount :: non_neg_integer()}.
-publish_to_local(Name, Message) ->
-    syn_groups:publish_to_local(Name, Message).
-
--spec multi_call(Name :: any(), Message :: any()) ->
-    {[{pid(), Reply :: any()}], [BadPid :: pid()]}.
-multi_call(Name, Message) ->
-    syn_groups:multi_call(Name, Message).
-
--spec multi_call(Name :: any(), Message :: any(), Timeout :: non_neg_integer()) ->
-    {[{pid(), Reply :: any()}], [BadPid :: pid()]}.
-multi_call(Name, Message, Timeout) ->
-    syn_groups:multi_call(Name, Message, Timeout).
-
--spec multi_call_reply(CallerPid :: pid(), Reply :: any()) -> {syn_multi_call_reply, pid(), Reply :: any()}.
-multi_call_reply(CallerPid, Reply) ->
-    syn_groups:multi_call_reply(CallerPid, Reply).
-
-%% ===================================================================
-%% Internal
-%% ===================================================================
--spec start_application(atom()) -> ok | {error, any()}.
-start_application(Application) ->
-    case application:start(Application) of
-        ok -> ok;
-        {error, {already_started, Application}} -> ok;
-        Error -> Error
-    end.

+ 1 - 3
src/syn_app.erl

@@ -3,7 +3,7 @@
 %%
 %% The MIT License (MIT)
 %%
-%% Copyright (c) 2015 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
+%% Copyright (c) 2015-2019 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
 %%
 %% Permission is hereby granted, free of charge, to any person obtaining a copy
 %% of this software and associated documentation files (the "Software"), to deal
@@ -29,7 +29,6 @@
 %% API
 -export([start/2, stop/1]).
 
-
 %% ===================================================================
 %% API
 %% ===================================================================
@@ -38,7 +37,6 @@
     StartArgs :: any()
 ) -> {ok, pid()} | {ok, pid(), State :: any()} | {error, any()}.
 start(_StartType, _StartArgs) ->
-    %% start sup
     syn_sup:start_link().
 
 -spec stop(State :: any()) -> ok.

+ 118 - 54
src/syn_backbone.erl

@@ -3,7 +3,7 @@
 %%
 %% The MIT License (MIT)
 %%
-%% Copyright (c) 2015 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
+%% Copyright (c) 2015-2019 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
 %%
 %% Permission is hereby granted, free of charge, to any person obtaining a copy
 %% of this software and associated documentation files (the "Software"), to deal
@@ -24,74 +24,138 @@
 %% THE SOFTWARE.
 %% ==========================================================================================================
 -module(syn_backbone).
+-behaviour(gen_server).
 
 %% API
--export([initdb/0]).
+-export([start_link/0]).
 
-%% include
--include("syn.hrl").
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
 
+%% records
+-record(state, {}).
+
+%% includes
+-include("syn_records.hrl").
 
 %% ===================================================================
 %% API
 %% ===================================================================
+-spec start_link() -> {ok, pid()} | {error, any()}.
+start_link() ->
+    Options = [],
+    gen_server:start_link({local, ?MODULE}, ?MODULE, [], Options).
+
+%% ===================================================================
+%% Callbacks
+%% ===================================================================
+
+%% ----------------------------------------------------------------------------------------------------------
+%% Init
+%% ----------------------------------------------------------------------------------------------------------
+-spec init([]) ->
+    {ok, #state{}} |
+    {ok, #state{}, Timeout :: non_neg_integer()} |
+    ignore |
+    {stop, Reason :: any()}.
+init([]) ->
+    error_logger:info_msg("Creating syn tables"),
+    case create_ram_tables() of
+        ok -> {ok, #state{}};
+        Other -> Other
+    end.
+
+%% ----------------------------------------------------------------------------------------------------------
+%% Call messages
+%% ----------------------------------------------------------------------------------------------------------
+-spec handle_call(Request :: any(), From :: any(), #state{}) ->
+    {reply, Reply :: any(), #state{}} |
+    {reply, Reply :: any(), #state{}, Timeout :: non_neg_integer()} |
+    {noreply, #state{}} |
+    {noreply, #state{}, Timeout :: non_neg_integer()} |
+    {stop, Reason :: any(), Reply :: any(), #state{}} |
+    {stop, Reason :: any(), #state{}}.
+
+handle_call(Request, From, State) ->
+    error_logger:warning_msg("Received from ~p an unknown call message: ~p~n", [Request, From]),
+    {reply, undefined, State}.
 
--spec initdb() -> ok | {error, any()}.
-initdb() ->
-    %% ensure all nodes are added
-    ClusterNodes = [node() | nodes()],
-    {ok, _} = mnesia:change_config(extra_db_nodes, ClusterNodes),
-    %% create tables
-    create_table(syn_registry_table, [
+%% ----------------------------------------------------------------------------------------------------------
+%% Cast messages
+%% ----------------------------------------------------------------------------------------------------------
+-spec handle_cast(Msg :: any(), #state{}) ->
+    {noreply, #state{}} |
+    {noreply, #state{}, Timeout :: non_neg_integer()} |
+    {stop, Reason :: any(), #state{}}.
+
+handle_cast(Msg, State) ->
+    error_logger:warning_msg("Received an unknown cast message: ~p~n", [Msg]),
+    {noreply, State}.
+
+%% ----------------------------------------------------------------------------------------------------------
+%% All non Call / Cast messages
+%% ----------------------------------------------------------------------------------------------------------
+-spec handle_info(Info :: any(), #state{}) ->
+    {noreply, #state{}} |
+    {noreply, #state{}, Timeout :: non_neg_integer()} |
+    {stop, Reason :: any(), #state{}}.
+
+handle_info(Info, State) ->
+    error_logger:warning_msg("Received an unknown info message: ~p~n", [Info]),
+    {noreply, State}.
+
+%% ----------------------------------------------------------------------------------------------------------
+%% Terminate
+%% ----------------------------------------------------------------------------------------------------------
+-spec terminate(Reason :: any(), #state{}) -> terminated.
+terminate(Reason, _State) ->
+    error_logger:info_msg("Terminating with reason: ~p~n", [Reason]),
+    error_logger:info_msg("Removing syn tables~n", [Reason]),
+    delete_ram_tables(),
+    terminated.
+
+%% ----------------------------------------------------------------------------------------------------------
+%% Convert process state when code is changed.
+%% ----------------------------------------------------------------------------------------------------------
+-spec code_change(OldVsn :: any(), #state{}, Extra :: any()) -> {ok, #state{}}.
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%% ===================================================================
+%% Internal
+%% ===================================================================
+-spec create_ram_tables() -> ok | {error, Reason :: term()}.
+create_ram_tables() ->
+    case create_registry_table() of
+        {atomic, ok} ->
+            case create_groups_table() of
+                {atomic, ok} -> ok;
+                {aborted, Reason} -> {error, {could_not_create_syn_groups_table, Reason}}
+            end;
+        {aborted, Reason} ->
+            {error, {could_not_create_syn_registry_table, Reason}}
+    end.
+
+-spec create_registry_table() -> {atomic, ok} | {aborted, Reason :: term()}.
+create_registry_table() ->
+    mnesia:create_table(syn_registry_table, [
         {type, set},
-        {ram_copies, ClusterNodes},
         {attributes, record_info(fields, syn_registry_table)},
         {index, [#syn_registry_table.pid]},
-        {storage_properties, [{ets, [{read_concurrency, true}]}]}
-    ]),
-    create_table(syn_groups_table, [
+        {storage_properties, [{ets, [{read_concurrency, true}, {write_concurrency, true}]}]}
+    ]).
+
+-spec create_groups_table() -> {atomic, ok} | {aborted, Reason :: term()}.
+create_groups_table() ->
+    mnesia:create_table(syn_groups_table, [
         {type, bag},
-        {ram_copies, ClusterNodes},
         {attributes, record_info(fields, syn_groups_table)},
         {index, [#syn_groups_table.pid]},
         {storage_properties, [{ets, [{read_concurrency, true}]}]}
     ]).
 
-create_table(TableName, Options) ->
-    CurrentNode = node(),
-    %% ensure table exists
-    case mnesia:create_table(TableName, Options) of
-        {atomic, ok} ->
-            error_logger:info_msg("~p was successfully created~n", [TableName]),
-            ok;
-        {aborted, {already_exists, TableName}} ->
-            %% table already exists, try to add current node as copy
-            add_table_copy_to_current_node(TableName);
-        {aborted, {already_exists, TableName, CurrentNode}} ->
-            %% table already exists, try to add current node as copy
-            add_table_copy_to_current_node(TableName);
-        Other ->
-            error_logger:error_msg("Error while creating ~p: ~p~n", [TableName, Other]),
-            {error, Other}
-    end.
-
--spec add_table_copy_to_current_node(TableName :: atom()) -> ok | {error, any()}.
-add_table_copy_to_current_node(TableName) ->
-    CurrentNode = node(),
-    %% wait for table
-    mnesia:wait_for_tables([TableName], 10000),
-    %% add copy
-    case mnesia:add_table_copy(TableName, CurrentNode, ram_copies) of
-        {atomic, ok} ->
-            error_logger:info_msg("Copy of ~p was successfully added to current node~n", [TableName]),
-            ok;
-        {aborted, {already_exists, TableName}} ->
-            error_logger:info_msg("Copy of ~p is already added to current node~n", [TableName]),
-            ok;
-        {aborted, {already_exists, TableName, CurrentNode}} ->
-            error_logger:info_msg("Copy of ~p is already added to current node~n", [TableName]),
-            ok;
-        {aborted, Reason} ->
-            error_logger:error_msg("Error while creating copy of ~p: ~p~n", [TableName, Reason]),
-            {error, Reason}
-    end.
+-spec delete_ram_tables() -> ok.
+delete_ram_tables() ->
+    mnesia:delete_table(syn_registry_table),
+    mnesia:delete_table(syn_groups_table),
+    ok.

+ 65 - 250
src/syn_consistency.erl

@@ -3,10 +3,7 @@
 %%
 %% The MIT License (MIT)
 %%
-%% Copyright (c) 2015 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
-%%
-%% Portions of code from Ulf Wiger's unsplit server module:
-%% <https://github.com/uwiger/unsplit/blob/master/src/unsplit_server.erl>
+%% Copyright (c) 2015-2019 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
 %%
 %% Permission is hereby granted, free of charge, to any person obtaining a copy
 %% of this software and associated documentation files (the "Software"), to deal
@@ -31,25 +28,16 @@
 
 %% API
 -export([start_link/0]).
+-export([resume_local_syn_registry/0]).
 
 %% gen_server callbacks
 -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
 
-%% internal
--export([get_registry_processes_info_of_node/1]).
--export([write_registry_processes_info_to_node/2]).
--export([get_groups_processes_info_of_node/1]).
--export([write_groups_processes_info_to_node/2]).
-
 %% records
--record(state, {
-    registry_conflicting_process_callback_module = undefined :: atom(),
-    registry_conflicting_process_callback_function = undefined :: atom()
-}).
-
-%% include
--include("syn.hrl").
+-record(state, {}).
 
+%% includes
+-include("syn_records.hrl").
 
 %% ===================================================================
 %% API
@@ -59,6 +47,11 @@ start_link() ->
     Options = [],
     gen_server:start_link({local, ?MODULE}, ?MODULE, [], Options).
 
+-spec resume_local_syn_registry() -> ok.
+resume_local_syn_registry() ->
+    %% resume processes able to modify mnesia tables
+    sys:resume(syn_registry).
+
 %% ===================================================================
 %% Callbacks
 %% ===================================================================
@@ -72,18 +65,15 @@ start_link() ->
     ignore |
     {stop, Reason :: any()}.
 init([]) ->
-    %% monitor mnesia events
-    mnesia:subscribe(system),
-    %% get options
-    {ok, [RegistryConflictingProcessCallbackModule, RegistryConflictingProcessCallbackFunction]} = syn_utils:get_env_value(
-        registry_conflicting_process_callback,
-        [undefined, undefined]
-    ),
-    %% build state
-    {ok, #state{
-        registry_conflicting_process_callback_module = RegistryConflictingProcessCallbackModule,
-        registry_conflicting_process_callback_function = RegistryConflictingProcessCallbackFunction
-    }}.
+    %% monitor nodes
+    ok = net_kernel:monitor_nodes(true),
+    %% wait for table
+    case mnesia:wait_for_tables([syn_registry_table], 10000) of
+        ok ->
+            {ok, #state{}};
+        Reason ->
+            {stop, {error_waiting_for_process_registry_table, Reason}}
+    end.
 
 %% ----------------------------------------------------------------------------------------------------------
 %% Call messages
@@ -120,39 +110,61 @@ handle_cast(Msg, State) ->
     {noreply, #state{}, Timeout :: non_neg_integer()} |
     {stop, Reason :: any(), #state{}}.
 
-handle_info({mnesia_system_event, {inconsistent_database, Context, RemoteNode}}, State) ->
-    error_logger:error_msg("MNESIA signalled an inconsistent database on node ~p for remote node ~p with context: ~p, initiating automerge~n", [node(), RemoteNode, Context]),
-    automerge(RemoteNode),
-    {noreply, State};
-
-handle_info({mnesia_system_event, {mnesia_down, RemoteNode}}, State) when RemoteNode =/= node() ->
-    error_logger:error_msg("Received a MNESIA down event, removing on node ~p all pids of node ~p~n", [node(), RemoteNode]),
-    delete_registry_pids_of_disconnected_node(RemoteNode),
-    delete_groups_pids_of_disconnected_node(RemoteNode),
+handle_info({nodeup, RemoteNode}, State) ->
+    error_logger:info_msg("Node ~p has joined the cluster of local node ~p~n", [RemoteNode, node()]),
+    global:trans({{?MODULE, auto_merge_node_up}, self()},
+        fun() ->
+            error_logger:info_msg("Merge: ----> Initiating on ~p for remote node ~p~n", [node(), RemoteNode]),
+            %% request remote node process info & suspend remote registry
+            RegistryTuples = rpc:call(RemoteNode, syn_registry, get_local_registry_tuples_and_suspend, [node()]),
+            sync_registry_tuples(RemoteNode, RegistryTuples),
+            error_logger:error_msg("Merge: <---- Done on ~p for remote node ~p~n", [node(), RemoteNode])
+        end
+    ),
+    %% resume remote processes able to modify tables
+    ok = rpc:call(RemoteNode, sys, resume, [syn_registry]),
+    %% resume
     {noreply, State};
 
-handle_info({mnesia_system_event, _MnesiaEvent}, State) ->
-    %% ignore mnesia event
+handle_info({nodedown, RemoteNode}, State) ->
+    error_logger:warning_msg("Node ~p has left the cluster of local node ~p~n", [RemoteNode, node()]),
     {noreply, State};
 
-handle_info({handle_purged_registry_double_processes, DoubleRegistryProcessesInfo}, #state{
-    registry_conflicting_process_callback_module = RegistryConflictingProcessCallbackModule,
-    registry_conflicting_process_callback_function = RegistryConflictingProcessCallbackFunction
-} = State) ->
-    error_logger:warning_msg("About to purge double processes after netsplit~n"),
-    handle_purged_registry_double_processes(RegistryConflictingProcessCallbackModule, RegistryConflictingProcessCallbackFunction, DoubleRegistryProcessesInfo),
-    {noreply, State};
 
 handle_info(Info, State) ->
     error_logger:warning_msg("Received an unknown info message: ~p~n", [Info]),
     {noreply, State}.
 
+sync_registry_tuples(RemoteNode, RegistryTuples) ->
+    %% ensure that registry doesn't have any joining node's entries
+    purge_registry_entries_for_node(RemoteNode),
+    %% loop
+    F = fun({Name, RemotePid, _RemoteNode, RemoteMeta}) ->
+        %% check if same name is registered
+        case syn_registry:find_process_entry_by_name(Name) of
+            undefined ->
+                %% no conflict
+                ok;
+            Entry ->
+                error_logger:warning_msg(
+                    "Conflicting name process found for: ~p, processes are ~p, ~p, killing local~n",
+                    [Name, Entry#syn_registry_table.pid, RemotePid]
+                ),
+                %% kill the local one
+                exit(Entry#syn_registry_table.pid, kill)
+        end,
+        %% enqueue registration (to be done on syn_registry for monitor)
+        syn_registry:sync_register(Name, RemotePid, RemoteMeta)
+    end,
+    %% add to table
+    lists:foreach(F, RegistryTuples).
+
 %% ----------------------------------------------------------------------------------------------------------
 %% Terminate
 %% ----------------------------------------------------------------------------------------------------------
 -spec terminate(Reason :: any(), #state{}) -> terminated.
 terminate(Reason, _State) ->
-    error_logger:info_msg("Terminating syn consistency with reason: ~p~n", [Reason]),
+    error_logger:info_msg("Terminating with reason: ~p~n", [Reason]),
     terminated.
 
 %% ----------------------------------------------------------------------------------------------------------
@@ -162,213 +174,16 @@ terminate(Reason, _State) ->
 code_change(_OldVsn, State, _Extra) ->
     {ok, State}.
 
-
 %% ===================================================================
 %% Internal
 %% ===================================================================
--spec delete_registry_pids_of_disconnected_node(RemoteNode :: atom()) -> ok.
-delete_registry_pids_of_disconnected_node(RemoteNode) ->
+-spec purge_registry_entries_for_node(Node :: atom()) -> ok.
+purge_registry_entries_for_node(Node) ->
     %% build match specs
-    MatchHead = #syn_registry_table{key = '$1', node = '$2', _ = '_'},
-    Guard = {'=:=', '$2', RemoteNode},
+    MatchHead = #syn_registry_table{name = '$1', node = '$2', _ = '_'},
+    Guard = {'=:=', '$2', Node},
     IdFormat = '$1',
     %% delete
-    DelF = fun(Id) -> mnesia:dirty_delete({syn_registry_table, Id}) end,
     NodePids = mnesia:dirty_select(syn_registry_table, [{MatchHead, [Guard], [IdFormat]}]),
+    DelF = fun(Id) -> mnesia:dirty_delete({syn_registry_table, Id}) end,
     lists:foreach(DelF, NodePids).
-
--spec delete_groups_pids_of_disconnected_node(RemoteNode :: atom()) -> ok.
-delete_groups_pids_of_disconnected_node(RemoteNode) ->
-    %% build match specs
-    Pattern = #syn_groups_table{node = RemoteNode, _ = '_'},
-    ObjectsToDelete = mnesia:dirty_match_object(syn_groups_table, Pattern),
-    %% delete
-    DelF = fun(Record) -> mnesia:dirty_delete_object(syn_groups_table, Record) end,
-    lists:foreach(DelF, ObjectsToDelete).
-
--spec automerge(RemoteNode :: atom()) -> ok.
-automerge(RemoteNode) ->
-    %% suspend processes able to modify mnesia tables
-    sys:suspend(syn_registry),
-    sys:suspend(syn_groups),
-    %% resolve conflicts
-    global:trans({{?MODULE, automerge}, self()},
-        fun() ->
-            error_logger:warning_msg("AUTOMERGE starting on node ~p for remote node ~p (global lock is set)~n", [node(), RemoteNode]),
-            check_stitch(RemoteNode),
-            error_logger:warning_msg("AUTOMERGE done (global lock about to be unset)~n")
-        end
-    ),
-    %% resume processes able to modify mnesia tables
-    sys:resume(syn_registry),
-    sys:resume(syn_groups).
-
--spec check_stitch(RemoteNode :: atom()) -> ok.
-check_stitch(RemoteNode) ->
-    case catch lists:member(RemoteNode, mnesia:system_info(running_db_nodes)) of
-        true ->
-            error_logger:warning_msg("Remote node ~p is already stitched.~n", [RemoteNode]),
-            ok;
-        false ->
-            catch stitch(RemoteNode),
-            ok;
-        Error ->
-            error_logger:error_msg("Could not check if node is stiched: ~p~n", [Error]),
-            ok
-    end.
-
--spec stitch(RemoteNode :: atom()) -> {ok, any()} | {error, any()}.
-stitch(RemoteNode) ->
-    mnesia_controller:connect_nodes(
-        [RemoteNode],
-        fun(MergeF) ->
-            catch case MergeF([syn_registry_table, syn_groups_table]) of
-                {merged, _, _} = Res ->
-                    stitch_registry_tab(RemoteNode),
-                    stitch_group_tab(RemoteNode),
-                    Res;
-                Other ->
-                    Other
-            end
-        end).
-
--spec stitch_registry_tab(RemoteNode :: atom()) -> ok.
-stitch_registry_tab(RemoteNode) ->
-    %% get remote processes info
-    RemoteRegistryProcessesInfo = rpc:call(RemoteNode, ?MODULE, get_registry_processes_info_of_node, [RemoteNode]),
-    %% get local processes info
-    LocalRegistryProcessesInfo = get_registry_processes_info_of_node(node()),
-    %% purge doubles (if any)
-    {LocalRegistryProcessesInfo1, RemoteRegistryProcessesInfo1} = purge_registry_double_processes_from_local_mnesia(
-        LocalRegistryProcessesInfo,
-        RemoteRegistryProcessesInfo
-    ),
-    %% write
-    write_remote_registry_processes_to_local(RemoteNode, RemoteRegistryProcessesInfo1),
-    write_local_registry_processes_to_remote(RemoteNode, LocalRegistryProcessesInfo1).
-
--spec purge_registry_double_processes_from_local_mnesia(
-    LocalRegistryProcessesInfo :: list(),
-    RemoteRegistryProcessesInfo :: list()
-) ->
-    {LocalRegistryProcessesInfo :: list(), RemoteRegistryProcessesInfo :: list()}.
-purge_registry_double_processes_from_local_mnesia(LocalRegistryProcessesInfo, RemoteRegistryProcessesInfo) ->
-    %% create ETS table
-    Tab = ets:new(syn_automerge_doubles_table, [set]),
-
-    %% insert local processes info
-    ets:insert(Tab, LocalRegistryProcessesInfo),
-
-    %% find doubles
-    F = fun({Key, _RemoteProcessPid, _RemoteProcessMeta}, Acc) ->
-        case ets:lookup(Tab, Key) of
-            [] -> Acc;
-            [{Key, LocalProcessPid, LocalProcessMeta}] ->
-                %% found a double process, remove it from local mnesia table
-                mnesia:dirty_delete(syn_registry_table, Key),
-                %% remove it from ETS
-                ets:delete(Tab, Key),
-                %% add it to acc
-                [{Key, LocalProcessPid, LocalProcessMeta} | Acc]
-        end
-    end,
-    DoubleRegistryProcessesInfo = lists:foldl(F, [], RemoteRegistryProcessesInfo),
-
-    %% send to syn_consistency gen_server to handle double processes once merging is done
-    ?MODULE ! {handle_purged_registry_double_processes, DoubleRegistryProcessesInfo},
-
-    %% compute local processes without doubles
-    LocalRegistryProcessesInfo1 = ets:tab2list(Tab),
-    %% delete ETS table
-    ets:delete(Tab),
-    %% return
-    {LocalRegistryProcessesInfo1, RemoteRegistryProcessesInfo}.
-
--spec write_remote_registry_processes_to_local(RemoteNode :: atom(), RemoteRegistryProcessesInfo :: list()) -> ok.
-write_remote_registry_processes_to_local(RemoteNode, RemoteRegistryProcessesInfo) ->
-    write_registry_processes_info_to_node(RemoteNode, RemoteRegistryProcessesInfo).
-
--spec write_local_registry_processes_to_remote(RemoteNode :: atom(), LocalRegistryProcessesInfo :: list()) -> ok.
-write_local_registry_processes_to_remote(RemoteNode, LocalRegistryProcessesInfo) ->
-    ok = rpc:call(RemoteNode, ?MODULE, write_registry_processes_info_to_node, [node(), LocalRegistryProcessesInfo]).
-
--spec get_registry_processes_info_of_node(Node :: atom()) -> list().
-get_registry_processes_info_of_node(Node) ->
-    %% build match specs
-    MatchHead = #syn_registry_table{key = '$1', pid = '$2', node = '$3', meta = '$4'},
-    Guard = {'=:=', '$3', Node},
-    ProcessInfoFormat = {{'$1', '$2', '$4'}},
-    %% select
-    mnesia:dirty_select(syn_registry_table, [{MatchHead, [Guard], [ProcessInfoFormat]}]).
-
--spec write_registry_processes_info_to_node(Node :: atom(), RegistryProcessesInfo :: list()) -> ok.
-write_registry_processes_info_to_node(Node, RegistryProcessesInfo) ->
-    FWrite = fun({Key, ProcessPid, ProcessMeta}) ->
-        mnesia:dirty_write(#syn_registry_table{
-            key = Key,
-            pid = ProcessPid,
-            node = Node,
-            meta = ProcessMeta
-        })
-    end,
-    lists:foreach(FWrite, RegistryProcessesInfo).
-
--spec stitch_group_tab(RemoteNode :: atom()) -> ok.
-stitch_group_tab(RemoteNode) ->
-    %% get remote processes info
-    RemoteGroupsRegistryProcessesInfo = rpc:call(RemoteNode, ?MODULE, get_groups_processes_info_of_node, [RemoteNode]),
-    %% get local processes info
-    LocalGroupsRegistryProcessesInfo = get_groups_processes_info_of_node(node()),
-    %% write
-    write_remote_groups_processes_info_to_local(RemoteNode, RemoteGroupsRegistryProcessesInfo),
-    write_local_groups_processes_info_to_remote(RemoteNode, LocalGroupsRegistryProcessesInfo).
-
--spec get_groups_processes_info_of_node(Node :: atom()) -> list().
-get_groups_processes_info_of_node(Node) ->
-    %% build match specs
-    MatchHead = #syn_groups_table{name = '$1', pid = '$2', node = '$3'},
-    Guard = {'=:=', '$3', Node},
-    GroupInfoFormat = {{'$1', '$2'}},
-    %% select
-    mnesia:dirty_select(syn_groups_table, [{MatchHead, [Guard], [GroupInfoFormat]}]).
-
--spec write_remote_groups_processes_info_to_local(RemoteNode :: atom(), RemoteGroupsRegistryProcessesInfo :: list()) -> ok.
-write_remote_groups_processes_info_to_local(RemoteNode, RemoteGroupsRegistryProcessesInfo) ->
-    write_groups_processes_info_to_node(RemoteNode, RemoteGroupsRegistryProcessesInfo).
-
--spec write_local_groups_processes_info_to_remote(RemoteNode :: atom(), LocalGroupsRegistryProcessesInfo :: list()) -> ok.
-write_local_groups_processes_info_to_remote(RemoteNode, LocalGroupsRegistryProcessesInfo) ->
-    ok = rpc:call(RemoteNode, ?MODULE, write_groups_processes_info_to_node, [node(), LocalGroupsRegistryProcessesInfo]).
-
--spec write_groups_processes_info_to_node(Node :: atom(), GroupsRegistryProcessesInfo :: list()) -> ok.
-write_groups_processes_info_to_node(Node, GroupsRegistryProcessesInfo) ->
-    FWrite = fun({Name, Pid}) ->
-        mnesia:dirty_write(#syn_groups_table{
-            name = Name,
-            pid = Pid,
-            node = Node
-        })
-    end,
-    lists:foreach(FWrite, GroupsRegistryProcessesInfo).
-
--spec handle_purged_registry_double_processes(
-    RegistryConflictingProcessCallbackModule :: atom(),
-    RegistryConflictingProcessCallbackFunction :: atom(),
-    DoubleRegistryProcessesInfo :: list()
-) -> ok.
-handle_purged_registry_double_processes(undefined, _, DoubleRegistryProcessesInfo) ->
-    F = fun({Key, LocalProcessPid, _LocalProcessMeta}) ->
-        error_logger:warning_msg("Found a double process for ~s, killing it on local node ~p~n", [Key, node()]),
-        exit(LocalProcessPid, kill)
-    end,
-    lists:foreach(F, DoubleRegistryProcessesInfo);
-handle_purged_registry_double_processes(RegistryConflictingProcessCallbackModule, RegistryConflictingProcessCallbackFunction, DoubleRegistryProcessesInfo) ->
-    F = fun({Key, LocalProcessPid, LocalProcessMeta}) ->
-        spawn(
-            fun() ->
-                error_logger:warning_msg("Found a double process for ~s, about to trigger callback on local node ~p~n", [Key, node()]),
-                RegistryConflictingProcessCallbackModule:RegistryConflictingProcessCallbackFunction(Key, LocalProcessPid, LocalProcessMeta)
-            end
-        )
-    end,
-    lists:foreach(F, DoubleRegistryProcessesInfo).

+ 0 - 417
src/syn_groups.erl

@@ -1,417 +0,0 @@
-%% ==========================================================================================================
-%% Syn - A global Process Registry and Process Group manager.
-%%
-%% The MIT License (MIT)
-%%
-%% Copyright (c) 2016 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a copy
-%% of this software and associated documentation files (the "Software"), to deal
-%% in the Software without restriction, including without limitation the rights
-%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-%% copies of the Software, and to permit persons to whom the Software is
-%% furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-%% THE SOFTWARE.
-%% ==========================================================================================================
--module(syn_groups).
--behaviour(gen_server).
-
-%% API
--export([start_link/0]).
--export([join/2, join/3]).
--export([leave/2]).
--export([member/2]).
--export([get_members/1, get_members/2]).
--export([get_local_members/1, get_local_members/2]).
--export([publish/2]).
--export([publish_to_local/2]).
--export([multi_call/2, multi_call/3]).
--export([multi_call_reply/2]).
-
-%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-
-%% internal
--export([multi_call_and_receive/4]).
-
-%% records
--record(state, {
-    process_groups_process_exit_callback_module = undefined :: atom(),
-    process_groups_process_exit_callback_function = undefined :: atom()
-}).
-
-%% macros
--define(DEFAULT_MULTI_CALL_TIMEOUT_MS, 5000).
-
-%% include
--include("syn.hrl").
-
-
-%% ===================================================================
-%% API
-%% ===================================================================
--spec start_link() -> {ok, pid()} | {error, any()}.
-start_link() ->
-    Options = [],
-    gen_server:start_link({local, ?MODULE}, ?MODULE, [], Options).
-
--spec join(Name :: any(), Pid :: pid()) -> ok.
-join(Name, Pid) ->
-    join(Name, Pid, undefined).
-
--spec join(Name :: any(), Pid :: pid(), Meta :: any()) -> ok.
-join(Name, Pid, Meta) when is_pid(Pid) ->
-    Node = node(Pid),
-    gen_server:call({?MODULE, Node}, {join, Name, Pid, Meta}).
-
--spec leave(Name :: any(), Pid :: pid()) -> ok | {error, pid_not_in_group}.
-leave(Name, Pid) when is_pid(Pid) ->
-    Node = node(Pid),
-    gen_server:call({?MODULE, Node}, {leave, Name, Pid}).
-
--spec member(Pid :: pid(), Name :: any()) -> boolean().
-member(Pid, Name) when is_pid(Pid) ->
-    i_member(Pid, Name).
-
--spec get_members(Name :: any()) -> [pid()].
-get_members(Name) ->
-    i_get_members(Name).
-
--spec get_members(Name :: any(), with_meta) -> [{pid(), Meta :: any()}].
-get_members(Name, with_meta) ->
-    i_get_members(Name, with_meta).
-
--spec get_local_members(Name :: any()) -> [pid()].
-get_local_members(Name) ->
-    i_get_local_members(Name).
-
--spec get_local_members(Name :: any(), with_meta) -> [{pid(), Meta :: any()}].
-get_local_members(Name, with_meta) ->
-    i_get_local_members(Name, with_meta).
-
--spec publish(Name :: any(), Message :: any()) -> {ok, RecipientCount :: non_neg_integer()}.
-publish(Name, Message) ->
-    MemberPids = i_get_members(Name),
-    FSend = fun(Pid) ->
-        Pid ! Message
-    end,
-    lists:foreach(FSend, MemberPids),
-    {ok, length(MemberPids)}.
-
--spec publish_to_local(Name :: any(), Message :: any()) -> {ok, RecipientCount :: non_neg_integer()}.
-publish_to_local(Name, Message) ->
-    MemberPids = i_get_local_members(Name),
-    FSend = fun(Pid) ->
-        Pid ! Message
-    end,
-    lists:foreach(FSend, MemberPids),
-    {ok, length(MemberPids)}.
-
--spec multi_call(Name :: any(), Message :: any()) -> {[{pid(), Reply :: any()}], [BadPid :: pid()]}.
-multi_call(Name, Message) ->
-    multi_call(Name, Message, ?DEFAULT_MULTI_CALL_TIMEOUT_MS).
-
--spec multi_call(Name :: any(), Message :: any(), Timeout :: non_neg_integer()) ->
-    {[{pid(), Reply :: any()}], [BadPid :: pid()]}.
-multi_call(Name, Message, Timeout) ->
-    Self = self(),
-    MemberPids = i_get_members(Name),
-    FSend = fun(Pid) ->
-        spawn_link(?MODULE, multi_call_and_receive, [Self, Pid, Message, Timeout])
-    end,
-    lists:foreach(FSend, MemberPids),
-    collect_replies(MemberPids).
-
--spec multi_call_reply(CallerPid :: pid(), Reply :: any()) -> {syn_multi_call_reply, pid(), Reply :: any()}.
-multi_call_reply(CallerPid, Reply) ->
-    CallerPid ! {syn_multi_call_reply, self(), Reply}.
-
-%% ===================================================================
-%% Callbacks
-%% ===================================================================
-
-%% ----------------------------------------------------------------------------------------------------------
-%% Init
-%% ----------------------------------------------------------------------------------------------------------
--spec init([]) ->
-    {ok, #state{}} |
-    {ok, #state{}, Timeout :: non_neg_integer()} |
-    ignore |
-    {stop, Reason :: any()}.
-init([]) ->
-    %% trap linked processes signal
-    process_flag(trap_exit, true),
-    
-    %% get options
-    {ok, [ProcessExitCallbackModule, ProcessExitCallbackFunction]} = syn_utils:get_env_value(
-        process_groups_process_exit_callback,
-        [undefined, undefined]
-    ),
-    
-    %% build state
-    {ok, #state{
-        process_groups_process_exit_callback_module = ProcessExitCallbackModule,
-        process_groups_process_exit_callback_function = ProcessExitCallbackFunction
-    }}.
-
-%% ----------------------------------------------------------------------------------------------------------
-%% Call messages
-%% ----------------------------------------------------------------------------------------------------------
--spec handle_call(Request :: any(), From :: any(), #state{}) ->
-    {reply, Reply :: any(), #state{}} |
-    {reply, Reply :: any(), #state{}, Timeout :: non_neg_integer()} |
-    {noreply, #state{}} |
-    {noreply, #state{}, Timeout :: non_neg_integer()} |
-    {stop, Reason :: any(), Reply :: any(), #state{}} |
-    {stop, Reason :: any(), #state{}}.
-
-handle_call({join, Name, Pid, Meta}, _From, State) ->
-    %% check if pid is already in group
-    case find_by_pid_and_name(Pid, Name) of
-        undefined ->
-            ok;
-        Process ->
-            %% remove old reference
-            mnesia:dirty_delete_object(Process)
-    end,
-    %% add to group
-    mnesia:dirty_write(#syn_groups_table{
-        name = Name,
-        pid = Pid,
-        node = node(),
-        meta = Meta
-    }),
-    %% link
-    erlang:link(Pid),
-    %% return
-    {reply, ok, State};
-
-handle_call({leave, Name, Pid}, _From, State) ->
-    case find_by_pid_and_name(Pid, Name) of
-        undefined ->
-            {reply, {error, pid_not_in_group}, State};
-        Process ->
-            %% remove from table
-            remove_process(Process),
-            %% unlink only when process is no more in groups
-            case find_groups_by_pid(Pid) of
-                [] -> erlang:unlink(Pid);
-                _ -> nop
-            end,
-            %% reply
-            {reply, ok, State}
-    end;
-
-handle_call(Request, From, State) ->
-    error_logger:warning_msg("Received from ~p an unknown call message: ~p~n", [Request, From]),
-    {reply, undefined, State}.
-
-%% ----------------------------------------------------------------------------------------------------------
-%% Cast messages
-%% ----------------------------------------------------------------------------------------------------------
--spec handle_cast(Msg :: any(), #state{}) ->
-    {noreply, #state{}} |
-    {noreply, #state{}, Timeout :: non_neg_integer()} |
-    {stop, Reason :: any(), #state{}}.
-
-handle_cast(Msg, State) ->
-    error_logger:warning_msg("Received an unknown cast message: ~p~n", [Msg]),
-    {noreply, State}.
-
-%% ----------------------------------------------------------------------------------------------------------
-%% All non Call / Cast messages
-%% ----------------------------------------------------------------------------------------------------------
--spec handle_info(Info :: any(), #state{}) ->
-    {noreply, #state{}} |
-    {noreply, #state{}, Timeout :: non_neg_integer()} |
-    {stop, Reason :: any(), #state{}}.
-
-handle_info({'EXIT', Pid, Reason}, #state{
-    process_groups_process_exit_callback_module = ProcessExitCallbackModule,
-    process_groups_process_exit_callback_function = ProcessExitCallbackFunction
-} = State) ->
-    %% check if pid is in table
-    case find_groups_by_pid(Pid) of
-        [] ->
-            %% log
-            case Reason of
-                normal -> ok;
-                shutdown -> ok;
-                {shutdown, _} -> ok;
-                killed -> ok;
-                _ ->
-                    error_logger:error_msg("Received an exit message from an unlinked process ~p with reason: ~p~n", [Pid, Reason])
-            end;
-        
-        Processes ->
-            F = fun(Process) ->
-                %% get group & meta
-                Name = Process#syn_groups_table.name,
-                Meta = Process#syn_groups_table.meta,
-                %% log
-                case Reason of
-                    normal -> ok;
-                    shutdown -> ok;
-                    {shutdown, _} -> ok;
-                    killed -> ok;
-                    _ ->
-                        error_logger:error_msg("Process of group ~p and pid ~p exited with reason: ~p~n", [Name, Pid, Reason])
-                end,
-                %% delete from table
-                remove_process(Process),
-                
-                %% callback in separate process
-                case ProcessExitCallbackModule of
-                    undefined ->
-                        ok;
-                    _ ->
-                        spawn(fun() ->
-                            ProcessExitCallbackModule:ProcessExitCallbackFunction(Name, Pid, Meta, Reason)
-                        end)
-                end
-            end,
-            lists:foreach(F, Processes)
-    end,
-    %% return
-    {noreply, State};
-
-handle_info(Info, State) ->
-    error_logger:warning_msg("Received an unknown info message: ~p~n", [Info]),
-    {noreply, State}.
-
-%% ----------------------------------------------------------------------------------------------------------
-%% Terminate
-%% ----------------------------------------------------------------------------------------------------------
--spec terminate(Reason :: any(), #state{}) -> terminated.
-terminate(Reason, _State) ->
-    error_logger:info_msg("Terminating syn_groups with reason: ~p~n", [Reason]),
-    terminated.
-
-%% ----------------------------------------------------------------------------------------------------------
-%% Convert process state when code is changed.
-%% ----------------------------------------------------------------------------------------------------------
--spec code_change(OldVsn :: any(), #state{}, Extra :: any()) -> {ok, #state{}}.
-code_change(_OldVsn, State, _Extra) ->
-    {ok, State}.
-
-%% ===================================================================
-%% Internal
-%% ===================================================================
--spec find_by_pid_and_name(Pid :: pid(), Name :: any()) -> Process :: #syn_groups_table{} | undefined.
-find_by_pid_and_name(Pid, Name) ->
-    %% build match specs
-    MatchHead = #syn_groups_table{name = Name, pid = Pid, _ = '_'},
-    Guards = [],
-    Result = '$_',
-    %% select
-    case mnesia:dirty_select(syn_groups_table, [{MatchHead, Guards, [Result]}]) of
-        [] -> undefined;
-        [Process] -> Process
-    end.
-
--spec i_member(Pid :: pid(), Name :: any()) -> boolean().
-i_member(Pid, Name) ->
-    case find_by_pid_and_name(Pid, Name) of
-        undefined -> false;
-        _ -> true
-    end.
-
--spec i_get_members(Name :: any()) -> [pid()].
-i_get_members(Name) ->
-    Processes = mnesia:dirty_read(syn_groups_table, Name),
-    Pids = lists:map(fun(Process) ->
-        Process#syn_groups_table.pid
-    end, Processes),
-    lists:sort(Pids).
-
--spec i_get_members(Name :: any(), with_meta) -> [{pid(), Meta :: any()}].
-i_get_members(Name, with_meta) ->
-    Processes = mnesia:dirty_read(syn_groups_table, Name),
-    PidsWithMeta = lists:map(fun(Process) ->
-        {Process#syn_groups_table.pid, Process#syn_groups_table.meta}
-    end, Processes),
-    lists:keysort(1, PidsWithMeta).
-
--spec i_get_local_members(Name :: any()) -> [pid()].
-i_get_local_members(Name) ->
-    %% build name guard
-    NameGuard = case is_tuple(Name) of
-        true -> {'==', '$1', {Name}};
-        _ -> {'=:=', '$1', Name}
-    end,
-    %% build match specs
-    MatchHead = #syn_groups_table{name = '$1', node = '$2', pid = '$3', _ = '_'},
-    Guards = [NameGuard, {'=:=', '$2', node()}],
-    Result = '$3',
-    %% select
-    Pids = mnesia:dirty_select(syn_groups_table, [{MatchHead, Guards, [Result]}]),
-    lists:sort(Pids).
-
--spec i_get_local_members(Name :: any(), with_meta) -> [{pid(), Meta :: any()}].
-i_get_local_members(Name, with_meta) ->
-    %% build name guard
-    NameGuard = case is_tuple(Name) of
-        true -> {'==', '$1', {Name}};
-        _ -> {'=:=', '$1', Name}
-    end,
-    %% build match specs
-    MatchHead = #syn_groups_table{name = '$1', node = '$2', pid = '$3', meta = '$4', _ = '_'},
-    Guards = [NameGuard, {'=:=', '$2', node()}],
-    Result = {{'$3', '$4'}},
-    %% select
-    PidsWithMeta = mnesia:dirty_select(syn_groups_table, [{MatchHead, Guards, [Result]}]),
-    lists:keysort(1, PidsWithMeta).
-
--spec find_groups_by_pid(Pid :: pid()) -> [Process :: #syn_groups_table{}].
-find_groups_by_pid(Pid) ->
-    mnesia:dirty_index_read(syn_groups_table, Pid, #syn_groups_table.pid).
-
--spec remove_process(Process :: #syn_groups_table{}) -> ok.
-remove_process(Process) ->
-    mnesia:dirty_delete_object(syn_groups_table, Process).
-
--spec multi_call_and_receive(
-    CollectorPid :: pid(),
-    Pid :: pid(),
-    Message :: any(),
-    Timeout :: non_neg_integer()
-) -> any().
-multi_call_and_receive(CollectorPid, Pid, Message, Timeout) ->
-    MonitorRef = monitor(process, Pid),
-    Pid ! {syn_multi_call, self(), Message},
-    
-    receive
-        {syn_multi_call_reply, Pid, Reply} ->
-            CollectorPid ! {reply, Pid, Reply};
-        {'DOWN', MonitorRef, _, _, _} ->
-            CollectorPid ! {bad_pid, Pid}
-    after Timeout ->
-        CollectorPid ! {bad_pid, Pid}
-    end.
-
--spec collect_replies(MemberPids :: [pid()]) -> {[{pid(), Reply :: any()}], [BadPid :: pid()]}.
-collect_replies(MemberPids) ->
-    collect_replies(MemberPids, [], []).
-
--spec collect_replies(MemberPids :: [pid()], [{pid(), Reply :: any()}], [pid()]) ->
-    {[{pid(), Reply :: any()}], [BadPid :: pid()]}.
-collect_replies([], Replies, BadPids) -> {Replies, BadPids};
-collect_replies(MemberPids, Replies, BadPids) ->
-    receive
-        {reply, Pid, Reply} ->
-            MemberPids1 = lists:delete(Pid, MemberPids),
-            collect_replies(MemberPids1, [{Pid, Reply} | Replies], BadPids);
-        {bad_pid, Pid} ->
-            MemberPids1 = lists:delete(Pid, MemberPids),
-            collect_replies(MemberPids1, Replies, [Pid | BadPids])
-    end.

+ 8 - 5
include/syn.hrl → src/syn_records.hrl

@@ -3,7 +3,7 @@
 %%
 %% The MIT License (MIT)
 %%
-%% Copyright (c) 2015 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
+%% Copyright (c) 2015-2019 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
 %%
 %% Permission is hereby granted, free of charge, to any person obtaining a copy
 %% of this software and associated documentation files (the "Software"), to deal
@@ -25,14 +25,17 @@
 %% ==========================================================================================================
 %% records
 -record(syn_registry_table, {
-    key = undefined :: any(),
-    pid = undefined :: undefined | pid() | atom(),
+    name = undefined :: any(),
+    pid = undefined :: undefined | pid(),
     node = undefined :: atom(),
-    meta = undefined :: any()
+    meta = undefined :: any(),
+    monitor_ref = undefined :: undefined | reference()
 }).
 -record(syn_groups_table, {
     name = undefined :: any(),
-    pid = undefined :: undefined | pid() | atom(),
+    pid = undefined :: undefined | pid(),
     node = undefined :: atom(),
     meta = undefined :: any()
 }).
+-type syn_registry_tuple() :: {Name :: term(), Pid :: pid(), Node :: node(), Meta :: term()}.
+-export_type([syn_registry_tuple/0]).

+ 168 - 192
src/syn_registry.erl

@@ -3,7 +3,7 @@
 %%
 %% The MIT License (MIT)
 %%
-%% Copyright (c) 2015 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
+%% Copyright (c) 2015-2019 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
 %%
 %% Permission is hereby granted, free of charge, to any person obtaining a copy
 %% of this software and associated documentation files (the "Software"), to deal
@@ -28,24 +28,26 @@
 
 %% API
 -export([start_link/0]).
+-export([whereis/1, whereis/2]).
 -export([register/2, register/3]).
 -export([unregister/1]).
--export([find_by_key/1, find_by_key/2]).
--export([find_by_pid/1, find_by_pid/2]).
 -export([count/0, count/1]).
 
+%% sync API
+-export([sync_register/3, sync_unregister/1]).
+-export([get_local_registry_tuples_and_suspend/1]).
+
+%% internal
+-export([find_process_entry_by_name/1]).
+
 %% gen_server callbacks
 -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
 
 %% records
--record(state, {
-    registry_process_exit_callback_module = undefined :: atom(),
-    registry_process_exit_callback_function = undefined :: atom()
-}).
-
-%% include
--include("syn.hrl").
+-record(state, {}).
 
+%% includes
+-include("syn_records.hrl").
 
 %% ===================================================================
 %% API
@@ -55,53 +57,55 @@ start_link() ->
     Options = [],
     gen_server:start_link({local, ?MODULE}, ?MODULE, [], Options).
 
--spec find_by_key(Key :: any()) -> pid() | undefined.
-find_by_key(Key) ->
-    case i_find_by_key(on_connected_node, Key) of
-        undefined -> undefined;
-        Process -> Process#syn_registry_table.pid
-    end.
-
--spec find_by_key(Key :: any(), with_meta) -> {pid(), Meta :: any()} | undefined.
-find_by_key(Key, with_meta) ->
-    case i_find_by_key(on_connected_node, Key) of
-        undefined -> undefined;
-        Process -> {Process#syn_registry_table.pid, Process#syn_registry_table.meta}
-    end.
-
--spec find_by_pid(Pid :: pid()) -> Key :: any() | undefined.
-find_by_pid(Pid) when is_pid(Pid) ->
-    case i_find_by_pid(on_connected_node, Pid) of
+-spec whereis(Name :: term()) -> pid() | undefined.
+whereis(Name) ->
+    case find_process_entry_by_name(Name) of
         undefined -> undefined;
-        Process -> Process#syn_registry_table.key
+        Entry -> Entry#syn_registry_table.pid
     end.
 
--spec find_by_pid(Pid :: pid(), with_meta) -> {Key :: any(), Meta :: any()} | undefined.
-find_by_pid(Pid, with_meta) when is_pid(Pid) ->
-    case i_find_by_pid(on_connected_node, Pid) of
+-spec whereis(Name :: term(), with_meta) -> {pid(), Meta :: term()} | undefined.
+whereis(Name, with_meta) ->
+    case find_process_entry_by_name(Name) of
         undefined -> undefined;
-        Process -> {Process#syn_registry_table.key, Process#syn_registry_table.meta}
+        Entry -> {Entry#syn_registry_table.pid, Entry#syn_registry_table.meta}
     end.
 
--spec register(Key :: any(), Pid :: pid()) -> ok | {error, taken | pid_already_registered}.
-register(Key, Pid) when is_pid(Pid) ->
-    register(Key, Pid, undefined).
+-spec register(Name :: term(), Pid :: pid()) -> ok | {error, Reason :: term()}.
+register(Name, Pid) ->
+    register(Name, Pid, undefined).
 
--spec register(Key :: any(), Pid :: pid(), Meta :: any()) -> ok | {error, taken | pid_already_registered}.
-register(Key, Pid, Meta) when is_pid(Pid) ->
+-spec register(Name :: term(), Pid :: pid(), Meta :: term()) -> ok | {error, Reason :: term()}.
+register(Name, Pid, Meta) when is_pid(Pid) ->
     Node = node(Pid),
-    gen_server:call({?MODULE, Node}, {register_on_node, Key, Pid, Meta}).
+    gen_server:call({?MODULE, Node}, {register_on_node, Name, Pid, Meta}).
 
--spec unregister(Key :: any()) -> ok | {error, undefined}.
-unregister(Key) ->
-    case i_find_by_key(Key) of
+-spec unregister(Name :: term()) -> ok | {error, Reason :: term()}.
+unregister(Name) ->
+    % get process' node
+    case find_process_entry_by_name(Name) of
         undefined ->
             {error, undefined};
-        Process ->
-            Node = node(Process#syn_registry_table.pid),
-            gen_server:call({?MODULE, Node}, {unregister_on_node, Key})
+        Entry ->
+            Node = node(Entry#syn_registry_table.pid),
+            gen_server:call({?MODULE, Node}, {unregister_on_node, Name})
     end.
 
+-spec sync_register(Name :: term(), Pid :: pid(), Meta :: term()) -> ok.
+sync_register(Name, Pid, Meta) ->
+    gen_server:cast(?MODULE, {sync_register, Name, Pid, Meta}).
+
+-spec sync_unregister(Name :: term()) -> ok.
+sync_unregister(Name) ->
+    gen_server:cast(?MODULE, {sync_unregister, Name}).
+
+-spec get_local_registry_tuples_and_suspend(FromNode :: node()) -> list(syn_registry_tuple()).
+get_local_registry_tuples_and_suspend(FromNode) ->
+    Result = gen_server:call(?MODULE, {get_local_registry_tuples_and_suspend, FromNode}),
+    %% suspend self to not modify table
+    sys:suspend(?MODULE),
+    Result.
+
 -spec count() -> non_neg_integer().
 count() ->
     mnesia:table_info(syn_registry_table, size).
@@ -129,20 +133,13 @@ count(Node) ->
     ignore |
     {stop, Reason :: any()}.
 init([]) ->
-    %% trap linked processes signal
-    process_flag(trap_exit, true),
-    
-    %% get options
-    {ok, [ProcessExitCallbackModule, ProcessExitCallbackFunction]} = syn_utils:get_env_value(
-        registry_process_exit_callback,
-        [undefined, undefined]
-    ),
-    
-    %% build state
-    {ok, #state{
-        registry_process_exit_callback_module = ProcessExitCallbackModule,
-        registry_process_exit_callback_function = ProcessExitCallbackFunction
-    }}.
+    %% wait for table
+    case mnesia:wait_for_tables([syn_registry_table], 10000) of
+        ok ->
+            {ok, #state{}};
+        Reason ->
+            {stop, {error_waiting_for_process_registry_table, Reason}}
+    end.
 
 %% ----------------------------------------------------------------------------------------------------------
 %% Call messages
@@ -155,54 +152,41 @@ init([]) ->
     {stop, Reason :: any(), Reply :: any(), #state{}} |
     {stop, Reason :: any(), #state{}}.
 
-handle_call({register_on_node, Key, Pid, Meta}, _From, State) ->
-    %% check & register in gen_server process to ensure atomicity at node level without transaction lock
-    %% atomicity is obviously not at cluster level, which is covered by syn_consistency.
-    
-    %% check if key registered
-    case i_find_by_key(Key) of
-        undefined ->
-            %% check if pid registered with different key
-            case i_find_by_pid(Pid) of
+handle_call({register_on_node, Name, Pid, Meta}, _From, State) ->
+    %% check if pid is alive
+    case is_process_alive(Pid) of
+        true ->
+            %% check if name available
+            case find_process_entry_by_name(Name) of
                 undefined ->
                     %% add to table
-                    register_on_node(Key, Pid, node(), Meta),
+                    register_on_node(Name, Pid, node(Pid), Meta),
+                    %% multicast
+                    rpc:eval_everywhere(nodes(), ?MODULE, sync_register, [Name, Pid, Meta]),
                     %% return
                     {reply, ok, State};
-                
                 _ ->
-                    {reply, {error, pid_already_registered}, State}
+                    {reply, {error, taken}, State}
             end;
-        
-        Process when Process#syn_registry_table.pid =:= Pid ->
-            %% re-register (maybe different metadata?)
-            register_on_node(Key, Pid, node(), Meta),
-            %% return
-            {reply, ok, State};
-        
         _ ->
-            {reply, {error, taken}, State}
+            {reply, {error, not_alive}, State}
     end;
 
-handle_call({unregister_on_node, Key}, _From, State) ->
-    %% we check again for key to return the correct response regardless of race conditions
-    case i_find_by_key(Key) of
-        undefined ->
-            {reply, {error, undefined}, State};
-        Process ->
-            %% remove from table
-            remove_process_by_key(Key),
-            %% unlink
-            Pid = Process#syn_registry_table.pid,
-            erlang:unlink(Pid),
-            %% reply
-            {reply, ok, State}
-    end;
-
-handle_call({unlink_process, Pid}, _From, State) ->
-    erlang:unlink(Pid),
+handle_call({unregister_on_node, Name}, _From, State) ->
+    %% remove from table
+    unregister_on_node(Name),
+    %% multicast
+    rpc:eval_everywhere(nodes(), ?MODULE, sync_unregister, [Name]),
+    %% return
     {reply, ok, State};
 
+handle_call({get_local_registry_tuples_and_suspend, FromNode}, _From, State) ->
+    error_logger:info_msg("Received request of local registry tuples from remote node: ~p~n", [FromNode]),
+    %% get tuples
+    RegistryTuples = get_registry_tuples_of_current_node(),
+    %% return
+    {reply, RegistryTuples, State};
+
 handle_call(Request, From, State) ->
     error_logger:warning_msg("Received from ~p an unknown call message: ~p~n", [Request, From]),
     {reply, undefined, State}.
@@ -215,6 +199,18 @@ handle_call(Request, From, State) ->
     {noreply, #state{}, Timeout :: non_neg_integer()} |
     {stop, Reason :: any(), #state{}}.
 
+handle_cast({sync_register, Name, Pid, Meta}, State) ->
+    %% add to table
+    register_on_node(Name, Pid, node(Pid), Meta),
+    %% return
+    {noreply, State};
+
+handle_cast({sync_unregister, Name}, State) ->
+    %% add to table
+    unregister_on_node(Name),
+    %% return
+    {noreply, State};
+
 handle_cast(Msg, State) ->
     error_logger:warning_msg("Received an unknown cast message: ~p~n", [Msg]),
     {noreply, State}.
@@ -227,58 +223,22 @@ handle_cast(Msg, State) ->
     {noreply, #state{}, Timeout :: non_neg_integer()} |
     {stop, Reason :: any(), #state{}}.
 
-handle_info({'EXIT', Pid, Reason}, #state{
-    registry_process_exit_callback_module = ProcessExitCallbackModule,
-    registry_process_exit_callback_function = ProcessExitCallbackFunction
-} = State) ->
-    %% check if pid is in table
-    {Key, Meta} = case i_find_by_pid(Pid) of
-        undefined ->
-            %% log
-            case Reason of
-                normal -> ok;
-                shutdown -> ok;
-                {shutdown, _} -> ok;
-                killed -> ok;
-                _ ->
-                    error_logger:error_msg("Received an exit message from an unlinked process ~p with reason: ~p~n", [Pid, Reason])
-            end,
-            
-            %% return
-            {undefined, undefined};
-        
-        Process ->
-            %% get process info
-            Key0 = Process#syn_registry_table.key,
-            Meta0 = Process#syn_registry_table.meta,
-            
+handle_info({'DOWN', _MonitorRef, process, Pid, Reason}, State) ->
+    case find_processes_entry_by_pid(Pid) of
+        [] ->
             %% log
-            case Reason of
-                normal -> ok;
-                shutdown -> ok;
-                {shutdown, _} -> ok;
-                killed -> ok;
-                _ ->
-                    error_logger:error_msg("Process with key ~p and pid ~p exited with reason: ~p~n", [Key0, Pid, Reason])
-            end,
-            
-            %% delete from table
-            remove_process_by_key(Key0),
-            
-            %% return
-            {Key0, Meta0}
-    end,
-    
-    %% callback
-    case ProcessExitCallbackModule of
-        undefined ->
-            ok;
-        _ ->
-            spawn(fun() ->
-                ProcessExitCallbackModule:ProcessExitCallbackFunction(Key, Pid, Meta, Reason)
-            end)
+            log_process_exit(undefined, Pid, Reason);
+
+        Entries ->
+            lists:foreach(fun(Entry) ->
+                %% get process info
+                Name = Entry#syn_registry_table.name,
+                %% log
+                log_process_exit(Name, Pid, Reason),
+                %% delete from table
+                unregister_on_node(Name)
+            end, Entries)
     end,
-    
     %% return
     {noreply, State};
 
@@ -291,7 +251,7 @@ handle_info(Info, State) ->
 %% ----------------------------------------------------------------------------------------------------------
 -spec terminate(Reason :: any(), #state{}) -> terminated.
 terminate(Reason, _State) ->
-    error_logger:info_msg("Terminating syn_registry with reason: ~p~n", [Reason]),
+    error_logger:info_msg("Terminating with reason: ~p~n", [Reason]),
     terminated.
 
 %% ----------------------------------------------------------------------------------------------------------
@@ -304,53 +264,69 @@ code_change(_OldVsn, State, _Extra) ->
 %% ===================================================================
 %% Internal
 %% ===================================================================
--spec i_find_by_key(on_connected_node, Key :: any()) -> Process :: #syn_registry_table{} | undefined.
-i_find_by_key(on_connected_node, Key) ->
-    case i_find_by_key(Key) of
-        undefined -> undefined;
-        Process -> return_if_on_connected_node(Process)
-    end.
-
--spec i_find_by_key(Key :: any()) -> Process :: #syn_registry_table{} | undefined.
-i_find_by_key(Key) ->
-    case mnesia:dirty_read(syn_registry_table, Key) of
-        [Process] -> Process;
-        _ -> undefined
-    end.
-
--spec i_find_by_pid(on_connected_node, Pid :: pid()) -> Process :: #syn_registry_table{} | undefined.
-i_find_by_pid(on_connected_node, Pid) ->
-    case i_find_by_pid(Pid) of
-        undefined -> undefined;
-        Process -> return_if_on_connected_node(Process)
-    end.
-
--spec i_find_by_pid(Pid :: pid()) -> Process :: #syn_registry_table{} | undefined.
-i_find_by_pid(Pid) ->
-    case mnesia:dirty_index_read(syn_registry_table, Pid, #syn_registry_table.pid) of
-        [Process] -> Process;
-        _ -> undefined
-    end.
-
--spec return_if_on_connected_node(Process :: #syn_registry_table{}) -> Process :: #syn_registry_table{} | undefined.
-return_if_on_connected_node(Process) ->
-    case lists:member(Process#syn_registry_table.node, [node() | nodes()]) of
-        true -> Process;
-        _ -> undefined
-    end.
-
--spec remove_process_by_key(Key :: any()) -> ok.
-remove_process_by_key(Key) ->
-    mnesia:dirty_delete(syn_registry_table, Key).
-
--spec register_on_node(Key :: any(), Pid :: pid(), Node :: atom(), Meta :: any()) -> true.
-register_on_node(Key, Pid, Node, Meta) ->
+-spec register_on_node(Name :: any(), Pid :: pid(), Node :: atom(), Meta :: any()) -> true.
+register_on_node(Name, Pid, Node, Meta) ->
+    MonitorRef = case find_processes_entry_by_pid(Pid) of
+        [] ->
+            %% process is not monitored yet, add
+            erlang:monitor(process, Pid);
+        [Entry | _] ->
+            Entry#syn_registry_table.monitor_ref
+    end,
     %% add to table
     mnesia:dirty_write(#syn_registry_table{
-        key = Key,
+        name = Name,
         pid = Pid,
         node = Node,
-        meta = Meta
-    }),
-    %% link
-    erlang:link(Pid).
+        meta = Meta,
+        monitor_ref = MonitorRef
+    }).
+
+-spec unregister_on_node(Name :: any()) -> ok.
+unregister_on_node(Name) ->
+    mnesia:dirty_delete(syn_registry_table, Name).
+%% TODO: unmonitor process!
+
+-spec find_processes_entry_by_pid(Pid :: pid()) -> Entries :: list(#syn_registry_table{}).
+find_processes_entry_by_pid(Pid) when is_pid(Pid) ->
+    mnesia:dirty_index_read(syn_registry_table, Pid, #syn_registry_table.pid).
+
+-spec find_process_entry_by_name(Name :: term()) -> Entry :: #syn_registry_table{} | undefined.
+find_process_entry_by_name(Name) ->
+    case mnesia:dirty_read(syn_registry_table, Name) of
+        [Entry] -> Entry;
+        _ -> undefined
+    end.
+
+-spec get_registry_tuples_of_current_node() -> list(syn_registry_tuple()).
+get_registry_tuples_of_current_node() ->
+    %% build match specs
+    MatchHead = #syn_registry_table{name = '$1', pid = '$2', node = '$3', meta = '$4', _ = '_'},
+    Guard = {'=:=', '$3', node()},
+    RegistryTupleFormat = {{'$1', '$2', '$3', '$4'}},
+    %% select
+    mnesia:dirty_select(syn_registry_table, [{MatchHead, [Guard], [RegistryTupleFormat]}]).
+
+-spec log_process_exit(Name :: term(), Pid :: pid(), Reason :: term()) -> ok.
+log_process_exit(Name, Pid, Reason) ->
+    case Reason of
+        normal -> ok;
+        shutdown -> ok;
+        {shutdown, _} -> ok;
+        killed -> ok;
+        noconnection -> ok;
+        noproc -> ok;
+        _ ->
+            case Name of
+                undefined ->
+                    error_logger:error_msg(
+                        "Received a DOWN message from an unmonitored process ~p on local node ~p with reason: ~p~n",
+                        [Pid, node(), Reason]
+                    );
+                _ ->
+                    error_logger:error_msg(
+                        "Process with name ~p and pid ~p on local node ~p exited with reason: ~p~n",
+                        [Name, Pid, node(), Reason]
+                    )
+            end
+    end.

+ 4 - 4
src/syn_sup.erl

@@ -3,7 +3,7 @@
 %%
 %% The MIT License (MIT)
 %%
-%% Copyright (c) 2015 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
+%% Copyright (c) 2015-2019 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
 %%
 %% Permission is hereby granted, free of charge, to any person obtaining a copy
 %% of this software and associated documentation files (the "Software"), to deal
@@ -50,8 +50,8 @@ start_link() ->
     {ok, {{supervisor:strategy(), non_neg_integer(), pos_integer()}, [supervisor:child_spec()]}}.
 init([]) ->
     Children = [
-        ?CHILD(syn_registry, worker),
-        ?CHILD(syn_groups, worker),
-        ?CHILD(syn_consistency, worker)
+        ?CHILD(syn_backbone, worker),
+        ?CHILD(syn_consistency, worker),
+        ?CHILD(syn_registry, worker)
     ],
     {ok, {{one_for_one, 10, 10}, Children}}.

+ 0 - 45
src/syn_utils.erl

@@ -1,45 +0,0 @@
-%% ==========================================================================================================
-%% Syn - A global Process Registry and Process Group manager.
-%%
-%% The MIT License (MIT)
-%%
-%% Copyright (c) 2015 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a copy
-%% of this software and associated documentation files (the "Software"), to deal
-%% in the Software without restriction, including without limitation the rights
-%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-%% copies of the Software, and to permit persons to whom the Software is
-%% furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-%% THE SOFTWARE.
-%% ==========================================================================================================
--module(syn_utils).
-
-%% API
--export([get_env_value/1, get_env_value/2]).
-
-
-%% ===================================================================
-%% API
-%% ===================================================================
-%% get an environment value
--spec get_env_value(Key :: any()) -> {ok, any()} | undefined.
-get_env_value(Key) ->
-    application:get_env(Key).
-
--spec get_env_value(Key :: any(), Default :: any()) -> {ok, any()}.
-get_env_value(Key, Default) ->
-    case application:get_env(Key) of
-        undefined -> {ok, Default};
-        {ok, Val} -> {ok, Val}
-    end.

+ 0 - 19
test/results/.keep

@@ -7,25 +7,6 @@
 %% Syn config
     {syn, [
 
-        %% You can set a callback to be triggered when a process exits.
-        %% This callback will be called only on the node where the process was running.
-
-        {registry_process_exit_callback, [syn_registry_SUITE, registry_process_exit_callback_dummy]},
-
-        %% After a net split, when nodes reconnect, Syn will merge the data from all the nodes in the cluster.
-        %% If the same Key was used to register a process on different nodes during a net split, then there will be a conflict.
-        %% By default, Syn will discard the processes running on the node the conflict is being resolved on,
-        %% and will kill it by sending a `kill` signal with `exit(Pid, kill)`.
-        %% If this is not desired, you can set the registry_conflicting_process_callback option here below to instruct Syn
-        %% to trigger a callback, so that you can perform custom operations (such as a graceful shutdown).
-
-        {registry_conflicting_process_callback, [syn_registry_consistency_SUITE, registry_conflicting_process_callback_dummy]},
-    
-        %% You can set a callback to be triggered when a member process of a group exits.
-        %% This callback will be called only on the node where the process was running.
-    
-        {process_groups_process_exit_callback, [syn_groups_SUITE, process_groups_process_exit_callback_dummy]}
-
     ]}
 
 ].

+ 0 - 288
test/syn_create_mnesia_SUITE.erl

@@ -1,288 +0,0 @@
-%% ==========================================================================================================
-%% Syn - A global Process Registry and Process Group manager.
-%%
-%% The MIT License (MIT)
-%%
-%% Copyright (c) 2015 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a copy
-%% of this software and associated documentation files (the "Software"), to deal
-%% in the Software without restriction, including without limitation the rights
-%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-%% copies of the Software, and to permit persons to whom the Software is
-%% furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-%% THE SOFTWARE.
-%% ==========================================================================================================
--module(syn_create_mnesia_SUITE).
-
-%% callbacks
--export([all/0]).
--export([init_per_suite/1, end_per_suite/1]).
--export([groups/0, init_per_group/2, end_per_group/2]).
--export([init_per_testcase/2, end_per_testcase/2]).
-
-%% tests
--export([
-    single_node_when_mnesia_is_ram/1,
-    single_node_when_mnesia_is_opt_disc_no_schema_exists/1,
-    single_node_when_mnesia_is_opt_disc_schema_exists/1,
-    single_node_when_mnesia_is_disc/1
-]).
--export([
-    two_nodes_when_mnesia_is_ram/1,
-    two_nodes_when_mnesia_is_opt_disc_no_schema_exists/1,
-    two_nodes_when_mnesia_is_opt_disc_schema_exists/1,
-    two_nodes_when_mnesia_is_disc/1
-]).
-
-%% include
--include_lib("common_test/include/ct.hrl").
-
-
-%% ===================================================================
-%% Callbacks
-%% ===================================================================
-
-%% -------------------------------------------------------------------
-%% Function: all() -> GroupsAndTestCases | {skip,Reason}
-%% GroupsAndTestCases = [{group,GroupName} | TestCase]
-%% GroupName = atom()
-%% TestCase = atom()
-%% Reason = term()
-%% -------------------------------------------------------------------
-all() ->
-    [
-        {group, single_node_mnesia_creation},
-        {group, two_nodes_mnesia_creation}
-    ].
-
-%% -------------------------------------------------------------------
-%% Function: groups() -> [Group]
-%% Group = {GroupName,Properties,GroupsAndTestCases}
-%% GroupName = atom()
-%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
-%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
-%% TestCase = atom()
-%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
-%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
-%%			   repeat_until_any_ok | repeat_until_any_fail
-%% N = integer() | forever
-%% -------------------------------------------------------------------
-groups() ->
-    [
-        {single_node_mnesia_creation, [shuffle], [
-            single_node_when_mnesia_is_ram,
-            single_node_when_mnesia_is_opt_disc_no_schema_exists,
-            single_node_when_mnesia_is_opt_disc_schema_exists,
-            single_node_when_mnesia_is_disc
-        ]},
-        {two_nodes_mnesia_creation, [shuffle], [
-            two_nodes_when_mnesia_is_ram,
-            two_nodes_when_mnesia_is_opt_disc_no_schema_exists,
-            two_nodes_when_mnesia_is_opt_disc_schema_exists,
-            two_nodes_when_mnesia_is_disc
-        ]}
-    ].
-%% -------------------------------------------------------------------
-%% Function: init_per_suite(Config0) ->
-%%				Config1 | {skip,Reason} |
-%%              {skip_and_save,Reason,Config1}
-%% Config0 = Config1 = [tuple()]
-%% Reason = term()
-%% -------------------------------------------------------------------
-init_per_suite(Config) ->
-    %% config
-    [
-        {slave_node_short_name, syn_slave}
-        | Config
-    ].
-
-%% -------------------------------------------------------------------
-%% Function: end_per_suite(Config0) -> void() | {save_config,Config1}
-%% Config0 = Config1 = [tuple()]
-%% -------------------------------------------------------------------
-end_per_suite(_Config) -> ok.
-
-%% -------------------------------------------------------------------
-%% Function: init_per_group(GroupName, Config0) ->
-%%				Config1 | {skip,Reason} |
-%%              {skip_and_save,Reason,Config1}
-%% GroupName = atom()
-%% Config0 = Config1 = [tuple()]
-%% Reason = term()
-%% -------------------------------------------------------------------
-init_per_group(two_nodes_mnesia_creation, Config) ->
-    %% start slave
-    SlaveNodeShortName = proplists:get_value(slave_node_short_name, Config),
-    {ok, SlaveNode} = syn_test_suite_helper:start_slave(SlaveNodeShortName),
-    %% config
-    [
-        {slave_node, SlaveNode}
-        | Config
-    ];
-init_per_group(_GroupName, Config) -> Config.
-
-%% -------------------------------------------------------------------
-%% Function: end_per_group(GroupName, Config0) ->
-%%				void() | {save_config,Config1}
-%% GroupName = atom()
-%% Config0 = Config1 = [tuple()]
-%% -------------------------------------------------------------------
-end_per_group(two_nodes_mnesia_creation, Config) ->
-    %% get slave node name
-    SlaveNodeShortName = proplists:get_value(slave_node_short_name, Config),
-    %% stop slave
-    syn_test_suite_helper:stop_slave(SlaveNodeShortName);
-end_per_group(_GroupName, _Config) ->
-    ok.
-
-% ----------------------------------------------------------------------------------------------------------
-% Function: init_per_testcase(TestCase, Config0) ->
-%				Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
-% TestCase = atom()
-% Config0 = Config1 = [tuple()]
-% Reason = term()
-% ----------------------------------------------------------------------------------------------------------
-init_per_testcase(_TestCase, Config) ->
-    Config.
-
-% ----------------------------------------------------------------------------------------------------------
-% Function: end_per_testcase(TestCase, Config0) ->
-%				void() | {save_config,Config1} | {fail,Reason}
-% TestCase = atom()
-% Config0 = Config1 = [tuple()]
-% Reason = term()
-% ----------------------------------------------------------------------------------------------------------
-end_per_testcase(_TestCase, Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    syn_test_suite_helper:clean_after_test(SlaveNode).
-
-%% ===================================================================
-%% Tests
-%% ===================================================================
-single_node_when_mnesia_is_ram(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% check table exists
-    true = lists:member(syn_registry_table, mnesia:system_info(tables)).
-
-single_node_when_mnesia_is_opt_disc_no_schema_exists(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, opt_disc),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% check table exists
-    true = lists:member(syn_registry_table, mnesia:system_info(tables)).
-
-single_node_when_mnesia_is_opt_disc_schema_exists(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, opt_disc),
-    %% create schema
-    mnesia:create_schema([node()]),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% check table exists
-    true = lists:member(syn_registry_table, mnesia:system_info(tables)).
-
-single_node_when_mnesia_is_disc(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, disc),
-    %% create schema
-    mnesia:create_schema([node()]),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% check table exists
-    true = lists:member(syn_registry_table, mnesia:system_info(tables)).
-
-two_nodes_when_mnesia_is_ram(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    rpc:call(SlaveNode, mnesia, schema_location, [ram]),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
-    %% check table exists on local
-    true = lists:member(syn_registry_table, mnesia:system_info(tables)),
-    %% check table exists on remote
-    SlaveNodeMnesiaSystemInfo = rpc:call(SlaveNode, mnesia, system_info, [tables]),
-    true = rpc:call(SlaveNode, lists, member, [syn_registry_table, SlaveNodeMnesiaSystemInfo]).
-
-two_nodes_when_mnesia_is_opt_disc_no_schema_exists(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, opt_disc),
-    rpc:call(SlaveNode, mnesia, schema_location, [opt_disc]),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
-    %% check table exists on local
-    true = lists:member(syn_registry_table, mnesia:system_info(tables)),
-    %% check table exists on remote
-    SlaveNodeMnesiaSystemInfo = rpc:call(SlaveNode, mnesia, system_info, [tables]),
-    true = rpc:call(SlaveNode, lists, member, [syn_registry_table, SlaveNodeMnesiaSystemInfo]).
-
-two_nodes_when_mnesia_is_opt_disc_schema_exists(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, opt_disc),
-    rpc:call(SlaveNode, mnesia, schema_location, [opt_disc]),
-    %% create schema
-    mnesia:create_schema([node(), SlaveNode]),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
-    %% check table exists on local
-    true = lists:member(syn_registry_table, mnesia:system_info(tables)),
-    %% check table exists on remote
-    SlaveNodeMnesiaSystemInfo = rpc:call(SlaveNode, mnesia, system_info, [tables]),
-    true = rpc:call(SlaveNode, lists, member, [syn_registry_table, SlaveNodeMnesiaSystemInfo]).
-
-two_nodes_when_mnesia_is_disc(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, disc),
-    rpc:call(SlaveNode, mnesia, schema_location, [disc]),
-    %% create schema
-    mnesia:create_schema([node(), SlaveNode]),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
-    %% check table exists on local
-    true = lists:member(syn_registry_table, mnesia:system_info(tables)),
-    %% check table exists on remote
-    SlaveNodeMnesiaSystemInfo = rpc:call(SlaveNode, mnesia, system_info, [tables]),
-    true = rpc:call(SlaveNode, lists, member, [syn_registry_table, SlaveNodeMnesiaSystemInfo]).

+ 0 - 693
test/syn_groups_SUITE.erl

@@ -1,693 +0,0 @@
-%% ==========================================================================================================
-%% Syn - A global Process Registry and Process Group manager.
-%%
-%% The MIT License (MIT)
-%%
-%% Copyright (c) 2016 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a copy
-%% of this software and associated documentation files (the "Software"), to deal
-%% in the Software without restriction, including without limitation the rights
-%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-%% copies of the Software, and to permit persons to whom the Software is
-%% furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-%% THE SOFTWARE.
-%% ==========================================================================================================
--module(syn_groups_SUITE).
-
-%% callbacks
--export([all/0]).
--export([init_per_suite/1, end_per_suite/1]).
--export([groups/0, init_per_group/2, end_per_group/2]).
--export([init_per_testcase/2, end_per_testcase/2]).
-
-%% tests
--export([
-    single_node_join/1,
-    single_node_leave/1,
-    single_node_kill/1,
-    single_node_leave_and_kill_multi_groups/1,
-    single_node_publish/1,
-    single_node_multi_call/1,
-    single_node_multi_call_when_recipient_crashes/1,
-    single_node_meta/1,
-    single_node_callback_on_process_exit/1,
-    single_node_all_keys/1
-]).
--export([
-    two_nodes_kill/1,
-    two_nodes_publish/1,
-    two_nodes_multi_call/1,
-    two_nodes_local_members/1,
-    two_nodes_local_publish/1
-]).
-
-%% internals
--export([recipient_loop/1]).
--export([called_loop/1, called_loop_that_crashes/1]).
--export([process_groups_process_exit_callback_dummy/4]).
-
-%% include
--include_lib("common_test/include/ct.hrl").
-
-
-%% ===================================================================
-%% Callbacks
-%% ===================================================================
-
-%% -------------------------------------------------------------------
-%% Function: all() -> GroupsAndTestCases | {skip,Reason}
-%% GroupsAndTestCases = [{group,GroupName} | TestCase]
-%% GroupName = atom()
-%% TestCase = atom()
-%% Reason = term()
-%% -------------------------------------------------------------------
-all() ->
-    [
-        {group, single_node_process_groups},
-        {group, two_nodes_process_groups}
-    ].
-
-%% -------------------------------------------------------------------
-%% Function: groups() -> [Group]
-%% Group = {GroupName,Properties,GroupsAndTestCases}
-%% GroupName = atom()
-%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
-%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
-%% TestCase = atom()
-%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
-%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
-%%			   repeat_until_any_ok | repeat_until_any_fail
-%% N = integer() | forever
-%% -------------------------------------------------------------------
-groups() ->
-    [
-        {single_node_process_groups, [shuffle], [
-            single_node_join,
-            single_node_leave,
-            single_node_kill,
-            single_node_leave_and_kill_multi_groups,
-            single_node_publish,
-            single_node_multi_call,
-            single_node_multi_call_when_recipient_crashes,
-            single_node_meta,
-            single_node_callback_on_process_exit,
-            single_node_all_keys
-        ]},
-        {two_nodes_process_groups, [shuffle], [
-            two_nodes_kill,
-            two_nodes_publish,
-            two_nodes_multi_call,
-            two_nodes_local_members,
-            two_nodes_local_publish
-        ]}
-    ].
-%% -------------------------------------------------------------------
-%% Function: init_per_suite(Config0) ->
-%%				Config1 | {skip,Reason} |
-%%              {skip_and_save,Reason,Config1}
-%% Config0 = Config1 = [tuple()]
-%% Reason = term()
-%% -------------------------------------------------------------------
-init_per_suite(Config) ->
-    %% config
-    [
-        {slave_node_short_name, syn_slave}
-        | Config
-    ].
-
-%% -------------------------------------------------------------------
-%% Function: end_per_suite(Config0) -> void() | {save_config,Config1}
-%% Config0 = Config1 = [tuple()]
-%% -------------------------------------------------------------------
-end_per_suite(_Config) -> ok.
-
-%% -------------------------------------------------------------------
-%% Function: init_per_group(GroupName, Config0) ->
-%%				Config1 | {skip,Reason} |
-%%              {skip_and_save,Reason,Config1}
-%% GroupName = atom()
-%% Config0 = Config1 = [tuple()]
-%% Reason = term()
-%% -------------------------------------------------------------------
-init_per_group(two_nodes_process_groups, Config) ->
-    %% start slave
-    SlaveNodeShortName = proplists:get_value(slave_node_short_name, Config),
-    {ok, SlaveNode} = syn_test_suite_helper:start_slave(SlaveNodeShortName),
-    %% config
-    [
-        {slave_node, SlaveNode}
-        | Config
-    ];
-init_per_group(_GroupName, Config) -> Config.
-
-%% -------------------------------------------------------------------
-%% Function: end_per_group(GroupName, Config0) ->
-%%				void() | {save_config,Config1}
-%% GroupName = atom()
-%% Config0 = Config1 = [tuple()]
-%% -------------------------------------------------------------------
-end_per_group(two_nodes_process_groups, Config) ->
-    %% get slave node name
-    SlaveNodeShortName = proplists:get_value(slave_node_short_name, Config),
-    %% stop slave
-    syn_test_suite_helper:stop_slave(SlaveNodeShortName);
-end_per_group(_GroupName, _Config) ->
-    ok.
-
-% ----------------------------------------------------------------------------------------------------------
-% Function: init_per_testcase(TestCase, Config0) ->
-%				Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
-% TestCase = atom()
-% Config0 = Config1 = [tuple()]
-% Reason = term()
-% ----------------------------------------------------------------------------------------------------------
-init_per_testcase(_TestCase, Config) ->
-    Config.
-
-% ----------------------------------------------------------------------------------------------------------
-% Function: end_per_testcase(TestCase, Config0) ->
-%				void() | {save_config,Config1} | {fail,Reason}
-% TestCase = atom()
-% Config0 = Config1 = [tuple()]
-% Reason = term()
-% ----------------------------------------------------------------------------------------------------------
-end_per_testcase(_TestCase, Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    syn_test_suite_helper:clean_after_test(SlaveNode).
-
-%% ===================================================================
-%% Tests
-%% ===================================================================
-single_node_join(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% start process
-    Pid = syn_test_suite_helper:start_process(),
-    %% retrieve
-    [] = syn:get_members(<<"my group">>),
-    false = syn:member(Pid, <<"my group">>),
-    %% join
-    ok = syn:join(<<"my group">>, Pid),
-    %% allow to rejoin
-    ok = syn:join(<<"my group">>, Pid),
-    %% retrieve
-    [Pid] = syn:get_members(<<"my group">>),
-    true = syn:member(Pid, <<"my group">>),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid).
-
-single_node_leave(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% start process
-    Pid = syn_test_suite_helper:start_process(),
-    %% retrieve
-    [] = syn:get_members(<<"my group">>),
-    false = syn:member(Pid, <<"my group">>),
-    %% leave before join
-    {error, pid_not_in_group} = syn:leave(<<"my group">>, Pid),
-    %% join
-    ok = syn:join(<<"my group">>, Pid),
-    %% retrieve
-    [Pid] = syn:get_members(<<"my group">>),
-    true = syn:member(Pid, <<"my group">>),
-    %% leave
-    ok = syn:leave(<<"my group">>, Pid),
-    %% retrieve
-    [] = syn:get_members(<<"my group">>),
-    false = syn:member(Pid, <<"my group">>),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid).
-
-single_node_kill(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% start process
-    Pid = syn_test_suite_helper:start_process(),
-    %% retrieve
-    [] = syn:get_members(<<"my group 1">>),
-    [] = syn:get_members(<<"my group 2">>),
-    false = syn:member(Pid, <<"my group 1">>),
-    false = syn:member(Pid, <<"my group 2">>),
-    %% join
-    ok = syn:join(<<"my group 1">>, Pid),
-    ok = syn:join(<<"my group 2">>, Pid),
-    %% retrieve
-    [Pid] = syn:get_members(<<"my group 1">>),
-    [Pid] = syn:get_members(<<"my group 2">>),
-    true = syn:member(Pid, <<"my group 1">>),
-    true = syn:member(Pid, <<"my group 2">>),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid),
-    timer:sleep(100),
-    %% retrieve
-    [] = syn:get_members(<<"my group 1">>),
-    [] = syn:get_members(<<"my group 2">>),
-    false = syn:member(Pid, <<"my group 1">>),
-    false = syn:member(Pid, <<"my group 2">>).
-
-single_node_leave_and_kill_multi_groups(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% start process
-    Pid = syn_test_suite_helper:start_process(),
-    %% retrieve
-    [] = syn:get_members(<<"my group 1">>),
-    [] = syn:get_members(<<"my group 2">>),
-    false = syn:member(Pid, <<"my group 1">>),
-    false = syn:member(Pid, <<"my group 2">>),
-    %% join
-    ok = syn:join(<<"my group 1">>, Pid),
-    ok = syn:join(<<"my group 2">>, Pid),
-    %% retrieve
-    [Pid] = syn:get_members(<<"my group 1">>),
-    [Pid] = syn:get_members(<<"my group 2">>),
-    true = syn:member(Pid, <<"my group 1">>),
-    true = syn:member(Pid, <<"my group 2">>),
-    %% leave group 1
-    ok = syn:leave(<<"my group 1">>, Pid),
-    %% retrieve
-    [] = syn:get_members(<<"my group 1">>),
-    [Pid] = syn:get_members(<<"my group 2">>),
-    false = syn:member(Pid, <<"my group 1">>),
-    true = syn:member(Pid, <<"my group 2">>),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid),
-    timer:sleep(100),
-    %% retrieve
-    [] = syn:get_members(<<"my group 1">>),
-    [] = syn:get_members(<<"my group 2">>),
-    false = syn:member(Pid, <<"my group 1">>),
-    false = syn:member(Pid, <<"my group 2">>).
-
-single_node_publish(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% start processes
-    ResultPid = self(),
-    F = fun() -> recipient_loop(ResultPid) end,
-    Pid1 = syn_test_suite_helper:start_process(F),
-    Pid2 = syn_test_suite_helper:start_process(F),
-    %% join
-    ok = syn:join(<<"my group">>, Pid1),
-    ok = syn:join(<<"my group">>, Pid2),
-    %% publish
-    {ok, 2} = syn:publish(<<"my group">>, {test, message}),
-    %% check publish was received
-    receive
-        {received, Pid1, {test, message}} -> ok
-    after 2000 ->
-        ok = published_message_was_not_received_by_pid1
-    end,
-    receive
-        {received, Pid2, {test, message}} -> ok
-    after 2000 ->
-        ok = published_message_was_not_received_by_pid2
-    end,
-    %% kill processes
-    syn_test_suite_helper:kill_process(Pid1),
-    syn_test_suite_helper:kill_process(Pid2).
-
-single_node_multi_call(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% start processes
-    Pid1 = syn_test_suite_helper:start_process(fun() -> called_loop(pid1) end),
-    Pid2 = syn_test_suite_helper:start_process(fun() -> called_loop(pid2) end),
-    PidUnresponsive = syn_test_suite_helper:start_process(),
-    %% register
-    ok = syn:join(<<"my group">>, Pid1),
-    ok = syn:join(<<"my group">>, Pid2),
-    ok = syn:join(<<"my group">>, PidUnresponsive),
-    %% call
-    {Replies, BadPids} = syn:multi_call(<<"my group">>, get_pid_name),
-    %% check responses
-    2 = length(Replies),
-    pid1 = proplists:get_value(Pid1, Replies),
-    pid2 = proplists:get_value(Pid2, Replies),
-    [PidUnresponsive] = BadPids,
-    %% kill processes
-    syn_test_suite_helper:kill_process(Pid1),
-    syn_test_suite_helper:kill_process(Pid2),
-    syn_test_suite_helper:kill_process(PidUnresponsive).
-
-single_node_multi_call_when_recipient_crashes(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% start processes
-    Pid1 = syn_test_suite_helper:start_process(fun() -> called_loop(pid1) end),
-    Pid2 = syn_test_suite_helper:start_process(fun() -> called_loop(pid2) end),
-    PidCrashes = syn_test_suite_helper:start_process(fun() -> called_loop_that_crashes(pid_crashes) end),
-    %% register
-    ok = syn:join(<<"my group">>, Pid1),
-    ok = syn:join(<<"my group">>, Pid2),
-    ok = syn:join(<<"my group">>, PidCrashes),
-    %% call
-    {Time, {Replies, BadPids}} = timer:tc(syn, multi_call, [<<"my group">>, get_pid_name]),
-    %% check that pid2 was monitored, no need to wait for timeout
-    true = Time / 1000 < 1000,
-    %% check responses
-    2 = length(Replies),
-    pid1 = proplists:get_value(Pid1, Replies),
-    pid2 = proplists:get_value(Pid2, Replies),
-    [PidCrashes] = BadPids,
-    %% kill processes
-    syn_test_suite_helper:kill_process(Pid1),
-    syn_test_suite_helper:kill_process(Pid2).
-
-single_node_meta(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% start process
-    Pid = syn_test_suite_helper:start_process(),
-    %% retrieve
-    [] = syn:get_members(<<"my group">>, with_meta),
-    false = syn:member(Pid, <<"my group">>),
-    %% join
-    ok = syn:join(<<"my group">>, Pid, {some, meta}),
-    %% retrieve
-    [{Pid, {some, meta}}] = syn:get_members(<<"my group">>, with_meta),
-    %% allow to rejoin to update meta
-    ok = syn:join(<<"my group">>, Pid, {updated, meta}),
-    %% retrieve
-    [{Pid, {updated, meta}}] = syn:get_members(<<"my group">>, with_meta),
-    %% leave
-    ok = syn:leave(<<"my group">>, Pid),
-    %% retrieve
-    [] = syn:get_members(<<"my group">>),
-    false = syn:member(Pid, <<"my group">>),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid).
-
-single_node_callback_on_process_exit(_Config) ->
-    CurrentNode = node(),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% load configuration variables from syn-test.config => this defines the callback
-    syn_test_suite_helper:set_environment_variables(),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% register global process
-    ResultPid = self(),
-    global:register_name(syn_process_groups_SUITE_result, ResultPid),
-    %% start process
-    Pid = syn_test_suite_helper:start_process(),
-    %% register
-    ok = syn:join(<<"my group">>, Pid, {some, meta, 1}),
-    ok = syn:join(<<"my other group">>, Pid, {some, meta, 2}),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid),
-    %% check callback were triggered
-    receive
-        {exited, CurrentNode, <<"my group">>, Pid, {some, meta, 1}, killed} -> ok
-    after 2000 ->
-        ok = process_groups_exit_callback_was_not_called_from_local_node
-    end,
-    receive
-        {exited, CurrentNode, <<"my other group">>, Pid, {some, meta, 2}, killed} -> ok
-    after 2000 ->
-        ok = process_groups_exit_callback_was_not_called_from_local_node
-    end,
-    %% unregister
-    global:unregister_name(syn_process_groups_SUITE_result).
-
-%% covering bug <https://github.com/ostinelli/syn/issues/32>
-single_node_all_keys(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% start process
-    Pid = syn_test_suite_helper:start_process(),
-    %% join with complex names
-    GroupNames = [
-        "mygroup",
-        <<"mygroup">>,
-        mygroup,
-        {mygroup},
-        {mygroup, 12345},
-        {mygroup, {other, <<"mygroup">>}},
-        [mygroup, {other, <<"mygroup">>}],
-        {mygroup, {other, <<"mygroup">>}, [mygroup, {other, <<"mygroup">>}]}
-    ],
-    F = fun(GroupName) ->
-        ok = syn:join(GroupName, Pid),
-        %% retrieve
-        [Pid] = syn:get_members(GroupName),
-        true = syn:member(Pid, GroupName)
-    end,
-    lists:foreach(F, GroupNames),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid).
-
-two_nodes_kill(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    rpc:call(SlaveNode, mnesia, schema_location, [ram]),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
-    %% start processes
-    PidLocal = syn_test_suite_helper:start_process(),
-    PidSlave = syn_test_suite_helper:start_process(SlaveNode),
-    %% retrieve
-    [] = syn:get_members(<<"my group">>),
-    false = syn:member(PidLocal, <<"my group">>),
-    false = syn:member(PidSlave, <<"my group">>),
-    [] = rpc:call(SlaveNode, syn, get_members, [<<"my group">>]),
-    false = rpc:call(SlaveNode, syn, member, [PidLocal, <<"my group">>]),
-    false = rpc:call(SlaveNode, syn, member, [PidSlave, <<"my group">>]),
-    %% register
-    ok = syn:join(<<"my group">>, PidSlave),
-    ok = rpc:call(SlaveNode, syn, join, [<<"my group">>, PidLocal]),
-    %% retrieve, pid should have the same order in all nodes
-    [PidSlave, PidLocal] = syn:get_members(<<"my group">>),
-    [PidSlave, PidLocal] = rpc:call(SlaveNode, syn, get_members, [<<"my group">>]),
-    %% kill processes
-    syn_test_suite_helper:kill_process(PidLocal),
-    syn_test_suite_helper:kill_process(PidSlave),
-    timer:sleep(100),
-    %% retrieve
-    [] = syn:get_members(<<"my group">>),
-    false = syn:member(PidLocal, <<"my group">>),
-    false = syn:member(PidSlave, <<"my group">>),
-    [] = rpc:call(SlaveNode, syn, get_members, [<<"my group">>]),
-    false = rpc:call(SlaveNode, syn, member, [PidLocal, <<"my group">>]),
-    false = rpc:call(SlaveNode, syn, member, [PidSlave, <<"my group">>]).
-
-two_nodes_publish(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    rpc:call(SlaveNode, mnesia, schema_location, [ram]),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
-    %% start process
-    ResultPid = self(),
-    F = fun() -> recipient_loop(ResultPid) end,
-    PidLocal = syn_test_suite_helper:start_process(F),
-    PidSlave = syn_test_suite_helper:start_process(SlaveNode, F),
-    %% register
-    ok = syn:join(<<"my group">>, PidSlave),
-    ok = rpc:call(SlaveNode, syn, join, [<<"my group">>, PidLocal]),
-    %% publish
-    syn:publish(<<"my group">>, {test, message}),
-    %% check publish was received
-    receive
-        {received, PidLocal, {test, message}} -> ok
-    after 2000 ->
-        ok = published_message_was_not_received_by_pidlocal
-    end,
-    receive
-        {received, PidSlave, {test, message}} -> ok
-    after 2000 ->
-        ok = published_message_was_not_received_by_pidslave
-    end,
-    %% kill processes
-    syn_test_suite_helper:kill_process(PidLocal),
-    syn_test_suite_helper:kill_process(PidSlave).
-
-two_nodes_multi_call(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    rpc:call(SlaveNode, mnesia, schema_location, [ram]),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
-    %% start processes
-    PidLocal = syn_test_suite_helper:start_process(fun() -> called_loop(pid1) end),
-    PidSlave = syn_test_suite_helper:start_process(SlaveNode, fun() -> called_loop(pid2) end),
-    PidUnresponsive = syn_test_suite_helper:start_process(),
-    %% join
-    ok = syn:join(<<"my group">>, PidLocal),
-    ok = syn:join(<<"my group">>, PidSlave),
-    ok = syn:join(<<"my group">>, PidUnresponsive),
-    timer:sleep(100),
-    %% call
-    {Replies, BadPids} = syn:multi_call(<<"my group">>, get_pid_name, 3000),
-    %% check responses
-    2 = length(Replies),
-    pid1 = proplists:get_value(PidLocal, Replies),
-    pid2 = proplists:get_value(PidSlave, Replies),
-    [PidUnresponsive] = BadPids,
-    %% kill processes
-    syn_test_suite_helper:kill_process(PidLocal),
-    syn_test_suite_helper:kill_process(PidSlave),
-    syn_test_suite_helper:kill_process(PidUnresponsive).
-
-two_nodes_local_members(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    rpc:call(SlaveNode, mnesia, schema_location, [ram]),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
-    %% start processes
-    PidLocal1 = syn_test_suite_helper:start_process(),
-    PidLocal2 = syn_test_suite_helper:start_process(),
-    PidSlave = syn_test_suite_helper:start_process(SlaveNode),
-    %% join
-    ok = syn:join(<<"my group">>, PidLocal1, {meta, pid_local_1}),
-    ok = syn:join(<<"my group">>, PidLocal2, {meta, pid_local_2}),
-    ok = syn:join(<<"my group">>, PidSlave, {meta, pid_slave}),
-    timer:sleep(100),
-    %% retrieve, pid should have the same order in all nodes
-    [PidLocal1, PidLocal2] = syn:get_local_members(<<"my group">>),
-    [
-        {PidLocal1, {meta, pid_local_1}},
-        {PidLocal2, {meta, pid_local_2}}
-    ] = syn:get_local_members(<<"my group">>, with_meta),
-    %% local pids leave
-    ok = syn:leave(<<"my group">>, PidLocal1),
-    ok = syn:leave(<<"my group">>, PidLocal2),
-    %% retrieve, no more local pids
-    [] = syn:get_local_members(<<"my group">>),
-    %% kill processes
-    syn_test_suite_helper:kill_process(PidLocal1),
-    syn_test_suite_helper:kill_process(PidLocal2),
-    syn_test_suite_helper:kill_process(PidSlave).
-
-two_nodes_local_publish(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    rpc:call(SlaveNode, mnesia, schema_location, [ram]),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
-    %% start processes
-    ResultPid = self(),
-    F = fun() -> recipient_loop(ResultPid) end,
-    PidLocal1 = syn_test_suite_helper:start_process(F),
-    PidLocal2 = syn_test_suite_helper:start_process(F),
-    PidSlave = syn_test_suite_helper:start_process(SlaveNode, F),
-    %% join
-    ok = syn:join(<<"my group">>, PidLocal1, {meta, pid_local_1}),
-    ok = syn:join(<<"my group">>, PidLocal2, {meta, pid_local_2}),
-    ok = syn:join(<<"my group">>, PidSlave, {meta, pid_slave}),
-    %% publish
-    {ok, 2} = syn:publish_to_local(<<"my group">>, {test, message}),
-    %% check publish was received by local pids
-    receive
-        {received, PidLocal1, {test, message}} -> ok
-    after 2000 ->
-        ok = published_message_was_not_received_by_pid_local_1
-    end,
-    receive
-        {received, PidLocal2, {test, message}} -> ok
-    after 2000 ->
-        ok = published_message_was_not_received_by_pid_local_2
-    end,
-    receive
-        {received, PidSlave, {test, message}} ->
-            ko = published_message_was_received_by_pid_slave
-    after 1000 ->
-        ok
-    end,
-    %% kill processes
-    syn_test_suite_helper:kill_process(PidLocal1),
-    syn_test_suite_helper:kill_process(PidLocal2),
-    syn_test_suite_helper:kill_process(PidSlave).
-
-%% ===================================================================
-%% Internal
-%% ===================================================================
-recipient_loop(Pid) ->
-    receive
-        Message -> Pid ! {received, self(), Message}
-    end.
-
-called_loop(PidName) ->
-    receive
-        {syn_multi_call, CallerPid, get_pid_name} -> syn:multi_call_reply(CallerPid, PidName)
-    end.
-
-called_loop_that_crashes(_PidName) ->
-    receive
-        {syn_multi_call, _CallerPid, get_pid_name} -> exit(recipient_crashed_on_purpose)
-    end.
-
-process_groups_process_exit_callback_dummy(Name, Pid, Meta, Reason) ->
-    global:send(syn_process_groups_SUITE_result, {exited, node(), Name, Pid, Meta, Reason}).

+ 0 - 229
test/syn_groups_consistency_SUITE.erl

@@ -1,229 +0,0 @@
-%% ==========================================================================================================
-%% Syn - A global Process Registry and Process Group manager.
-%%
-%% The MIT License (MIT)
-%%
-%% Copyright (c) 2016 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a copy
-%% of this software and associated documentation files (the "Software"), to deal
-%% in the Software without restriction, including without limitation the rights
-%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-%% copies of the Software, and to permit persons to whom the Software is
-%% furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-%% THE SOFTWARE.
--module(syn_groups_consistency_SUITE).
-
-%% callbacks
--export([all/0]).
--export([init_per_suite/1, end_per_suite/1]).
--export([groups/0, init_per_group/2, end_per_group/2]).
--export([init_per_testcase/2, end_per_testcase/2]).
-
-%% tests
--export([
-    two_nodes_netsplit_when_there_are_no_conflicts/1
-]).
-
-%% include
--include_lib("common_test/include/ct.hrl").
-
-
-%% ===================================================================
-%% Callbacks
-%% ===================================================================
-
-%% -------------------------------------------------------------------
-%% Function: all() -> GroupsAndTestCases | {skip,Reason}
-%% GroupsAndTestCases = [{group,GroupName} | TestCase]
-%% GroupName = atom()
-%% TestCase = atom()
-%% Reason = term()
-%% -------------------------------------------------------------------
-all() ->
-    [
-        {group, two_nodes_netsplits}
-    ].
-
-%% -------------------------------------------------------------------
-%% Function: groups() -> [Group]
-%% Group = {GroupName,Properties,GroupsAndTestCases}
-%% GroupName = atom()
-%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
-%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
-%% TestCase = atom()
-%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
-%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
-%%			   repeat_until_any_ok | repeat_until_any_fail
-%% N = integer() | forever
-%% -------------------------------------------------------------------
-groups() ->
-    [
-        {two_nodes_netsplits, [shuffle], [
-            two_nodes_netsplit_when_there_are_no_conflicts
-        ]}
-    ].
-%% -------------------------------------------------------------------
-%% Function: init_per_suite(Config0) ->
-%%				Config1 | {skip,Reason} |
-%%              {skip_and_save,Reason,Config1}
-%% Config0 = Config1 = [tuple()]
-%% Reason = term()
-%% -------------------------------------------------------------------
-init_per_suite(Config) ->
-    %% init
-    SlaveNodeShortName = syn_slave,
-    %% start slave
-    {ok, SlaveNode} = syn_test_suite_helper:start_slave(SlaveNodeShortName),
-    %% config
-    [
-        {slave_node_short_name, SlaveNodeShortName},
-        {slave_node, SlaveNode}
-        | Config
-    ].
-
-%% -------------------------------------------------------------------
-%% Function: end_per_suite(Config0) -> void() | {save_config,Config1}
-%% Config0 = Config1 = [tuple()]
-%% -------------------------------------------------------------------
-end_per_suite(Config) ->
-    %% get slave node name
-    SlaveNodeShortName = proplists:get_value(slave_node_short_name, Config),
-    %% stop slave
-    syn_test_suite_helper:stop_slave(SlaveNodeShortName).
-
-%% -------------------------------------------------------------------
-%% Function: init_per_group(GroupName, Config0) ->
-%%				Config1 | {skip,Reason} |
-%%              {skip_and_save,Reason,Config1}
-%% GroupName = atom()
-%% Config0 = Config1 = [tuple()]
-%% Reason = term()
-%% -------------------------------------------------------------------
-init_per_group(_GroupName, Config) -> Config.
-
-%% -------------------------------------------------------------------
-%% Function: end_per_group(GroupName, Config0) ->
-%%				void() | {save_config,Config1}
-%% GroupName = atom()
-%% Config0 = Config1 = [tuple()]
-%% -------------------------------------------------------------------
-end_per_group(_GroupName, _Config) -> ok.
-
-% ----------------------------------------------------------------------------------------------------------
-% Function: init_per_testcase(TestCase, Config0) ->
-%				Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
-% TestCase = atom()
-% Config0 = Config1 = [tuple()]
-% Reason = term()
-% ----------------------------------------------------------------------------------------------------------
-init_per_testcase(_TestCase, Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    rpc:call(SlaveNode, mnesia, schema_location, [ram]),
-    %% return
-    Config.
-
-% ----------------------------------------------------------------------------------------------------------
-% Function: end_per_testcase(TestCase, Config0) ->
-%				void() | {save_config,Config1} | {fail,Reason}
-% TestCase = atom()
-% Config0 = Config1 = [tuple()]
-% Reason = term()
-% ----------------------------------------------------------------------------------------------------------
-end_per_testcase(_TestCase, Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    syn_test_suite_helper:clean_after_test(SlaveNode).
-
-%% ===================================================================
-%% Tests
-%% ===================================================================
-two_nodes_netsplit_when_there_are_no_conflicts(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    CurrentNode = node(),
-
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    rpc:call(SlaveNode, mnesia, schema_location, [ram]),
-
-    %% start syn
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
-
-    %% start processes
-    LocalPid = syn_test_suite_helper:start_process(),
-    SlavePidLocal = syn_test_suite_helper:start_process(SlaveNode),
-    SlavePidSlave = syn_test_suite_helper:start_process(SlaveNode),
-
-    %% join
-    ok = syn:join({group, tuple_name}, LocalPid),
-    ok = syn:join({group, tuple_name}, SlavePidLocal),    %% joined on local node
-    ok = rpc:call(SlaveNode, syn, join, [{group, tuple_name}, SlavePidSlave]),    %% joined on slave node
-    timer:sleep(100),
-
-    %% check tables
-    3 = mnesia:table_info(syn_groups_table, size),
-    3 = rpc:call(SlaveNode, mnesia, table_info, [syn_groups_table, size]),
-
-    LocalActiveReplicas = mnesia:table_info(syn_groups_table, active_replicas),
-    2 = length(LocalActiveReplicas),
-    true = lists:member(SlaveNode, LocalActiveReplicas),
-    true = lists:member(CurrentNode, LocalActiveReplicas),
-
-    SlaveActiveReplicas = rpc:call(SlaveNode, mnesia, table_info, [syn_groups_table, active_replicas]),
-    2 = length(SlaveActiveReplicas),
-    true = lists:member(SlaveNode, SlaveActiveReplicas),
-    true = lists:member(CurrentNode, SlaveActiveReplicas),
-
-    %% simulate net split
-    syn_test_suite_helper:disconnect_node(SlaveNode),
-    timer:sleep(1000),
-
-    %% check tables
-    1 = mnesia:table_info(syn_groups_table, size),
-    [CurrentNode] = mnesia:table_info(syn_groups_table, active_replicas),
-
-    %% reconnect
-    syn_test_suite_helper:connect_node(SlaveNode),
-    timer:sleep(1000),
-
-    %% check tables
-    3 = mnesia:table_info(syn_groups_table, size),
-    3 = rpc:call(SlaveNode, mnesia, table_info, [syn_groups_table, size]),
-
-    LocalActiveReplicasAfter = mnesia:table_info(syn_groups_table, active_replicas),
-    2 = length(LocalActiveReplicasAfter),
-    true = lists:member(SlaveNode, LocalActiveReplicasAfter),
-    true = lists:member(CurrentNode, LocalActiveReplicasAfter),
-
-    SlaveActiveReplicasAfter = rpc:call(SlaveNode, mnesia, table_info, [syn_groups_table, active_replicas]),
-    2 = length(SlaveActiveReplicasAfter),
-    true = lists:member(SlaveNode, SlaveActiveReplicasAfter),
-    true = lists:member(CurrentNode, SlaveActiveReplicasAfter),
-
-    %% check grouos
-    3 = length(syn:get_members({group, tuple_name})),
-    true = syn:member(LocalPid, {group, tuple_name}),
-    true = syn:member(SlavePidLocal, {group, tuple_name}),
-    true = syn:member(SlavePidSlave, {group, tuple_name}),
-    3 = length(rpc:call(SlaveNode, syn, get_members, [{group, tuple_name}])),
-    true = rpc:call(SlaveNode, syn, member, [LocalPid, {group, tuple_name}]),
-    true = rpc:call(SlaveNode, syn, member, [SlavePidLocal, {group, tuple_name}]),
-    true = rpc:call(SlaveNode, syn, member, [SlavePidSlave, {group, tuple_name}]).

+ 338 - 432
test/syn_registry_SUITE.erl

@@ -3,7 +3,7 @@
 %%
 %% The MIT License (MIT)
 %%
-%% Copyright (c) 2015 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
+%% Copyright (c) 2015-2019 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
 %%
 %% Permission is hereby granted, free of charge, to any person obtaining a copy
 %% of this software and associated documentation files (the "Software"), to deal
@@ -33,27 +33,19 @@
 
 %% tests
 -export([
-    single_node_when_mnesia_is_ram_find_by_key/1,
-    single_node_when_mnesia_is_ram_find_by_key_with_meta/1,
-    single_node_when_mnesia_is_ram_find_by_pid/1,
-    single_node_when_mnesia_is_ram_find_by_pid_with_meta/1,
-    single_node_when_mnesia_is_ram_with_gen_server_name/1,
-    single_node_when_mnesia_is_ram_re_register_error/1,
-    single_node_when_mnesia_is_ram_unregister/1,
-    single_node_when_mnesia_is_ram_process_count/1,
-    single_node_when_mnesia_is_ram_callback_on_process_exit/1,
-    single_node_when_mnesia_is_disc_find_by_key/1
+    single_node_register_and_monitor/1,
+    single_node_register_and_unregister/1,
+    single_node_registration_errors/1,
+    single_node_registry_count/1
 ]).
 -export([
-    two_nodes_when_mnesia_is_ram_find_by_key/1,
-    two_nodes_when_mnesia_is_ram_find_by_key_with_meta/1,
-    two_nodes_when_mnesia_is_ram_process_count/1,
-    two_nodes_when_mnesia_is_ram_callback_on_process_exit/1,
-    two_nodes_when_mnesia_is_disc_find_by_pid/1
+    two_nodes_register_monitor_and_unregister/1,
+    two_nodes_registry_count/1
+]).
+-export([
+    three_nodes_consistency_partial_net_split/1,
+    three_nodes_consistency_full_net_split/1
 ]).
-
-%% internals
--export([registry_process_exit_callback_dummy/4]).
 
 %% include
 -include_lib("common_test/include/ct.hrl").
@@ -73,7 +65,8 @@
 all() ->
     [
         {group, single_node_process_registration},
-        {group, two_nodes_process_registration}
+        {group, two_nodes_process_registration},
+        {group, three_nodes_process_registration}
     ].
 
 %% -------------------------------------------------------------------
@@ -91,23 +84,18 @@ all() ->
 groups() ->
     [
         {single_node_process_registration, [shuffle], [
-            single_node_when_mnesia_is_ram_find_by_key,
-            single_node_when_mnesia_is_ram_find_by_key_with_meta,
-            single_node_when_mnesia_is_ram_find_by_pid,
-            single_node_when_mnesia_is_ram_find_by_pid_with_meta,
-            single_node_when_mnesia_is_ram_with_gen_server_name,
-            single_node_when_mnesia_is_ram_re_register_error,
-            single_node_when_mnesia_is_ram_unregister,
-            single_node_when_mnesia_is_ram_process_count,
-            single_node_when_mnesia_is_ram_callback_on_process_exit,
-            single_node_when_mnesia_is_disc_find_by_key
+            single_node_register_and_monitor,
+            single_node_register_and_unregister,
+            single_node_registration_errors,
+            single_node_registry_count
         ]},
         {two_nodes_process_registration, [shuffle], [
-            two_nodes_when_mnesia_is_ram_find_by_key,
-            two_nodes_when_mnesia_is_ram_find_by_key_with_meta,
-            two_nodes_when_mnesia_is_ram_process_count,
-            two_nodes_when_mnesia_is_ram_callback_on_process_exit,
-            two_nodes_when_mnesia_is_disc_find_by_pid
+            two_nodes_register_monitor_and_unregister,
+            two_nodes_registry_count
+        ]},
+        {three_nodes_process_registration, [shuffle], [
+            three_nodes_consistency_partial_net_split,
+            three_nodes_consistency_full_net_split
         ]}
     ].
 %% -------------------------------------------------------------------
@@ -118,17 +106,14 @@ groups() ->
 %% Reason = term()
 %% -------------------------------------------------------------------
 init_per_suite(Config) ->
-    %% config
-    [
-        {slave_node_short_name, syn_slave}
-        | Config
-    ].
+    Config.
 
 %% -------------------------------------------------------------------
 %% Function: end_per_suite(Config0) -> void() | {save_config,Config1}
 %% Config0 = Config1 = [tuple()]
 %% -------------------------------------------------------------------
-end_per_suite(_Config) -> ok.
+end_per_suite(_Config) ->
+    ok.
 
 %% -------------------------------------------------------------------
 %% Function: init_per_group(GroupName, Config0) ->
@@ -140,14 +125,17 @@ end_per_suite(_Config) -> ok.
 %% -------------------------------------------------------------------
 init_per_group(two_nodes_process_registration, Config) ->
     %% start slave
-    SlaveNodeShortName = proplists:get_value(slave_node_short_name, Config),
-    {ok, SlaveNode} = syn_test_suite_helper:start_slave(SlaveNodeShortName),
+    {ok, SlaveNode} = syn_test_suite_helper:start_slave(syn_slave),
     %% config
-    [
-        {slave_node, SlaveNode}
-        | Config
-    ];
-init_per_group(_GroupName, Config) -> Config.
+    [{slave_node, SlaveNode} | Config];
+init_per_group(three_nodes_process_registration, Config) ->
+    %% start slave
+    {ok, SlaveNode1} = syn_test_suite_helper:start_slave(syn_slave_1),
+    {ok, SlaveNode2} = syn_test_suite_helper:start_slave(syn_slave_2),
+    %% config
+    [{slave_node_1, SlaveNode1}, {slave_node_2, SlaveNode2} | Config];
+init_per_group(_GroupName, Config) ->
+    Config.
 
 %% -------------------------------------------------------------------
 %% Function: end_per_group(GroupName, Config0) ->
@@ -156,10 +144,16 @@ init_per_group(_GroupName, Config) -> Config.
 %% Config0 = Config1 = [tuple()]
 %% -------------------------------------------------------------------
 end_per_group(two_nodes_process_registration, Config) ->
-    %% get slave node name
-    SlaveNodeShortName = proplists:get_value(slave_node_short_name, Config),
-    %% stop slave
-    syn_test_suite_helper:stop_slave(SlaveNodeShortName);
+    SlaveNode = proplists:get_value(slave_node, Config),
+    syn_test_suite_helper:connect_node(SlaveNode),
+    syn_test_suite_helper:stop_slave(syn_slave);
+end_per_group(three_nodes_process_registration, Config) ->
+    SlaveNode1 = proplists:get_value(slave_node_1, Config),
+    syn_test_suite_helper:connect_node(SlaveNode1),
+    SlaveNode2 = proplists:get_value(slave_node_2, Config),
+    syn_test_suite_helper:connect_node(SlaveNode2),
+    syn_test_suite_helper:stop_slave(syn_slave_1),
+    syn_test_suite_helper:stop_slave(syn_slave_2);
 end_per_group(_GroupName, _Config) ->
     ok.
 
@@ -180,468 +174,380 @@ init_per_testcase(_TestCase, Config) ->
 % Config0 = Config1 = [tuple()]
 % Reason = term()
 % ----------------------------------------------------------------------------------------------------------
-end_per_testcase(_TestCase, Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    syn_test_suite_helper:clean_after_test(SlaveNode).
+end_per_testcase(_, _Config) ->
+    syn_test_suite_helper:clean_after_test().
 
 %% ===================================================================
 %% Tests
 %% ===================================================================
-single_node_when_mnesia_is_ram_find_by_key(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
+single_node_register_and_monitor(_Config) ->
     %% start
     ok = syn:start(),
-    ok = syn:init(),
-    %% start process
+    %% start processes
     Pid = syn_test_suite_helper:start_process(),
+    PidWithMeta = syn_test_suite_helper:start_process(),
     %% retrieve
-    undefined = syn:find_by_key(<<"my proc">>),
+    undefined = syn:whereis(<<"my proc">>),
     %% register
     ok = syn:register(<<"my proc">>, Pid),
+    ok = syn:register(<<"my proc 2">>, Pid),
+    ok = syn:register(<<"my proc with meta">>, PidWithMeta, {meta, <<"meta">>}),
     %% retrieve
-    Pid = syn:find_by_key(<<"my proc">>),
+    Pid = syn:whereis(<<"my proc">>),
+    Pid = syn:whereis(<<"my proc 2">>),
+    {PidWithMeta, {meta, <<"meta">>}} = syn:whereis(<<"my proc with meta">>, with_meta),
     %% kill process
     syn_test_suite_helper:kill_process(Pid),
+    syn_test_suite_helper:kill_process(PidWithMeta),
     timer:sleep(100),
     %% retrieve
-    undefined = syn:find_by_key(<<"my proc">>).
+    undefined = syn:whereis(<<"my proc">>),
+    undefined = syn:whereis(<<"my proc 2">>),
+    undefined = syn:whereis(<<"my proc with meta">>).
 
-single_node_when_mnesia_is_ram_with_gen_server_name(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
+single_node_register_and_unregister(_Config) ->
     %% start
     ok = syn:start(),
-    ok = syn:init(),
-    %% retrieve
-    undefined = syn:whereis_name(<<"my proc">>),
-    %% register
-    {ok, Pid} = syn_test_gen_server:start_link(self(), {via, syn, <<"my proc">>}),
-    %% retrieve
-    Pid = syn:whereis_name(<<"my proc">>),
-    %% gen_server call messages
-    call_received = gen_server:call({via, syn, <<"my proc">>}, message_is_ignored),
-    %% gen_server cast messages
-    gen_server:cast({via, syn, <<"my proc">>}, message_is_ignored),
-    ok = receive
-        cast_received -> ok
-    after
-        1000 -> error_receiving_cast_message
-    end,
-    %% any other message
-    syn:send(<<"my proc">>, message_is_ignored),
-    ok = receive
-        info_received -> ok
-    after
-        1000 -> error_receiving_info_message
-    end,
-    %% stop process
-    syn_test_gen_server:stop({via, syn, <<"my proc">>}),
-    ok = receive
-        stop_received -> ok
-    after
-        1000 -> error_stopping_process
-    end,
-    %% wait for process to exit
-    timer:sleep(100),
-    %% retrieve
-    undefined = syn:find_by_key(<<"my proc">>).
-
-single_node_when_mnesia_is_ram_find_by_key_with_meta(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% start process
-    Pid1 = syn_test_suite_helper:start_process(),
-    Pid2 = syn_test_suite_helper:start_process(),
-    %% retrieve
-    undefined = syn:find_by_key(<<"my proc 1">>, with_meta),
-    undefined = syn:find_by_key(<<"my proc 2">>, with_meta),
-    %% register
-    Meta = [{some, 1}, {meta, <<"data">>}],
-    ok = syn:register(<<"my proc 1">>, Pid1, Meta),
-    ok = syn:register(<<"my proc 2">>, Pid2),
-    %% retrieve
-    {Pid1, Meta} = syn:find_by_key(<<"my proc 1">>, with_meta),
-    {Pid2, undefined} = syn:find_by_key(<<"my proc 2">>, with_meta),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid1),
-    syn_test_suite_helper:kill_process(Pid2),
-    timer:sleep(100),
-    %% retrieve
-    undefined = syn:find_by_key(<<"my proc 1">>, with_meta),
-    undefined = syn:find_by_key(<<"my proc 2">>, with_meta).
-
-single_node_when_mnesia_is_ram_find_by_pid(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
     %% start process
     Pid = syn_test_suite_helper:start_process(),
+    %% retrieve
+    undefined = syn:whereis(<<"my proc">>),
     %% register
     ok = syn:register(<<"my proc">>, Pid),
+    ok = syn:register(<<"my proc 2">>, Pid),
     %% retrieve
-    <<"my proc">> = syn:find_by_pid(Pid),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid),
-    timer:sleep(100),
-    %% retrieve
-    undefined = syn:find_by_pid(Pid).
-
-single_node_when_mnesia_is_ram_find_by_pid_with_meta(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% start process
-    Pid1 = syn_test_suite_helper:start_process(),
-    Pid2 = syn_test_suite_helper:start_process(),
+    Pid = syn:whereis(<<"my proc">>),
+    Pid = syn:whereis(<<"my proc 2">>),
+    %% unregister 1
+    ok = syn:unregister(<<"my proc">>),
     %% retrieve
-    undefined = syn:find_by_pid(Pid1, with_meta),
-    undefined = syn:find_by_pid(Pid2, with_meta),
-    %% register
-    Meta = [{some, 1}, {meta, <<"data">>}],
-    ok = syn:register(<<"my proc 1">>, Pid1, Meta),
-    ok = syn:register(<<"my proc 2">>, Pid2),
+    undefined = syn:whereis(<<"my proc">>),
+    Pid = syn:whereis(<<"my proc 2">>),
+    %% unregister 2
+    ok = syn:unregister(<<"my proc 2">>),
+    {error, undefined} = syn:unregister(<<"my proc 2">>),
     %% retrieve
-    {<<"my proc 1">>, Meta} = syn:find_by_pid(Pid1, with_meta),
-    {<<"my proc 2">>, undefined} = syn:find_by_pid(Pid2, with_meta),
+    undefined = syn:whereis(<<"my proc">>),
+    undefined = syn:whereis(<<"my proc 2">>),
     %% kill process
-    syn_test_suite_helper:kill_process(Pid1),
-    syn_test_suite_helper:kill_process(Pid2),
-    timer:sleep(100),
-    %% retrieve
-    undefined = syn:find_by_pid(Pid1, with_meta),
-    undefined = syn:find_by_pid(Pid2, with_meta).
+    syn_test_suite_helper:kill_process(Pid).
 
-single_node_when_mnesia_is_ram_re_register_error(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
+single_node_registration_errors(_Config) ->
     %% start
     ok = syn:start(),
-    ok = syn:init(),
     %% start process
     Pid = syn_test_suite_helper:start_process(),
     Pid2 = syn_test_suite_helper:start_process(),
     %% register
     ok = syn:register(<<"my proc">>, Pid),
-    %% re-register same process
-    ok = syn:register(<<"my proc">>, Pid, {with, meta}),
-    %% register same process with another name
-    {error, pid_already_registered} = syn:register(<<"my proc 2">>, Pid),
-    %% register another process
     {error, taken} = syn:register(<<"my proc">>, Pid2),
-    %% retrieve
-    {Pid, {with, meta}} = syn:find_by_key(<<"my proc">>, with_meta),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid),
-    timer:sleep(100),
-    %% retrieve
-    undefined = syn:find_by_key(<<"my proc">>),
-    %% reuse
-    ok = syn:register(<<"my proc">>, Pid2),
-    %% retrieve
-    Pid2 = syn:find_by_key(<<"my proc">>),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid),
-    timer:sleep(100),
-    %% retrieve
-    undefined = syn:find_by_pid(Pid).
-
-single_node_when_mnesia_is_ram_unregister(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% start process
-    Pid = syn_test_suite_helper:start_process(),
-    %% unregister
-    {error, undefined} = syn:unregister(<<"my proc">>),
-    %% register
-    ok = syn:register(<<"my proc">>, Pid),
-    %% retrieve
-    Pid = syn:find_by_key(<<"my proc">>),
-    %% unregister
-    ok = syn:unregister(<<"my proc">>),
-    %% retrieve
-    undefined = syn:find_by_key(<<"my proc">>),
-    undefined = syn:find_by_pid(Pid),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid).
-
-single_node_when_mnesia_is_ram_process_count(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% count
-    0 = syn:registry_count(),
-    %% start process
-    Pid1 = syn_test_suite_helper:start_process(),
-    Pid2 = syn_test_suite_helper:start_process(),
-    Pid3 = syn_test_suite_helper:start_process(),
-    %% register
-    ok = syn:register(1, Pid1),
-    ok = syn:register(2, Pid2),
-    ok = syn:register(3, Pid3),
-    %% count
-    3 = syn:registry_count(),
     %% kill processes
-    syn_test_suite_helper:kill_process(Pid1),
-    syn_test_suite_helper:kill_process(Pid2),
-    syn_test_suite_helper:kill_process(Pid3),
-    timer:sleep(100),
-    %% count
-    0 = syn:registry_count().
-
-single_node_when_mnesia_is_ram_callback_on_process_exit(_Config) ->
-    CurrentNode = node(),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    %% load configuration variables from syn-test.config => this defines the callback
-    syn_test_suite_helper:set_environment_variables(),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% register global process
-    ResultPid = self(),
-    global:register_name(syn_register_process_SUITE_result, ResultPid),
-    %% start process
-    Pid = syn_test_suite_helper:start_process(),
-    %% register
-    Meta = {some, meta},
-    ok = syn:register(<<"my proc">>, Pid, Meta),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid),
-    %% check callback were triggered
-    receive
-        {exited, CurrentNode, <<"my proc">>, Pid, Meta, killed} -> ok
-    after 2000 ->
-        ok = registry_process_exit_callback_was_not_called_from_local_node
-    end,
-    %% unregister
-    global:unregister_name(syn_register_process_SUITE_result).
-
-single_node_when_mnesia_is_disc_find_by_key(_Config) ->
-    %% set schema location
-    application:set_env(mnesia, schema_location, disc),
-    %% create schema
-    mnesia:create_schema([node()]),
-    %% start
-    ok = syn:start(),
-    ok = syn:init(),
-    %% start process
-    Pid = syn_test_suite_helper:start_process(),
-    %% retrieve
-    undefined = syn:find_by_key(<<"my proc">>),
-    %% register
-    ok = syn:register(<<"my proc">>, Pid),
-    %% retrieve
-    Pid = syn:find_by_key(<<"my proc">>),
-    %% kill process
     syn_test_suite_helper:kill_process(Pid),
+    syn_test_suite_helper:kill_process(Pid2),
     timer:sleep(100),
     %% retrieve
-    undefined = syn:find_by_key(<<"my proc">>).
+    undefined = syn:whereis(<<"my proc">>),
+    %% try registering a dead pid
+    {error, not_alive} = syn:register(<<"my proc">>, Pid).
 
-two_nodes_when_mnesia_is_ram_find_by_key(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    rpc:call(SlaveNode, mnesia, schema_location, [ram]),
+single_node_registry_count(_Config) ->
     %% start
     ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
     %% start process
     Pid = syn_test_suite_helper:start_process(),
-    %% retrieve
-    undefined = syn:find_by_key(<<"my proc">>),
-    undefined = rpc:call(SlaveNode, syn, find_by_key, [<<"my proc">>]),
+    Pid2 = syn_test_suite_helper:start_process(),
+    PidUnregistered = syn_test_suite_helper:start_process(),
     %% register
     ok = syn:register(<<"my proc">>, Pid),
-    %% retrieve
-    Pid = syn:find_by_key(<<"my proc">>),
-    Pid = rpc:call(SlaveNode, syn, find_by_key, [<<"my proc">>]),
-    %% kill process
+    ok = syn:register(<<"my proc 2">>, Pid2),
+    %% count
+    2 = syn:registry_count(),
+    2 = syn:registry_count(node()),
+    %% kill & unregister
     syn_test_suite_helper:kill_process(Pid),
+    ok = syn:unregister(<<"my proc 2">>),
+    syn_test_suite_helper:kill_process(PidUnregistered),
     timer:sleep(100),
-    %% retrieve
-    undefined = syn:find_by_key(<<"my proc">>),
-    undefined = rpc:call(SlaveNode, syn, find_by_key, [<<"my proc">>]).
+    %% count
+    0 = syn:registry_count(),
+    0 = syn:registry_count(node()).
 
-two_nodes_when_mnesia_is_ram_find_by_key_with_meta(Config) ->
+two_nodes_register_monitor_and_unregister(Config) ->
     %% get slave
     SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    rpc:call(SlaveNode, mnesia, schema_location, [ram]),
     %% start
     ok = syn:start(),
-    ok = syn:init(),
     ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
     timer:sleep(100),
-    %% start process
-    Pid1 = syn_test_suite_helper:start_process(),
-    Pid2 = syn_test_suite_helper:start_process(),
+    %% start processes
+    LocalPid = syn_test_suite_helper:start_process(),
+    RemotePid = syn_test_suite_helper:start_process(SlaveNode),
+    RemotePidRegRemote = syn_test_suite_helper:start_process(SlaveNode),
     %% retrieve
-    undefined = syn:find_by_key(<<"my proc 1">>),
-    undefined = rpc:call(SlaveNode, syn, find_by_key, [<<"my proc 1">>]),
-    undefined = syn:find_by_key(<<"my proc 2">>),
-    undefined = rpc:call(SlaveNode, syn, find_by_key, [<<"my proc 2">>]),
+    undefined = syn:whereis(<<"local proc">>),
+    undefined = syn:whereis(<<"remote proc">>),
+    undefined = syn:whereis(<<"remote proc reg_remote">>),
+    undefined = rpc:call(SlaveNode, syn, whereis, [<<"local proc">>]),
+    undefined = rpc:call(SlaveNode, syn, whereis, [<<"remote proc">>]),
+    undefined = rpc:call(SlaveNode, syn, whereis, [<<"remote proc reg_remote">>]),
     %% register
-    Meta = [{some, 1}, {meta, <<"data">>}],
-    ok = syn:register(<<"my proc 1">>, Pid1, Meta),
-    ok = syn:register(<<"my proc 2">>, Pid2),
+    ok = syn:register(<<"local proc">>, LocalPid),
+    ok = syn:register(<<"remote proc">>, RemotePid),
+    ok = rpc:call(SlaveNode, syn, register, [<<"remote proc reg_remote">>, RemotePidRegRemote]),
+    timer:sleep(500),
     %% retrieve
-    {Pid1, Meta} = syn:find_by_key(<<"my proc 1">>, with_meta),
-    {Pid1, Meta} = rpc:call(SlaveNode, syn, find_by_key, [<<"my proc 1">>, with_meta]),
-    {Pid2, undefined} = syn:find_by_key(<<"my proc 2">>, with_meta),
-    {Pid2, undefined} = rpc:call(SlaveNode, syn, find_by_key, [<<"my proc 2">>, with_meta]),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid1),
-    syn_test_suite_helper:kill_process(Pid2),
+    LocalPid = syn:whereis(<<"local proc">>),
+    RemotePid = syn:whereis(<<"remote proc">>),
+    RemotePidRegRemote = syn:whereis(<<"remote proc reg_remote">>),
+    LocalPid = rpc:call(SlaveNode, syn, whereis, [<<"local proc">>]),
+    RemotePid = rpc:call(SlaveNode, syn, whereis, [<<"remote proc">>]),
+    RemotePidRegRemote = rpc:call(SlaveNode, syn, whereis, [<<"remote proc reg_remote">>]),
+    %% kill & unregister processes
+    syn_test_suite_helper:kill_process(LocalPid),
+    ok = syn:unregister(<<"remote proc">>),
+    syn_test_suite_helper:kill_process(RemotePidRegRemote),
     timer:sleep(100),
     %% retrieve
-    undefined = syn:find_by_key(<<"my proc 1">>),
-    undefined = rpc:call(SlaveNode, syn, find_by_key, [<<"my proc 1">>]),
-    undefined = syn:find_by_key(<<"my proc 2">>),
-    undefined = rpc:call(SlaveNode, syn, find_by_key, [<<"my proc 2">>]).
+    undefined = syn:whereis(<<"local proc">>),
+    undefined = syn:whereis(<<"remote proc">>),
+    undefined = syn:whereis(<<"remote proc reg_remote">>),
+    undefined = rpc:call(SlaveNode, syn, whereis, [<<"local proc">>]),
+    undefined = rpc:call(SlaveNode, syn, whereis, [<<"remote proc">>]),
+    undefined = rpc:call(SlaveNode, syn, whereis, [<<"remote proc reg_remote">>]),
+    %% kill proc
+    syn_test_suite_helper:kill_process(RemotePid).
 
-two_nodes_when_mnesia_is_ram_process_count(Config) ->
+two_nodes_registry_count(Config) ->
     %% get slave
     SlaveNode = proplists:get_value(slave_node, Config),
-    CurrentNode = node(),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    rpc:call(SlaveNode, mnesia, schema_location, [ram]),
     %% start
     ok = syn:start(),
-    ok = syn:init(),
     ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
     timer:sleep(100),
-    %% count
-    0 = syn:registry_count(),
-    0 = rpc:call(SlaveNode, syn, registry_count, []),
-    0 = syn:registry_count(CurrentNode),
-    0 = syn:registry_count(SlaveNode),
-    0 = rpc:call(SlaveNode, syn, registry_count, [CurrentNode]),
-    0 = rpc:call(SlaveNode, syn, registry_count, [SlaveNode]),
     %% start processes
-    PidLocal1 = syn_test_suite_helper:start_process(),
-    PidLocal2 = syn_test_suite_helper:start_process(),
-    PidSlave = syn_test_suite_helper:start_process(SlaveNode),
+    LocalPid = syn_test_suite_helper:start_process(),
+    RemotePid = syn_test_suite_helper:start_process(SlaveNode),
+    RemotePidRegRemote = syn_test_suite_helper:start_process(SlaveNode),
+    PidUnregistered = syn_test_suite_helper:start_process(),
     %% register
-    ok = syn:register(1, PidLocal1),
-    ok = syn:register(2, PidLocal2),
-    ok = syn:register(3, PidSlave),
-    timer:sleep(100),
+    ok = syn:register(<<"local proc">>, LocalPid),
+    ok = syn:register(<<"remote proc">>, RemotePid),
+    ok = rpc:call(SlaveNode, syn, register, [<<"remote proc reg_remote">>, RemotePidRegRemote]),
+    timer:sleep(500),
     %% count
     3 = syn:registry_count(),
-    3 = rpc:call(SlaveNode, syn, registry_count, []),
-    2 = syn:registry_count(CurrentNode),
-    1 = syn:registry_count(SlaveNode),
-    2 = rpc:call(SlaveNode, syn, registry_count, [CurrentNode]),
-    1 = rpc:call(SlaveNode, syn, registry_count, [SlaveNode]),
-    %% kill processes
-    syn_test_suite_helper:kill_process(PidLocal1),
-    syn_test_suite_helper:kill_process(PidLocal2),
-    syn_test_suite_helper:kill_process(PidSlave),
+    1 = syn:registry_count(node()),
+    2 = syn:registry_count(SlaveNode),
+    %% kill & unregister processes
+    syn_test_suite_helper:kill_process(LocalPid),
+    ok = syn:unregister(<<"remote proc">>),
+    syn_test_suite_helper:kill_process(RemotePidRegRemote),
     timer:sleep(100),
     %% count
     0 = syn:registry_count(),
-    0 = rpc:call(SlaveNode, syn, registry_count, []),
-    0 = syn:registry_count(CurrentNode),
+    0 = syn:registry_count(node()),
     0 = syn:registry_count(SlaveNode),
-    0 = rpc:call(SlaveNode, syn, registry_count, [CurrentNode]),
-    0 = rpc:call(SlaveNode, syn, registry_count, [SlaveNode]).
+    %% kill proc
+    syn_test_suite_helper:kill_process(RemotePid).
 
-two_nodes_when_mnesia_is_ram_callback_on_process_exit(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    CurrentNode = node(),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    rpc:call(SlaveNode, mnesia, schema_location, [ram]),
-    %% load configuration variables from syn-test.config => this defines the callback
-    syn_test_suite_helper:set_environment_variables(),
-    syn_test_suite_helper:set_environment_variables(SlaveNode),
-    %% start
+three_nodes_consistency_partial_net_split(Config) ->
+    %% get slaves
+    SlaveNode1 = proplists:get_value(slave_node_1, Config),
+    SlaveNode2 = proplists:get_value(slave_node_2, Config),
+    %% start syn on nodes
     ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
+    ok = rpc:call(SlaveNode1, syn, start, []),
+    ok = rpc:call(SlaveNode2, syn, start, []),
     timer:sleep(100),
-    %% register global process
-    ResultPid = self(),
-    global:register_name(syn_register_process_SUITE_result, ResultPid),
     %% start processes
-    PidLocal = syn_test_suite_helper:start_process(),
-    PidSlave = syn_test_suite_helper:start_process(SlaveNode),
-    %% register
-    Meta = {some, meta},
-    ok = syn:register(<<"local">>, PidLocal, Meta),
-    ok = syn:register(<<"slave">>, PidSlave),
-    %% kill process
-    syn_test_suite_helper:kill_process(PidLocal),
-    syn_test_suite_helper:kill_process(PidSlave),
-    %% check callback were triggered
-    receive
-        {exited, CurrentNode, <<"local">>, PidLocal, Meta, killed} -> ok
-    after 2000 ->
-        ok = registry_process_exit_callback_was_not_called_from_local_node
-    end,
-    receive
-        {exited, SlaveNode, <<"slave">>, PidSlave, undefined, killed} -> ok
-    after 2000 ->
-        ok = registry_process_exit_callback_was_not_called_from_slave_node
-    end,
-    %% unregister
-    global:unregister_name(syn_register_process_SUITE_result).
+    Pid0 = syn_test_suite_helper:start_process(),
+    Pid0b = syn_test_suite_helper:start_process(),
+    Pid1 = syn_test_suite_helper:start_process(SlaveNode1),
+    Pid2 = syn_test_suite_helper:start_process(SlaveNode2),
+    timer:sleep(100),
+    %% retrieve
+    undefined = syn:whereis(<<"proc0">>),
+    undefined = syn:whereis(<<"proc0b">>),
+    undefined = syn:whereis(<<"proc1">>),
+    undefined = syn:whereis(<<"proc2">>),
+    undefined = rpc:call(SlaveNode1, syn, whereis, [<<"proc0">>]),
+    undefined = rpc:call(SlaveNode1, syn, whereis, [<<"proc0b">>]),
+    undefined = rpc:call(SlaveNode1, syn, whereis, [<<"proc1">>]),
+    undefined = rpc:call(SlaveNode1, syn, whereis, [<<"proc2">>]),
+    undefined = rpc:call(SlaveNode2, syn, whereis, [<<"proc0">>]),
+    undefined = rpc:call(SlaveNode2, syn, whereis, [<<"proc0b">>]),
+    undefined = rpc:call(SlaveNode2, syn, whereis, [<<"proc1">>]),
+    undefined = rpc:call(SlaveNode2, syn, whereis, [<<"proc2">>]),
+    %% register (mix nodes)
+    ok = rpc:call(SlaveNode2, syn, register, [<<"proc0">>, Pid0]),
+    ok = syn:register(<<"proc1">>, Pid1),
+    ok = rpc:call(SlaveNode1, syn, register, [<<"proc2">>, Pid2]),
+    ok = rpc:call(SlaveNode1, syn, register, [<<"proc0b">>, Pid0b]),
+    timer:sleep(200),
+    %% retrieve
+    Pid0 = syn:whereis(<<"proc0">>),
+    Pid0b = syn:whereis(<<"proc0b">>),
+    Pid1 = syn:whereis(<<"proc1">>),
+    Pid2 = syn:whereis(<<"proc2">>),
+    Pid0 = rpc:call(SlaveNode1, syn, whereis, [<<"proc0">>]),
+    Pid0b = rpc:call(SlaveNode1, syn, whereis, [<<"proc0b">>]),
+    Pid1 = rpc:call(SlaveNode1, syn, whereis, [<<"proc1">>]),
+    Pid2 = rpc:call(SlaveNode1, syn, whereis, [<<"proc2">>]),
+    Pid0 = rpc:call(SlaveNode2, syn, whereis, [<<"proc0">>]),
+    Pid0b = rpc:call(SlaveNode2, syn, whereis, [<<"proc0b">>]),
+    Pid1 = rpc:call(SlaveNode2, syn, whereis, [<<"proc1">>]),
+    Pid2 = rpc:call(SlaveNode2, syn, whereis, [<<"proc2">>]),
+    %% disconnect slave 2 from main (slave 1 can still see slave 2)
+    syn_test_suite_helper:disconnect_node(SlaveNode2),
+    timer:sleep(500),
+    %% retrieve
+    Pid0 = syn:whereis(<<"proc0">>),
+    Pid0b = syn:whereis(<<"proc0b">>),
+    Pid1 = syn:whereis(<<"proc1">>),
+    undefined = syn:whereis(<<"proc2">>), %% main has lost slave 2 so 'proc2' is removed
+    Pid0 = rpc:call(SlaveNode1, syn, whereis, [<<"proc0">>]),
+    Pid0b = rpc:call(SlaveNode1, syn, whereis, [<<"proc0b">>]),
+    Pid1 = rpc:call(SlaveNode1, syn, whereis, [<<"proc1">>]),
+    Pid2 = rpc:call(SlaveNode1, syn, whereis, [<<"proc2">>]), %% slave 1 still has slave 2 so 'proc2' is still there
+    %% disconnect slave 1
+    syn_test_suite_helper:disconnect_node(SlaveNode1),
+    timer:sleep(500),
+    %% unregister 0b
+    ok = syn:unregister(<<"proc0b">>),
+    %% retrieve
+    Pid0 = syn:whereis(<<"proc0">>),
+    undefined = syn:whereis(<<"proc0b">>),
+    undefined = syn:whereis(<<"proc1">>),
+    undefined = syn:whereis(<<"proc2">>),
+    %% reconnect all
+    syn_test_suite_helper:connect_node(SlaveNode1),
+    syn_test_suite_helper:connect_node(SlaveNode2),
+    timer:sleep(5000),
+    %% retrieve
+    Pid0 = syn:whereis(<<"proc0">>),
+    undefined = syn:whereis(<<"proc0b">>),
+    Pid1 = syn:whereis(<<"proc1">>),
+    Pid2 = syn:whereis(<<"proc2">>),
+    Pid0 = rpc:call(SlaveNode1, syn, whereis, [<<"proc0">>]),
+    undefined = rpc:call(SlaveNode1, syn, whereis, [<<"proc0b">>]),
+    Pid1 = rpc:call(SlaveNode1, syn, whereis, [<<"proc1">>]),
+    Pid2 = rpc:call(SlaveNode1, syn, whereis, [<<"proc2">>]),
+    Pid0 = rpc:call(SlaveNode2, syn, whereis, [<<"proc0">>]),
+    undefined = rpc:call(SlaveNode2, syn, whereis, [<<"proc0b">>]),
+    Pid1 = rpc:call(SlaveNode2, syn, whereis, [<<"proc1">>]),
+    Pid2 = rpc:call(SlaveNode2, syn, whereis, [<<"proc2">>]),
+    %% kill processes
+    syn_test_suite_helper:kill_process(Pid0),
+    syn_test_suite_helper:kill_process(Pid1),
+    syn_test_suite_helper:kill_process(Pid2).
 
-two_nodes_when_mnesia_is_disc_find_by_pid(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, disc),
-    rpc:call(SlaveNode, mnesia, schema_location, [disc]),
-    %% create schema
-    mnesia:create_schema([node(), SlaveNode]),
-    %% start
+three_nodes_consistency_full_net_split(Config) ->
+    %% get slaves
+    SlaveNode1 = proplists:get_value(slave_node_1, Config),
+    SlaveNode2 = proplists:get_value(slave_node_2, Config),
+    %% start syn on nodes
     ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
+    ok = rpc:call(SlaveNode1, syn, start, []),
+    ok = rpc:call(SlaveNode2, syn, start, []),
     timer:sleep(100),
-    %% start process
-    Pid = syn_test_suite_helper:start_process(),
-    %% register
-    ok = syn:register(<<"my proc">>, Pid),
-    %% retrieve
-    <<"my proc">> = syn:find_by_pid(Pid),
-    <<"my proc">> = rpc:call(SlaveNode, syn, find_by_pid, [Pid]),
-    %% kill process
-    syn_test_suite_helper:kill_process(Pid),
+    %% start processes
+    Pid0 = syn_test_suite_helper:start_process(),
+    Pid0b = syn_test_suite_helper:start_process(),
+    Pid1 = syn_test_suite_helper:start_process(SlaveNode1),
+    Pid2 = syn_test_suite_helper:start_process(SlaveNode2),
     timer:sleep(100),
     %% retrieve
-    undefined = syn:find_by_pid(Pid),
-    undefined = rpc:call(SlaveNode, syn, find_by_pid, [Pid]).
+    undefined = syn:whereis(<<"proc0">>),
+    undefined = syn:whereis(<<"proc0b">>),
+    undefined = syn:whereis(<<"proc1">>),
+    undefined = syn:whereis(<<"proc2">>),
+    undefined = rpc:call(SlaveNode1, syn, whereis, [<<"proc0">>]),
+    undefined = rpc:call(SlaveNode1, syn, whereis, [<<"proc0b">>]),
+    undefined = rpc:call(SlaveNode1, syn, whereis, [<<"proc1">>]),
+    undefined = rpc:call(SlaveNode1, syn, whereis, [<<"proc2">>]),
+    undefined = rpc:call(SlaveNode2, syn, whereis, [<<"proc0">>]),
+    undefined = rpc:call(SlaveNode2, syn, whereis, [<<"proc0b">>]),
+    undefined = rpc:call(SlaveNode2, syn, whereis, [<<"proc1">>]),
+    undefined = rpc:call(SlaveNode2, syn, whereis, [<<"proc2">>]),
+    %% register (mix nodes)
+    ok = rpc:call(SlaveNode2, syn, register, [<<"proc0">>, Pid0]),
+    ok = rpc:call(SlaveNode2, syn, register, [<<"proc0b">>, Pid0b]),
+    ok = syn:register(<<"proc1">>, Pid1),
+    ok = rpc:call(SlaveNode1, syn, register, [<<"proc2">>, Pid2]),
+    timer:sleep(200),
+    %% retrieve
+    Pid0 = syn:whereis(<<"proc0">>),
+    Pid0b = syn:whereis(<<"proc0b">>),
+    Pid1 = syn:whereis(<<"proc1">>),
+    Pid2 = syn:whereis(<<"proc2">>),
+    Pid0 = rpc:call(SlaveNode1, syn, whereis, [<<"proc0">>]),
+    Pid0b = rpc:call(SlaveNode1, syn, whereis, [<<"proc0b">>]),
+    Pid1 = rpc:call(SlaveNode1, syn, whereis, [<<"proc1">>]),
+    Pid2 = rpc:call(SlaveNode1, syn, whereis, [<<"proc2">>]),
+    Pid0 = rpc:call(SlaveNode2, syn, whereis, [<<"proc0">>]),
+    Pid0b = rpc:call(SlaveNode2, syn, whereis, [<<"proc0b">>]),
+    Pid1 = rpc:call(SlaveNode2, syn, whereis, [<<"proc1">>]),
+    Pid2 = rpc:call(SlaveNode2, syn, whereis, [<<"proc2">>]),
+    %% disconnect slave 2 from main (slave 1 can still see slave 2)
+    syn_test_suite_helper:disconnect_node(SlaveNode2),
+    timer:sleep(500),
+    %% retrieve
+    Pid0 = syn:whereis(<<"proc0">>),
+    Pid0b = syn:whereis(<<"proc0b">>),
+    Pid1 = syn:whereis(<<"proc1">>),
+    undefined = syn:whereis(<<"proc2">>), %% main has lost slave 2 so 'proc2' is removed
+    Pid0 = rpc:call(SlaveNode1, syn, whereis, [<<"proc0">>]),
+    Pid0b = rpc:call(SlaveNode1, syn, whereis, [<<"proc0b">>]),
+    Pid1 = rpc:call(SlaveNode1, syn, whereis, [<<"proc1">>]),
+    Pid2 = rpc:call(SlaveNode1, syn, whereis, [<<"proc2">>]), %% slave 1 still has slave 2 so 'proc2' is still there
+    %% disconnect slave 2 from slave 1
+    rpc:call(SlaveNode1, syn_test_suite_helper, disconnect_node, [SlaveNode2]),
+    timer:sleep(500),
+    %% retrieve
+    Pid0 = syn:whereis(<<"proc0">>),
+    Pid0b = syn:whereis(<<"proc0b">>),
+    Pid1 = syn:whereis(<<"proc1">>),
+    undefined = syn:whereis(<<"proc2">>), %% main has lost slave 2 so 'proc2' is removed
+    undefined = syn:whereis(<<"proc2">>, with_meta),
+    Pid0 = rpc:call(SlaveNode1, syn, whereis, [<<"proc0">>]),
+    Pid0b = rpc:call(SlaveNode1, syn, whereis, [<<"proc0b">>]),
+    Pid1 = rpc:call(SlaveNode1, syn, whereis, [<<"proc1">>]),
+    undefined = rpc:call(SlaveNode1, syn, whereis, [<<"proc2">>]),
+    %% disconnect slave 1
+    syn_test_suite_helper:disconnect_node(SlaveNode1),
+    timer:sleep(500),
+    %% unregister
+    ok = syn:unregister(<<"proc0b">>),
+    %% retrieve
+    Pid0 = syn:whereis(<<"proc0">>),
+    undefined = syn:whereis(<<"proc0b">>),
+    undefined = syn:whereis(<<"proc1">>),
+    undefined = syn:whereis(<<"proc2">>),
+    %% reconnect all
+    syn_test_suite_helper:connect_node(SlaveNode1),
+    syn_test_suite_helper:connect_node(SlaveNode2),
+    rpc:call(SlaveNode1, syn_test_suite_helper, connect_node, [SlaveNode2]),
+    timer:sleep(5000),
 
-%% ===================================================================
-%% Internal
-%% ===================================================================
-registry_process_exit_callback_dummy(Key, Pid, Meta, Reason) ->
-    global:send(syn_register_process_SUITE_result, {exited, node(), Key, Pid, Meta, Reason}).
+    ct:pal("0: ~p",[nodes()]),
+    ct:pal("1: ~p",[rpc:call(SlaveNode1, erlang, nodes, [])]),
+    ct:pal("2: ~p",[rpc:call(SlaveNode2, erlang, nodes, [])]),
+
+    %% retrieve
+    Pid0 = syn:whereis(<<"proc0">>),
+    undefined = syn:whereis(<<"proc0b">>),
+    Pid1 = syn:whereis(<<"proc1">>),
+    Pid2 = syn:whereis(<<"proc2">>),
+    Pid0 = rpc:call(SlaveNode1, syn, whereis, [<<"proc0">>]),
+    undefined = rpc:call(SlaveNode1, syn, whereis, [<<"proc0b">>]),
+    Pid1 = rpc:call(SlaveNode1, syn, whereis, [<<"proc1">>]),
+    Pid2 = rpc:call(SlaveNode1, syn, whereis, [<<"proc2">>]),
+    Pid0 = rpc:call(SlaveNode2, syn, whereis, [<<"proc0">>]),
+    undefined = rpc:call(SlaveNode2, syn, whereis, [<<"proc0b">>]),
+    Pid1 = rpc:call(SlaveNode2, syn, whereis, [<<"proc1">>]),
+    Pid2 = rpc:call(SlaveNode2, syn, whereis, [<<"proc2">>]),
+    %% kill processes
+    syn_test_suite_helper:kill_process(Pid0),
+    syn_test_suite_helper:kill_process(Pid0b),
+    syn_test_suite_helper:kill_process(Pid1),
+    syn_test_suite_helper:kill_process(Pid2).

+ 0 - 484
test/syn_registry_consistency_SUITE.erl

@@ -1,484 +0,0 @@
-%% ==========================================================================================================
-%% Syn - A global Process Registry and Process Group manager.
-%%
-%% The MIT License (MIT)
-%%
-%% Copyright (c) 2015 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a copy
-%% of this software and associated documentation files (the "Software"), to deal
-%% in the Software without restriction, including without limitation the rights
-%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-%% copies of the Software, and to permit persons to whom the Software is
-%% furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-%% THE SOFTWARE.
--module(syn_registry_consistency_SUITE).
-
-%% callbacks
--export([all/0]).
--export([init_per_suite/1, end_per_suite/1]).
--export([groups/0, init_per_group/2, end_per_group/2]).
--export([init_per_testcase/2, end_per_testcase/2]).
-
-%% tests
--export([
-    two_nodes_netsplit_when_there_are_no_conflicts/1,
-    two_nodes_netsplit_kill_resolution_when_there_are_conflicts/1,
-    two_nodes_netsplit_callback_resolution_when_there_are_conflicts/1
-]).
-
--export([
-    three_nodes_netsplit_kill_resolution_when_there_are_conflicts/1
-]).
-
-%% internal
--export([process_reply_main/0]).
--export([registry_conflicting_process_callback_dummy/3]).
-
-%% include
--include_lib("common_test/include/ct.hrl").
-
-
-%% ===================================================================
-%% Callbacks
-%% ===================================================================
-
-%% -------------------------------------------------------------------
-%% Function: all() -> GroupsAndTestCases | {skip,Reason}
-%% GroupsAndTestCases = [{group,GroupName} | TestCase]
-%% GroupName = atom()
-%% TestCase = atom()
-%% Reason = term()
-%% -------------------------------------------------------------------
-all() ->
-    [
-        {group, two_nodes_netsplits},
-        {group, three_nodes_netsplits}
-    ].
-
-%% -------------------------------------------------------------------
-%% Function: groups() -> [Group]
-%% Group = {GroupName,Properties,GroupsAndTestCases}
-%% GroupName = atom()
-%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
-%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
-%% TestCase = atom()
-%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
-%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
-%%			   repeat_until_any_ok | repeat_until_any_fail
-%% N = integer() | forever
-%% -------------------------------------------------------------------
-groups() ->
-    [
-        {two_nodes_netsplits, [shuffle], [
-            two_nodes_netsplit_when_there_are_no_conflicts,
-            two_nodes_netsplit_kill_resolution_when_there_are_conflicts,
-            two_nodes_netsplit_callback_resolution_when_there_are_conflicts
-        ]},
-        {three_nodes_netsplits, [shuffle], [
-            three_nodes_netsplit_kill_resolution_when_there_are_conflicts
-        ]}
-    ].
-%% -------------------------------------------------------------------
-%% Function: init_per_suite(Config0) ->
-%%				Config1 | {skip,Reason} |
-%%              {skip_and_save,Reason,Config1}
-%% Config0 = Config1 = [tuple()]
-%% Reason = term()
-%% -------------------------------------------------------------------
-init_per_suite(Config) ->
-    %% init
-    SlaveNodeShortName = syn_slave,
-    %% start slave
-    {ok, SlaveNode} = syn_test_suite_helper:start_slave(SlaveNodeShortName),
-    %% config
-    [
-        {slave_node_short_name, SlaveNodeShortName},
-        {slave_node, SlaveNode}
-        | Config
-    ].
-
-%% -------------------------------------------------------------------
-%% Function: end_per_suite(Config0) -> void() | {save_config,Config1}
-%% Config0 = Config1 = [tuple()]
-%% -------------------------------------------------------------------
-end_per_suite(Config) ->
-    %% get slave node name
-    SlaveNodeShortName = proplists:get_value(slave_node_short_name, Config),
-    %% stop slave
-    syn_test_suite_helper:stop_slave(SlaveNodeShortName).
-
-%% -------------------------------------------------------------------
-%% Function: init_per_group(GroupName, Config0) ->
-%%				Config1 | {skip,Reason} |
-%%              {skip_and_save,Reason,Config1}
-%% GroupName = atom()
-%% Config0 = Config1 = [tuple()]
-%% Reason = term()
-%% -------------------------------------------------------------------
-init_per_group(three_nodes_netsplits, Config) ->
-    %% init
-    SlaveNode2ShortName = syn_slave_2,
-    %% start slave 2
-    {ok, SlaveNode2} = syn_test_suite_helper:start_slave(SlaveNode2ShortName),
-    %% config
-    [
-        {slave_node_2_short_name, SlaveNode2ShortName},
-        {slave_node_2, SlaveNode2}
-        | Config
-    ];
-init_per_group(_GroupName, Config) -> Config.
-
-%% -------------------------------------------------------------------
-%% Function: end_per_group(GroupName, Config0) ->
-%%				void() | {save_config,Config1}
-%% GroupName = atom()
-%% Config0 = Config1 = [tuple()]
-%% -------------------------------------------------------------------
-end_per_group(three_nodes_netsplits, Config) ->
-    %% get slave node 2 name
-    SlaveNode2ShortName = proplists:get_value(slave_node_2_short_name, Config),
-    %% stop slave
-    syn_test_suite_helper:stop_slave(SlaveNode2ShortName);
-end_per_group(_GroupName, _Config) -> ok.
-
-% ----------------------------------------------------------------------------------------------------------
-% Function: init_per_testcase(TestCase, Config0) ->
-%				Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
-% TestCase = atom()
-% Config0 = Config1 = [tuple()]
-% Reason = term()
-% ----------------------------------------------------------------------------------------------------------
-init_per_testcase(_TestCase, Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    %% set schema location
-    application:set_env(mnesia, schema_location, ram),
-    rpc:call(SlaveNode, mnesia, schema_location, [ram]),
-    %% return
-    Config.
-
-% ----------------------------------------------------------------------------------------------------------
-% Function: end_per_testcase(TestCase, Config0) ->
-%				void() | {save_config,Config1} | {fail,Reason}
-% TestCase = atom()
-% Config0 = Config1 = [tuple()]
-% Reason = term()
-% ----------------------------------------------------------------------------------------------------------
-end_per_testcase(_TestCase, Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    syn_test_suite_helper:clean_after_test(SlaveNode).
-
-%% ===================================================================
-%% Tests
-%% ===================================================================
-two_nodes_netsplit_when_there_are_no_conflicts(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    CurrentNode = node(),
-
-    %% start syn
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
-
-    %% start processes
-    LocalPid = syn_test_suite_helper:start_process(),
-    SlavePidLocal = syn_test_suite_helper:start_process(SlaveNode),
-    SlavePidSlave = syn_test_suite_helper:start_process(SlaveNode),
-
-    %% register
-    ok = syn:register(local_pid, LocalPid),
-    ok = syn:register(slave_pid_local, SlavePidLocal),    %% slave registered on local node
-    ok = rpc:call(SlaveNode, syn, register, [slave_pid_slave, SlavePidSlave]),    %% slave registered on slave node
-    timer:sleep(100),
-
-    %% check tables
-    3 = mnesia:table_info(syn_registry_table, size),
-    3 = rpc:call(SlaveNode, mnesia, table_info, [syn_registry_table, size]),
-
-    LocalActiveReplicas = mnesia:table_info(syn_registry_table, active_replicas),
-    2 = length(LocalActiveReplicas),
-    true = lists:member(SlaveNode, LocalActiveReplicas),
-    true = lists:member(CurrentNode, LocalActiveReplicas),
-
-    SlaveActiveReplicas = rpc:call(SlaveNode, mnesia, table_info, [syn_registry_table, active_replicas]),
-    2 = length(SlaveActiveReplicas),
-    true = lists:member(SlaveNode, SlaveActiveReplicas),
-    true = lists:member(CurrentNode, SlaveActiveReplicas),
-
-    %% simulate net split
-    syn_test_suite_helper:disconnect_node(SlaveNode),
-    timer:sleep(1000),
-
-    %% check tables
-    1 = mnesia:table_info(syn_registry_table, size),
-    [CurrentNode] = mnesia:table_info(syn_registry_table, active_replicas),
-
-    %% reconnect
-    syn_test_suite_helper:connect_node(SlaveNode),
-    timer:sleep(1000),
-
-    %% check tables
-    3 = mnesia:table_info(syn_registry_table, size),
-    3 = rpc:call(SlaveNode, mnesia, table_info, [syn_registry_table, size]),
-
-    LocalActiveReplicas2 = mnesia:table_info(syn_registry_table, active_replicas),
-    2 = length(LocalActiveReplicas2),
-    true = lists:member(SlaveNode, LocalActiveReplicas2),
-    true = lists:member(CurrentNode, LocalActiveReplicas2),
-
-    SlaveActiveReplicas2 = rpc:call(SlaveNode, mnesia, table_info, [syn_registry_table, active_replicas]),
-    2 = length(SlaveActiveReplicas2),
-    true = lists:member(SlaveNode, SlaveActiveReplicas2),
-    true = lists:member(CurrentNode, SlaveActiveReplicas2),
-
-    %% check processes
-    LocalPid = syn:find_by_key(local_pid),
-    SlavePidLocal = syn:find_by_key(slave_pid_local),
-    SlavePidSlave = syn:find_by_key(slave_pid_slave),
-
-    LocalPid = rpc:call(SlaveNode, syn, find_by_key, [local_pid]),
-    SlavePidLocal = rpc:call(SlaveNode, syn, find_by_key, [slave_pid_local]),
-    SlavePidSlave = rpc:call(SlaveNode, syn, find_by_key, [slave_pid_slave]),
-
-    %% kill processes
-    syn_test_suite_helper:kill_process(LocalPid),
-    syn_test_suite_helper:kill_process(SlavePidLocal),
-    syn_test_suite_helper:kill_process(SlavePidSlave).
-
-two_nodes_netsplit_kill_resolution_when_there_are_conflicts(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    CurrentNode = node(),
-
-    %% start syn
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
-
-    %% start processes
-    LocalPid = syn_test_suite_helper:start_process(),
-    SlavePid = syn_test_suite_helper:start_process(SlaveNode),
-
-    %% register
-    ok = syn:register(conflicting_key, SlavePid),
-    timer:sleep(100),
-
-    %% check tables
-    1 = mnesia:table_info(syn_registry_table, size),
-    1 = rpc:call(SlaveNode, mnesia, table_info, [syn_registry_table, size]),
-
-    %% check process
-    SlavePid = syn:find_by_key(conflicting_key),
-
-    %% simulate net split
-    syn_test_suite_helper:disconnect_node(SlaveNode),
-    timer:sleep(1000),
-
-    %% check tables
-    0 = mnesia:table_info(syn_registry_table, size),
-    [CurrentNode] = mnesia:table_info(syn_registry_table, active_replicas),
-
-    %% now register the local pid with the same key
-    ok = syn:register(conflicting_key, LocalPid),
-
-    %% check process
-    LocalPid = syn:find_by_key(conflicting_key),
-
-    %% reconnect
-    syn_test_suite_helper:connect_node(SlaveNode),
-    timer:sleep(1000),
-
-    %% check tables
-    1 = mnesia:table_info(syn_registry_table, size),
-    1 = rpc:call(SlaveNode, mnesia, table_info, [syn_registry_table, size]),
-
-    %% check process
-    FoundPid = syn:find_by_key(conflicting_key),
-    true = lists:member(FoundPid, [LocalPid, SlavePid]),
-
-    %% kill processes
-    syn_test_suite_helper:kill_process(LocalPid),
-    syn_test_suite_helper:kill_process(SlavePid),
-
-    %% unregister
-    global:unregister_name(syn_consistency_SUITE_result).
-
-two_nodes_netsplit_callback_resolution_when_there_are_conflicts(Config) ->
-    %% get slave
-    SlaveNode = proplists:get_value(slave_node, Config),
-    CurrentNode = node(),
-
-    %% load configuration variables from syn-test.config => this sets the registry_conflicting_process_callback option
-    syn_test_suite_helper:set_environment_variables(),
-    syn_test_suite_helper:set_environment_variables(SlaveNode),
-
-    %% start syn
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    timer:sleep(100),
-
-    %% start processes
-    LocalPid = syn_test_suite_helper:start_process(fun process_reply_main/0),
-    SlavePid = syn_test_suite_helper:start_process(SlaveNode, fun process_reply_main/0),
-
-    %% register global process
-    ResultPid = self(),
-    global:register_name(syn_consistency_SUITE_result, ResultPid),
-
-    %% register
-    Meta = {some, meta, data},
-    ok = syn:register(conflicting_key, SlavePid, Meta),
-    timer:sleep(100),
-
-    %% check tables
-    1 = mnesia:table_info(syn_registry_table, size),
-    1 = rpc:call(SlaveNode, mnesia, table_info, [syn_registry_table, size]),
-
-    %% check process
-    SlavePid = syn:find_by_key(conflicting_key),
-
-    %% simulate net split
-    syn_test_suite_helper:disconnect_node(SlaveNode),
-    timer:sleep(1000),
-
-    %% check tables
-    0 = mnesia:table_info(syn_registry_table, size),
-    [CurrentNode] = mnesia:table_info(syn_registry_table, active_replicas),
-
-    %% now register the local pid with the same key
-    ok = syn:register(conflicting_key, LocalPid, Meta),
-
-    %% check process
-    LocalPid = syn:find_by_key(conflicting_key),
-
-    %% reconnect
-    syn_test_suite_helper:connect_node(SlaveNode),
-    timer:sleep(1000),
-
-    %% check tables
-    1 = mnesia:table_info(syn_registry_table, size),
-    1 = rpc:call(SlaveNode, mnesia, table_info, [syn_registry_table, size]),
-
-    %% check process
-    FoundPid = syn:find_by_key(conflicting_key),
-    true = lists:member(FoundPid, [LocalPid, SlavePid]),
-
-    %% check message received from killed pid
-    KilledPid = lists:nth(1, lists:delete(FoundPid, [LocalPid, SlavePid])),
-    receive
-        {exited, KilledPid, Meta} -> ok
-    after 2000 ->
-        ok = conflicting_process_did_not_receive_message
-    end,
-
-    %% kill processes
-    syn_test_suite_helper:kill_process(LocalPid),
-    syn_test_suite_helper:kill_process(SlavePid),
-
-    %% unregister
-    global:unregister_name(syn_consistency_SUITE_result).
-
-three_nodes_netsplit_kill_resolution_when_there_are_conflicts(Config) ->
-    %% get slaves
-    SlaveNode = proplists:get_value(slave_node, Config),
-    SlaveNode2 = proplists:get_value(slave_node_2, Config),
-    CurrentNode = node(),
-
-    %% start syn
-    ok = syn:start(),
-    ok = syn:init(),
-    ok = rpc:call(SlaveNode, syn, start, []),
-    ok = rpc:call(SlaveNode, syn, init, []),
-    ok = rpc:call(SlaveNode2, syn, start, []),
-    ok = rpc:call(SlaveNode2, syn, init, []),
-    timer:sleep(100),
-
-    %% start processes
-    LocalPid = syn_test_suite_helper:start_process(),
-    SlavePid = syn_test_suite_helper:start_process(SlaveNode),
-    Slave2Pid = syn_test_suite_helper:start_process(SlaveNode2),
-
-    %% register
-    ok = syn:register(conflicting_key, SlavePid),
-    ok = syn:register(slave_2_process, Slave2Pid),
-    timer:sleep(100),
-
-    %% check tables
-    2 = mnesia:table_info(syn_registry_table, size),
-    2 = rpc:call(SlaveNode, mnesia, table_info, [syn_registry_table, size]),
-    2 = rpc:call(SlaveNode2, mnesia, table_info, [syn_registry_table, size]),
-
-    %% check process
-    SlavePid = syn:find_by_key(conflicting_key),
-
-    %% simulate net split
-    syn_test_suite_helper:disconnect_node(SlaveNode),
-    timer:sleep(1000),
-
-    %% check tables
-    1 = mnesia:table_info(syn_registry_table, size),
-    1 = rpc:call(SlaveNode2, mnesia, table_info, [syn_registry_table, size]),
-
-    ActiveReplicaseDuringNetsplit = mnesia:table_info(syn_registry_table, active_replicas),
-    true = lists:member(CurrentNode, ActiveReplicaseDuringNetsplit),
-    true = lists:member(SlaveNode2, ActiveReplicaseDuringNetsplit),
-
-    %% now register the local pid with the same conflicting key
-    ok = syn:register(conflicting_key, LocalPid),
-
-    %% check process
-    LocalPid = syn:find_by_key(conflicting_key),
-
-    %% reconnect
-    syn_test_suite_helper:connect_node(SlaveNode),
-    timer:sleep(1000),
-
-    %% check tables
-    2 = mnesia:table_info(syn_registry_table, size),
-    2 = rpc:call(SlaveNode, mnesia, table_info, [syn_registry_table, size]),
-    2 = rpc:call(SlaveNode2, mnesia, table_info, [syn_registry_table, size]),
-
-    %% check processes
-    FoundPid = syn:find_by_key(conflicting_key),
-    true = lists:member(FoundPid, [LocalPid, SlavePid]),
-
-    Slave2Pid = syn:find_by_key(slave_2_process),
-    Slave2Pid = rpc:call(SlaveNode, syn, find_by_key, [slave_2_process]),
-    Slave2Pid = rpc:call(SlaveNode2, syn, find_by_key, [slave_2_process]),
-
-    %% kill processes
-    syn_test_suite_helper:kill_process(LocalPid),
-    syn_test_suite_helper:kill_process(SlavePid),
-    syn_test_suite_helper:kill_process(Slave2Pid).
-
-%% ===================================================================
-%% Internal
-%% ===================================================================
-process_reply_main() ->
-    receive
-        {shutdown, Meta} ->
-            timer:sleep(500), %% wait for global processes to propagate
-            global:send(syn_consistency_SUITE_result, {exited, self(), Meta})
-    end.
-
-registry_conflicting_process_callback_dummy(_Key, Pid, Meta) ->
-    Pid ! {shutdown, Meta}.

+ 0 - 69
test/syn_test_gen_server.erl

@@ -1,69 +0,0 @@
-%% ==========================================================================================================
-%% Syn - A global Process Registry and Process Group manager.
-%%
-%% The MIT License (MIT)
-%%
-%% Copyright (c) 2015 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a copy
-%% of this software and associated documentation files (the "Software"), to deal
-%% in the Software without restriction, including without limitation the rights
-%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-%% copies of the Software, and to permit persons to whom the Software is
-%% furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-%% THE SOFTWARE.
-%% ==========================================================================================================
--module(syn_test_gen_server).
--behaviour(gen_server).
-
--export([start_link/2, stop/1]).
-
-%% gen_server callbacks
--export([
-    init/1,
-    handle_call/3,
-    handle_cast/2,
-    handle_info/2,
-    terminate/2,
-    code_change/3
-]).
-
-start_link(Parent, Name) ->
-    gen_server:start_link(Name, ?MODULE, Parent, []).
-
-stop(Server) ->
-    gen_server:cast(Server, stop).
-
-init(State) ->
-    {ok, State}.
-
-handle_call(_Request, _From, State) ->
-    {reply, call_received, State}.
-
-handle_cast(stop, State) ->
-    State ! stop_received,
-    {stop, normal, State};
-
-handle_cast(_Msg, State) ->
-    State ! cast_received,
-    {noreply, State}.
-
-handle_info(_Info, State) ->
-    State ! info_received,
-    {noreply, State}.
-
-terminate(_Reason, _State) ->
-    ok.
-
-code_change(_OldVsn, State, _Extra) ->
-    {ok, State}.

+ 10 - 43
test/syn_test_suite_helper.erl

@@ -26,10 +26,9 @@
 -module(syn_test_suite_helper).
 
 %% API
--export([set_environment_variables/0, set_environment_variables/1]).
 -export([start_slave/1, stop_slave/1]).
 -export([connect_node/1, disconnect_node/1]).
--export([clean_after_test/0, clean_after_test/1]).
+-export([clean_after_test/0]).
 -export([start_process/0, start_process/1, start_process/2]).
 -export([kill_process/1]).
 
@@ -43,18 +42,6 @@
 %% ===================================================================
 %% API
 %% ===================================================================
-set_environment_variables() ->
-    set_environment_variables(node()).
-set_environment_variables(Node) ->
-    % read config file
-    ConfigFilePath = filename:join([filename:dirname(code:which(?MODULE)), ?SYN_TEST_CONFIG_FILENAME]),
-    {ok, [AppsConfig]} = file:consult(ConfigFilePath),
-    % loop to set variables
-    F = fun({AppName, AppConfig}) ->
-        set_environment_for_app(Node, AppName, AppConfig)
-    end,
-    lists:foreach(F, AppsConfig).
-
 start_slave(NodeShortName) ->
     CodePath = code:get_path(),
     {ok, Node} = ct_slave:start(NodeShortName, [{boot_timeout, 10}]),
@@ -71,28 +58,14 @@ disconnect_node(Node) ->
     erlang:disconnect_node(Node).
 
 clean_after_test() ->
-    %% delete table
-    {atomic, ok} = mnesia:delete_table(syn_registry_table),
-    %% stop mnesia
-    mnesia:stop(),
-    %% delete schema
-    mnesia:delete_schema([node()]),
-    %% stop syn
-    syn:stop().
-
-clean_after_test(undefined) ->
-    clean_after_test();
-clean_after_test(Node) ->
-    %% delete table
-    {atomic, ok} = mnesia:delete_table(syn_registry_table),
-    %% stop mnesia
-    mnesia:stop(),
-    rpc:call(Node, mnesia, stop, []),
-    %% delete schema
-    mnesia:delete_schema([node(), Node]),
-    %% stop syn
-    syn:stop(),
-    rpc:call(Node, syn, stop, []).
+    Nodes = [node() | nodes()],
+    %% shutdown
+    lists:foreach(fun(Node) ->
+        ok = rpc:call(Node, syn, stop, []),
+        ok = rpc:call(Node, application, stop, [mnesia])
+    end, Nodes),
+    %% clean mnesia
+    mnesia:delete_schema(Nodes).
 
 start_process() ->
     Pid = spawn(fun process_main/0),
@@ -103,7 +76,7 @@ start_process(Node) when is_atom(Node) ->
 start_process(Loop) when is_function(Loop) ->
     Pid = spawn(Loop),
     Pid.
-start_process(Node, Loop)->
+start_process(Node, Loop) ->
     Pid = spawn(Node, Loop),
     Pid.
 
@@ -113,12 +86,6 @@ kill_process(Pid) ->
 %% ===================================================================
 %% Internal
 %% ===================================================================
-set_environment_for_app(Node, AppName, AppConfig) ->
-    F = fun({Key, Val}) ->
-        ok = rpc:call(Node, application, set_env, [AppName, Key, Val])
-    end,
-    lists:foreach(F, AppConfig).
-
 process_main() ->
     receive
         _ -> process_main()