syn_backbone.erl 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. %% ==========================================================================================================
  2. %% Syn - A global Process Registry and Process Group manager.
  3. %%
  4. %% The MIT License (MIT)
  5. %%
  6. %% Copyright (c) 2015-2021 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
  7. %%
  8. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  9. %% of this software and associated documentation files (the "Software"), to deal
  10. %% in the Software without restriction, including without limitation the rights
  11. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. %% copies of the Software, and to permit persons to whom the Software is
  13. %% furnished to do so, subject to the following conditions:
  14. %%
  15. %% The above copyright notice and this permission notice shall be included in
  16. %% all copies or substantial portions of the Software.
  17. %%
  18. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. %% THE SOFTWARE.
  25. %% ==========================================================================================================
  26. -module(syn_backbone).
  27. -behaviour(gen_server).
  28. %% API
  29. -export([start_link/0]).
  30. -export([create_tables_for_scope/1]).
  31. -export([get_table_name/2]).
  32. -export([save_process_name/2, get_process_name/1]).
  33. %% gen_server callbacks
  34. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
  35. %% includes
  36. -include("syn.hrl").
  37. %% ===================================================================
  38. %% API
  39. %% ===================================================================
  40. -spec start_link() -> {ok, pid()} | {error, any()}.
  41. start_link() ->
  42. Options = [],
  43. gen_server:start_link({local, ?MODULE}, ?MODULE, [], Options).
  44. -spec create_tables_for_scope(Scope :: atom()) -> ok.
  45. create_tables_for_scope(Scope) ->
  46. gen_server:call(?MODULE, {create_tables_for_scope, Scope}).
  47. -spec save_process_name(Key :: any(), ProcessName :: atom()) -> true.
  48. save_process_name(Key, ProcessName) ->
  49. true = ets:insert(syn_process_names, {Key, ProcessName}).
  50. -spec get_process_name(Key :: any()) -> ProcessName :: atom().
  51. get_process_name(Key) ->
  52. case ets:lookup(syn_process_names, Key) of
  53. [{_, ProcessName}] -> ProcessName;
  54. [] -> undefined
  55. end.
  56. -spec get_table_name(TableId :: atom(), Scope :: atom()) -> TableName :: atom() | undefined.
  57. get_table_name(TableId, Scope) ->
  58. case ets:lookup(syn_table_names, {TableId, Scope}) of
  59. [{_, TableName}] -> TableName;
  60. [] -> undefined
  61. end.
  62. %% ===================================================================
  63. %% Callbacks
  64. %% ===================================================================
  65. %% ----------------------------------------------------------------------------------------------------------
  66. %% Init
  67. %% ----------------------------------------------------------------------------------------------------------
  68. -spec init([]) ->
  69. {ok, State :: map()} |
  70. {ok, State :: map(), Timeout :: non_neg_integer()} |
  71. ignore |
  72. {stop, Reason :: any()}.
  73. init([]) ->
  74. %% create table names table
  75. ets:new(syn_table_names, [set, public, named_table, {read_concurrency, true}, {decentralized_counters, true}]),
  76. ets:new(syn_process_names, [set, public, named_table, {read_concurrency, true}, {decentralized_counters, true}]),
  77. %% init
  78. {ok, #{}}.
  79. %% ----------------------------------------------------------------------------------------------------------
  80. %% Call messages
  81. %% ----------------------------------------------------------------------------------------------------------
  82. -spec handle_call(Request :: any(), From :: any(), State :: map()) ->
  83. {reply, Reply :: any(), State :: map()} |
  84. {reply, Reply :: any(), State :: map(), Timeout :: non_neg_integer()} |
  85. {noreply, State :: map()} |
  86. {noreply, State :: map(), Timeout :: non_neg_integer()} |
  87. {stop, Reason :: any(), Reply :: any(), State :: map()} |
  88. {stop, Reason :: any(), State :: map()}.
  89. handle_call({create_tables_for_scope, Scope}, _From, State) ->
  90. error_logger:info_msg("SYN[~s] Creating tables for scope '~s'", [?MODULE, Scope]),
  91. ensure_table_existence(set, syn_registry_by_name, Scope),
  92. ensure_table_existence(bag, syn_registry_by_pid, Scope),
  93. ensure_table_existence(ordered_set, syn_groups_by_name, Scope),
  94. ensure_table_existence(ordered_set, syn_groups_by_pid, Scope),
  95. {reply, ok, State};
  96. handle_call(Request, From, State) ->
  97. error_logger:warning_msg("SYN[~s] Received from ~p an unknown call message: ~p", [?MODULE, From, Request]),
  98. {reply, undefined, State}.
  99. %% ----------------------------------------------------------------------------------------------------------
  100. %% Cast messages
  101. %% ----------------------------------------------------------------------------------------------------------
  102. -spec handle_cast(Msg :: any(), State :: map()) ->
  103. {noreply, State :: map()} |
  104. {noreply, State :: map(), Timeout :: non_neg_integer()} |
  105. {stop, Reason :: any(), State :: map()}.
  106. handle_cast(Msg, State) ->
  107. error_logger:warning_msg("SYN[~s] Received an unknown cast message: ~p", [?MODULE, Msg]),
  108. {noreply, State}.
  109. %% ----------------------------------------------------------------------------------------------------------
  110. %% All non Call / Cast messages
  111. %% ----------------------------------------------------------------------------------------------------------
  112. -spec handle_info(Info :: any(), State :: map()) ->
  113. {noreply, State :: map()} |
  114. {noreply, State :: map(), Timeout :: non_neg_integer()} |
  115. {stop, Reason :: any(), State :: map()}.
  116. handle_info(Info, State) ->
  117. error_logger:warning_msg("SYN[~s] Received an unknown info message: ~p", [?MODULE, Info]),
  118. {noreply, State}.
  119. %% ----------------------------------------------------------------------------------------------------------
  120. %% Terminate
  121. %% ----------------------------------------------------------------------------------------------------------
  122. -spec terminate(Reason :: any(), State :: map()) -> terminated.
  123. terminate(Reason, _State) ->
  124. error_logger:info_msg("SYN[~s] Terminating with reason: ~p", [?MODULE, Reason]),
  125. %% return
  126. terminated.
  127. %% ----------------------------------------------------------------------------------------------------------
  128. %% Convert process state when code is changed.
  129. %% ----------------------------------------------------------------------------------------------------------
  130. -spec code_change(OldVsn :: any(), State :: map(), Extra :: any()) -> {ok, State :: map()}.
  131. code_change(_OldVsn, State, _Extra) ->
  132. {ok, State}.
  133. %% ===================================================================
  134. %% Internal
  135. %% ===================================================================
  136. -spec ensure_table_existence(Type :: ets:type(), TableId :: atom(), Scope :: atom()) -> ok.
  137. ensure_table_existence(Type, TableId, Scope) ->
  138. %% build name
  139. TableIdBin = atom_to_binary(TableId),
  140. ScopeBin = atom_to_binary(Scope),
  141. TableName = binary_to_atom(<<TableIdBin/binary, "_", ScopeBin/binary>>),
  142. %% save to loopkup table
  143. true = ets:insert(syn_table_names, {{TableId, Scope}, TableName}),
  144. %% check or create
  145. case ets:whereis(TableName) of
  146. undefined ->
  147. %% regarding decentralized_counters: <https://blog.erlang.org/scalable-ets-counters/>
  148. ets:new(TableName, [
  149. Type, public, named_table,
  150. {read_concurrency, true}, {decentralized_counters, true}
  151. ]),
  152. ok;
  153. _ ->
  154. ok
  155. end.