syn_backbone.erl 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  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. %% gen_server callbacks
  33. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
  34. %% includes
  35. -include("syn.hrl").
  36. %% ===================================================================
  37. %% API
  38. %% ===================================================================
  39. -spec start_link() -> {ok, pid()} | {error, any()}.
  40. start_link() ->
  41. Options = [],
  42. gen_server:start_link({local, ?MODULE}, ?MODULE, [], Options).
  43. -spec create_tables_for_scope(Scope :: atom()) -> ok.
  44. create_tables_for_scope(Scope) ->
  45. gen_server:call(?MODULE, {create_tables_for_scope, Scope}).
  46. -spec get_table_name(TableId :: atom(), Scope :: atom()) -> TableName :: atom() | undefined.
  47. get_table_name(TableId, Scope) ->
  48. case ets:lookup(syn_table_names, {TableId, Scope}) of
  49. [{_, TableName}] -> TableName;
  50. [] -> undefined
  51. end.
  52. %% ===================================================================
  53. %% Callbacks
  54. %% ===================================================================
  55. %% ----------------------------------------------------------------------------------------------------------
  56. %% Init
  57. %% ----------------------------------------------------------------------------------------------------------
  58. -spec init([]) ->
  59. {ok, State :: map()} |
  60. {ok, State :: map(), Timeout :: non_neg_integer()} |
  61. ignore |
  62. {stop, Reason :: any()}.
  63. init([]) ->
  64. %% create table names table
  65. ets:new(syn_table_names, [set, public, named_table, {read_concurrency, true}, {decentralized_counters, true}]),
  66. ets:new(syn_process_names, [set, public, named_table, {read_concurrency, true}, {decentralized_counters, true}]),
  67. %% init
  68. {ok, #{}}.
  69. %% ----------------------------------------------------------------------------------------------------------
  70. %% Call messages
  71. %% ----------------------------------------------------------------------------------------------------------
  72. -spec handle_call(Request :: any(), From :: any(), State :: map()) ->
  73. {reply, Reply :: any(), State :: map()} |
  74. {reply, Reply :: any(), State :: map(), Timeout :: non_neg_integer()} |
  75. {noreply, State :: map()} |
  76. {noreply, State :: map(), Timeout :: non_neg_integer()} |
  77. {stop, Reason :: any(), Reply :: any(), State :: map()} |
  78. {stop, Reason :: any(), State :: map()}.
  79. handle_call({create_tables_for_scope, Scope}, _From, State) ->
  80. error_logger:info_msg("SYN[~s] Creating tables for scope '~s'", [node(), Scope]),
  81. ensure_table_exists(set, syn_registry_by_name, Scope),
  82. ensure_table_exists(bag, syn_registry_by_pid, Scope),
  83. ensure_table_exists(set, syn_groups_by_name, Scope),
  84. ensure_table_exists(bag, syn_groups_by_pid, Scope),
  85. {reply, ok, State};
  86. handle_call(Request, From, State) ->
  87. error_logger:warning_msg("SYN[~s] Received from ~p an unknown call message: ~p", [node(), From, Request]),
  88. {reply, undefined, State}.
  89. %% ----------------------------------------------------------------------------------------------------------
  90. %% Cast messages
  91. %% ----------------------------------------------------------------------------------------------------------
  92. -spec handle_cast(Msg :: any(), State :: map()) ->
  93. {noreply, State :: map()} |
  94. {noreply, State :: map(), Timeout :: non_neg_integer()} |
  95. {stop, Reason :: any(), State :: map()}.
  96. handle_cast(Msg, State) ->
  97. error_logger:warning_msg("SYN[~p] Received an unknown cast message: ~p", [node(), Msg]),
  98. {noreply, State}.
  99. %% ----------------------------------------------------------------------------------------------------------
  100. %% All non Call / Cast messages
  101. %% ----------------------------------------------------------------------------------------------------------
  102. -spec handle_info(Info :: any(), State :: map()) ->
  103. {noreply, State :: map()} |
  104. {noreply, State :: map(), Timeout :: non_neg_integer()} |
  105. {stop, Reason :: any(), State :: map()}.
  106. handle_info(Info, State) ->
  107. error_logger:warning_msg("SYN[~p] Received an unknown info message: ~p", [node(), Info]),
  108. {noreply, State}.
  109. %% ----------------------------------------------------------------------------------------------------------
  110. %% Terminate
  111. %% ----------------------------------------------------------------------------------------------------------
  112. -spec terminate(Reason :: any(), State :: map()) -> terminated.
  113. terminate(Reason, _State) ->
  114. error_logger:info_msg("SYN[~p] Terminating with reason: ~p", [node(), Reason]),
  115. %% return
  116. terminated.
  117. %% ----------------------------------------------------------------------------------------------------------
  118. %% Convert process state when code is changed.
  119. %% ----------------------------------------------------------------------------------------------------------
  120. -spec code_change(OldVsn :: any(), State :: map(), Extra :: any()) -> {ok, State :: map()}.
  121. code_change(_OldVsn, State, _Extra) ->
  122. {ok, State}.
  123. %% ===================================================================
  124. %% Internal
  125. %% ===================================================================
  126. -spec ensure_table_exists(Type :: set | ordered_set, TableId :: atom(), Scope :: atom()) -> ok.
  127. ensure_table_exists(Type, TableId, Scope) ->
  128. %% build name
  129. TableIdBin = atom_to_binary(TableId),
  130. ScopeBin = atom_to_binary(Scope),
  131. TableName = binary_to_atom(<<TableIdBin/binary, "_", ScopeBin/binary>>),
  132. %% save to loopkup table
  133. true = ets:insert(syn_table_names, {{TableId, Scope}, TableName}),
  134. %% check or create
  135. case ets:whereis(TableName) of
  136. undefined ->
  137. %% regarding decentralized_counters: <https://blog.erlang.org/scalable-ets-counters/>
  138. ets:new(TableName, [
  139. Type, public, named_table,
  140. {read_concurrency, true}, {decentralized_counters, true}
  141. ]),
  142. ok;
  143. _ ->
  144. ok
  145. end.