├── LICENSE
├── README.markdown
├── doc
├── edoc-info
├── erlang.png
├── index.html
├── modules-frame.html
├── overview-summary.html
├── stylesheet.css
├── varpool.html
└── varpool_sup.html
├── mix.exs
├── rebar.config
├── src
├── varpool.app.src
├── varpool.erl
└── varpool_sup.erl
└── test
└── varpool_test_server.erl
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2015-2023 Michael Truog
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a
6 | copy of this software and associated documentation files (the "Software"),
7 | to deal in the Software without restriction, including without limitation
8 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 | and/or sell copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 | DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | varpool: Erlang Process Pools as a Local Variable
2 | =================================================
3 |
4 | "Let messages ride together to minimize computational costs"
5 |
6 | Purpose
7 | -------
8 |
9 | There are two main approaches to process pooling in Erlang:
10 |
11 | 1. Treat the pool as shared data to operate on the pool in ways commonly used
12 | with imperative programming languages: A process is removed from the pool,
13 | the process is used and the process is added back to the same pool
14 | (interacting with the pool as mutable data).
15 | 2. Use the pool with Flow-Based-Programming (FBP) to use Erlang in a simpler
16 | way: The pool returns a process without modifying the pool and a
17 | message is sent to the process to utilize the process. Either an
18 | asynchronous or synchronous message is used with the process based on
19 | the requirements of the process and the effect of the request rate on the
20 | process message queue.
21 |
22 | The #1 approach is unfortunately very common in Erlang source code and is
23 | normally (erroneously) regarded as the correct approach when writing Erlang
24 | source code. A major negative aspect of the #1 approach is the impact on
25 | fault-tolerance. How is the process fault-tolerance handled when the process
26 | is removed from the pool (to be used)? If the process is still linked to the
27 | pool in a way where the pool will restart the process if it crashes while not
28 | being within the pool, it is impossible to isolate the cause of the crash
29 | in the implementation. The crash could have occurred due to the external
30 | usage of the process or it could have occurred due to an internal error
31 | within the pool source code. While you can easily argue that a stacktrace
32 | will show you the cause of the error, you will be unable to easily determine
33 | if the stacktrace occurred during or after the error on one side
34 | or the other. The ambiguity and complexity in the #1 approach makes it
35 | error-prone.
36 |
37 | A second major negative aspect of the #1 approach is the scalability impact
38 | when constantly mutating data to get a process from a pool. As the request
39 | rate increases, the latency associated with altering the pool becomes
40 | more significant. The extra latency helps to put artifical limits on any
41 | source code that depends on pooling with the #1 approach.
42 |
43 | The #2 approach is a more natural fit to the fault-tolerance and
44 | concurrency Erlang provides (the pool handles the process fault-tolerance
45 | and relying on the process message queue utilizes Erlang process concurrency).
46 | The pool does not become a scalability bottleneck while it remains
47 | immutable data. An additional benefit is that the implementation is simpler
48 | and easier to understand.
49 |
50 | The most common example of the #1 approach is probably
51 | [poolboy](https://github.com/devinus/poolboy). The varpool library
52 | provides the #2 approach as local variable data (for efficient access).
53 |
54 | Build
55 | -----
56 |
57 | rebar compile
58 |
59 | Test
60 | ----
61 |
62 | rebar eunit
63 |
64 | Author
65 | ------
66 |
67 | Michael Truog (mjtruog at protonmail dot com)
68 |
69 | License
70 | -------
71 |
72 | MIT License
73 |
74 |
--------------------------------------------------------------------------------
/doc/edoc-info:
--------------------------------------------------------------------------------
1 | %% encoding: UTF-8
2 | {application,varpool}.
3 | {modules,[varpool,varpool_sup]}.
4 |
--------------------------------------------------------------------------------
/doc/erlang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okeuday/varpool/2b6dc565c2b9983db40cb89612092f6d11388af7/doc/erlang.png
--------------------------------------------------------------------------------
/doc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The varpool application
5 |
6 |
7 |
8 |
9 |
10 |
11 | This page uses frames
12 | Your browser does not accept frames.
13 | You should go to the non-frame version instead.
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/doc/modules-frame.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The varpool application
5 |
6 |
7 |
8 | Modules
9 |
12 |
13 |
--------------------------------------------------------------------------------
/doc/overview-summary.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | The varpool application
6 |
7 |
8 |
9 |
10 | The varpool application
11 |
12 |
13 |
14 | Generated by EDoc
15 |
16 |
17 |
--------------------------------------------------------------------------------
/doc/stylesheet.css:
--------------------------------------------------------------------------------
1 | /* standard EDoc style sheet */
2 | body {
3 | font-family: Verdana, Arial, Helvetica, sans-serif;
4 | margin-left: .25in;
5 | margin-right: .2in;
6 | margin-top: 0.2in;
7 | margin-bottom: 0.2in;
8 | color: #000000;
9 | background-color: #ffffff;
10 | }
11 | h1,h2 {
12 | margin-left: -0.2in;
13 | }
14 | div.navbar {
15 | background-color: #add8e6;
16 | padding: 0.2em;
17 | }
18 | h2.indextitle {
19 | padding: 0.4em;
20 | background-color: #add8e6;
21 | }
22 | h3.function,h3.typedecl {
23 | background-color: #add8e6;
24 | padding-left: 1em;
25 | }
26 | div.spec {
27 | margin-left: 2em;
28 | background-color: #eeeeee;
29 | }
30 | a.module {
31 | text-decoration:none
32 | }
33 | a.module:hover {
34 | background-color: #eeeeee;
35 | }
36 | ul.definitions {
37 | list-style-type: none;
38 | }
39 | ul.index {
40 | list-style-type: none;
41 | background-color: #eeeeee;
42 | }
43 |
44 | /*
45 | * Minor style tweaks
46 | */
47 | ul {
48 | list-style-type: square;
49 | }
50 | table {
51 | border-collapse: collapse;
52 | }
53 | td {
54 | padding: 3
55 | }
56 |
--------------------------------------------------------------------------------
/doc/varpool.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Module varpool
6 |
7 |
8 |
9 |
10 |
11 |
12 | Module varpool
13 |
14 | .
15 | Copyright © 2015-2021 Michael Truog
16 |
17 | Version: 2.0.1 May 26 2021 19:32:09
18 | ------------------------------------------------------------------------
19 | Authors: Michael Truog (mjtruog at protonmail dot com ).
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | group() = {Group::any(), {M::module(), F::atom(), A::list()}, Options::[{shutdown, pos_integer()} | {count_hash, pos_integer()} | {count_random, pos_integer()} | {hash, {module(), atom()} | fun((any(), pos_integer()) -> non_neg_integer())} | {random, {module(), atom()} | fun((pos_integer()) -> non_neg_integer())}]}
28 |
29 |
30 |
31 | options() = [{max_r, non_neg_integer()} | {max_t, pos_integer()} | {groups, [group() , ...]}, ...]
32 |
33 |
34 |
35 |
41 |
42 |
43 |
44 |
45 |
46 |
destroy(Varpool::#varpool{owner = pid(), supervisor = pid(), max_r = non_neg_integer(), max_t = pos_integer(), groups = #{any() := #group{count_hash = pos_integer(), count_random = pos_integer(), count_total = pos_integer(), hash = fun((any(), pos_integer()) -> non_neg_integer()), random = fun((pos_integer()) -> non_neg_integer()), processes = array:array (pid())}}, processes = #{pid() := {any(), non_neg_integer()}}, monitors = [reference()]}) -> ok
47 |
48 |
49 |
50 |
51 |
52 |
get(Group::any(), Varpool::#varpool{owner = pid(), supervisor = pid(), max_r = non_neg_integer(), max_t = pos_integer(), groups = #{any() := #group{count_hash = pos_integer(), count_random = pos_integer(), count_total = pos_integer(), hash = fun((any(), pos_integer()) -> non_neg_integer()), random = fun((pos_integer()) -> non_neg_integer()), processes = array:array (pid())}}, processes = #{pid() := {any(), non_neg_integer()}}, monitors = [reference()]}) -> pid() | undefined
53 |
54 |
55 |
56 |
57 |
58 |
get(Group::any(), Key::any(), Varpool::#varpool{owner = pid(), supervisor = pid(), max_r = non_neg_integer(), max_t = pos_integer(), groups = #{any() := #group{count_hash = pos_integer(), count_random = pos_integer(), count_total = pos_integer(), hash = fun((any(), pos_integer()) -> non_neg_integer()), random = fun((pos_integer()) -> non_neg_integer()), processes = array:array (pid())}}, processes = #{pid() := {any(), non_neg_integer()}}, monitors = [reference()]}) -> pid() | undefined
59 |
60 |
61 |
62 |
63 |
64 |
new(Options::options() ) -> #varpool{owner = pid(), supervisor = pid(), max_r = non_neg_integer(), max_t = pos_integer(), groups = #{any() := #group{count_hash = pos_integer(), count_random = pos_integer(), count_total = pos_integer(), hash = fun((any(), pos_integer()) -> non_neg_integer()), random = fun((pos_integer()) -> non_neg_integer()), processes = array:array (pid())}}, processes = #{pid() := {any(), non_neg_integer()}}, monitors = [reference()]}
65 |
66 |
67 |
68 |
69 |
70 |
update(X1::any(), Varpool::#varpool{owner = pid(), supervisor = pid(), max_r = non_neg_integer(), max_t = pos_integer(), groups = #{any() := #group{count_hash = pos_integer(), count_random = pos_integer(), count_total = pos_integer(), hash = fun((any(), pos_integer()) -> non_neg_integer()), random = fun((pos_integer()) -> non_neg_integer()), processes = array:array (pid())}}, processes = #{pid() := {any(), non_neg_integer()}}, monitors = [reference()]}) -> {updated, #varpool{owner = pid(), supervisor = pid(), max_r = non_neg_integer(), max_t = pos_integer(), groups = #{any() := #group{count_hash = pos_integer(), count_random = pos_integer(), count_total = pos_integer(), hash = fun((any(), pos_integer()) -> non_neg_integer()), random = fun((pos_integer()) -> non_neg_integer()), processes = array:array (pid())}}, processes = #{pid() := {any(), non_neg_integer()}}, monitors = [reference()]}} | {ignored, #varpool{owner = pid(), supervisor = pid(), max_r = non_neg_integer(), max_t = pos_integer(), groups = #{any() := #group{count_hash = pos_integer(), count_random = pos_integer(), count_total = pos_integer(), hash = fun((any(), pos_integer()) -> non_neg_integer()), random = fun((pos_integer()) -> non_neg_integer()), processes = array:array (pid())}}, processes = #{pid() := {any(), non_neg_integer()}}, monitors = [reference()]}}
71 |
72 |
73 |
74 |
75 |
76 | Generated by EDoc
77 |
78 |
79 |
--------------------------------------------------------------------------------
/doc/varpool_sup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Module varpool_sup
6 |
7 |
8 |
9 |
10 |
11 |
12 | Module varpool_sup
13 |
14 | .
15 | Copyright © 2015-2020 Michael Truog
16 |
17 | Version: 2.0.1 May 26 2021 19:32:09
18 | ------------------------------------------------------------------------
19 | Authors: Michael Truog (mjtruog at protonmail dot com ).
20 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
init(X1) -> any()
37 |
38 |
39 |
40 |
41 |
42 |
start_link(Parent::pid(), MaxR::non_neg_integer(), MaxT::pos_integer(), Pool::nonempty_list()) -> {ok, pid()} | {error, any()}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
stop_link(Supervisor::pid()) -> ok
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | Generated by EDoc
59 |
60 |
61 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | #-*-Mode:elixir;coding:utf-8;tab-width:2;c-basic-offset:2;indent-tabs-mode:()-*-
2 | # ex: set ft=elixir fenc=utf-8 sts=2 ts=2 sw=2 et nomod:
3 |
4 | defmodule Varpool.Mixfile do
5 | use Mix.Project
6 |
7 | def project do
8 | [app: :varpool,
9 | version: "2.0.7",
10 | language: :erlang,
11 | erlc_options: [
12 | :deterministic,
13 | :debug_info,
14 | :warn_export_vars,
15 | :warn_unused_import,
16 | #:warn_missing_spec,
17 | :warnings_as_errors],
18 | description: description(),
19 | package: package(),
20 | deps: deps()]
21 | end
22 |
23 | def application do
24 | [applications: [
25 | :reltool_util]]
26 | end
27 |
28 | defp deps do
29 | [{:reltool_util, ">= 2.0.7"}]
30 | end
31 |
32 | defp description do
33 | "Erlang Process Pools as a Local Variable"
34 | end
35 |
36 | defp package do
37 | [files: ~w(src doc test rebar.config README.markdown LICENSE),
38 | maintainers: ["Michael Truog"],
39 | licenses: ["MIT"],
40 | links: %{"GitHub" => "https://github.com/okeuday/varpool"}]
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | %-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
2 | % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
3 |
4 | {deps,
5 | [{reltool_util, ".*",
6 | {git, "https://github.com/okeuday/reltool_util.git",
7 | {tag, "v2.0.7"}}}]}.
8 |
9 | {erl_opts,
10 | [warn_export_vars,
11 | warn_unused_import,
12 | %warn_missing_spec,
13 | warnings_as_errors]}.
14 | {cover_enabled, true}.
15 | {cover_print_enabled, true}.
16 | {cover_export_enabled, true}.
17 |
18 |
--------------------------------------------------------------------------------
/src/varpool.app.src:
--------------------------------------------------------------------------------
1 | %-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
2 | % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
3 |
4 | {application, varpool,
5 | [{description, "Local Variable Pool"},
6 | {vsn, "2.0.7"},
7 | {modules, [
8 | varpool,
9 | varpool_sup
10 | ]},
11 | {registered, []},
12 | {applications, [
13 | reltool_util,
14 | stdlib,
15 | kernel
16 | ]}]}.
17 |
18 |
--------------------------------------------------------------------------------
/src/varpool.erl:
--------------------------------------------------------------------------------
1 | %-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
2 | % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
3 | %%%
4 | %%%------------------------------------------------------------------------
5 | %%% @doc
6 | %%% ==Local Variable Pool==
7 | %%% @end
8 | %%%
9 | %%% MIT License
10 | %%%
11 | %%% Copyright (c) 2015-2021 Michael Truog
12 | %%%
13 | %%% Permission is hereby granted, free of charge, to any person obtaining a
14 | %%% copy of this software and associated documentation files (the "Software"),
15 | %%% to deal in the Software without restriction, including without limitation
16 | %%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 | %%% and/or sell copies of the Software, and to permit persons to whom the
18 | %%% Software is furnished to do so, subject to the following conditions:
19 | %%%
20 | %%% The above copyright notice and this permission notice shall be included in
21 | %%% all copies or substantial portions of the Software.
22 | %%%
23 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 | %%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 | %%% DEALINGS IN THE SOFTWARE.
30 | %%%
31 | %%% @author Michael Truog
32 | %%% @copyright 2015-2021 Michael Truog
33 | %%% @version 2.0.1 {@date} {@time}
34 | %%%------------------------------------------------------------------------
35 |
36 | -module(varpool).
37 | -author('mjtruog at protonmail dot com').
38 |
39 | %% external interface
40 | -export([new/1,
41 | destroy/1,
42 | update/2,
43 | get/2,
44 | get/3]).
45 |
46 | -record(group,
47 | {
48 | count_hash :: pos_integer(), % dimension 1
49 | count_random :: pos_integer(), % dimension 2
50 | count_total :: pos_integer(),
51 | hash :: fun((any(), pos_integer()) -> non_neg_integer()),
52 | random :: fun((pos_integer()) -> non_neg_integer()),
53 | processes :: array:array(pid())
54 | }).
55 |
56 | -record(varpool,
57 | {
58 | owner :: pid(),
59 | supervisor :: pid(),
60 | max_r :: non_neg_integer(),
61 | max_t :: pos_integer(),
62 | groups :: #{any() := #group{}},
63 | processes = #{} :: #{pid() := {any(), non_neg_integer()}},
64 | monitors = [] :: list(reference())
65 | }).
66 |
67 | -define(DEFAULT_MAX_R, 5). % max restart count
68 | -define(DEFAULT_MAX_T, 300). % max time in seconds
69 | -define(DEFAULT_HASH, fun erlang:phash2/2).
70 | -define(DEFAULT_RANDOM,
71 | fun(Count) ->
72 | % assuming exsplus/exsp for 58 bits, period 8.31e34
73 | rand:uniform(Count) - 1
74 | end).
75 |
76 | -type group() ::
77 | {Group :: any(),
78 | {M :: module(), F :: atom(), A :: list()},
79 | Options :: list({shutdown, pos_integer()} |
80 | {count_hash, pos_integer()} |
81 | {count_random, pos_integer()} |
82 | {hash,
83 | {module(), atom()} |
84 | fun((any(), pos_integer()) -> non_neg_integer())} |
85 | {random,
86 | {module(), atom()} |
87 | fun((pos_integer()) -> non_neg_integer())})}.
88 | -type options() ::
89 | nonempty_list({max_r, non_neg_integer()} |
90 | {max_t, pos_integer()} |
91 | {groups, nonempty_list(group())}).
92 | -export_type([options/0]).
93 |
94 | %%%------------------------------------------------------------------------
95 | %%% External interface functions
96 | %%%------------------------------------------------------------------------
97 |
98 | -spec new(Options :: options()) ->
99 | #varpool{}.
100 |
101 | new([_ | _] = Options) ->
102 | Defaults = [
103 | {max_r, ?DEFAULT_MAX_R},
104 | {max_t, ?DEFAULT_MAX_T},
105 | {groups, []}],
106 | [MaxR, MaxT, Groups] = take_values(Defaults, Options),
107 | true = is_integer(MaxR) andalso (MaxR >= 0),
108 | true = is_integer(MaxT) andalso (MaxT >= 1),
109 | true = is_list(Groups) andalso (length(Groups) > 0),
110 | ShutdownDefault = if
111 | MaxR >= 1 ->
112 | ((MaxT * 1000) div MaxR) - 100;
113 | MaxR == 0 ->
114 | (MaxT * 1000) - 100
115 | end,
116 | {GroupsData, GroupsSupervisor} = groups(Groups, ShutdownDefault),
117 | Owner = self(),
118 | {ok, Supervisor} = varpool_sup:start_link(Owner, MaxR, MaxT,
119 | GroupsSupervisor),
120 | #varpool{owner = Owner,
121 | supervisor = Supervisor,
122 | max_r = MaxR,
123 | max_t = MaxT,
124 | groups = GroupsData}.
125 |
126 | -spec destroy(#varpool{}) ->
127 | ok.
128 |
129 | destroy(#varpool{owner = Owner,
130 | supervisor = Supervisor,
131 | monitors = Monitors}) ->
132 | true = Owner =:= self(),
133 | [erlang:demonitor(MonitorRef, [flush]) || MonitorRef <- Monitors],
134 | ok = varpool_sup:stop_link(Supervisor),
135 | ok.
136 |
137 | -spec update(any(), #varpool{}) ->
138 | {updated, #varpool{}} |
139 | {ignored, #varpool{}}.
140 |
141 | update({'UP', Supervisor, process, Child, {Group, I} = Info},
142 | #varpool{owner = Owner,
143 | supervisor = Supervisor,
144 | groups = Groups,
145 | processes = Processes,
146 | monitors = Monitors} = VarPool) ->
147 | true = Owner =:= self(),
148 | NewProcesses = maps:put(Child, Info, Processes),
149 | GroupState = maps:get(Group, Groups),
150 | #group{processes = GroupProcesses} = GroupState,
151 | NewGroupProcesses = array:set(I, Child, GroupProcesses),
152 | NewGroupState = GroupState#group{processes = NewGroupProcesses},
153 | NewGroups = maps:put(Group, NewGroupState, Groups),
154 | NewMonitors = [erlang:monitor(process, Child) | Monitors],
155 | {updated, VarPool#varpool{groups = NewGroups,
156 | processes = NewProcesses,
157 | monitors = NewMonitors}};
158 | update({'DOWN', MonitorRef, process, Child, _Info},
159 | #varpool{owner = Owner,
160 | groups = Groups,
161 | processes = Processes,
162 | monitors = Monitors} = VarPool) ->
163 | true = Owner =:= self(),
164 | case maps:find(Child, Processes) of
165 | error ->
166 | {ignored, VarPool};
167 | {ok, {Group, I}} ->
168 | NewProcesses = maps:remove(Child, Processes),
169 | GroupState = maps:get(Group, Groups),
170 | #group{processes = GroupProcesses} = GroupState,
171 | NewGroupProcesses = array:set(I, undefined, GroupProcesses),
172 | NewGroupState = GroupState#group{processes = NewGroupProcesses},
173 | NewGroups = maps:put(Group, NewGroupState, Groups),
174 | NewMonitors = lists:delete(MonitorRef, Monitors),
175 | {updated, VarPool#varpool{groups = NewGroups,
176 | processes = NewProcesses,
177 | monitors = NewMonitors}}
178 | end;
179 | update(_,
180 | #varpool{owner = Owner} = VarPool) ->
181 | true = Owner =:= self(),
182 | {ignored, VarPool}.
183 |
184 | -spec get(Group :: any(), #varpool{}) ->
185 | pid() | undefined.
186 |
187 | get(Group, #varpool{groups = Groups}) ->
188 | #group{count_hash = 1,
189 | count_random = CountRandom,
190 | random = Random,
191 | processes = GroupProcesses} = maps:get(Group, Groups),
192 | IndexHash = 0,
193 | IndexRandom = if
194 | CountRandom > 1 ->
195 | Random(CountRandom);
196 | CountRandom =:= 1 ->
197 | 0
198 | end,
199 | I = IndexHash * CountRandom + IndexRandom,
200 | Child = array:get(I, GroupProcesses),
201 | if
202 | Child =:= undefined ->
203 | get_process(I,
204 | IndexHash * CountRandom,
205 | (IndexHash + 1) * CountRandom - 1, GroupProcesses);
206 | is_pid(Child) ->
207 | Child
208 | end.
209 |
210 | -spec get(Group :: any(), Key :: any(), #varpool{}) ->
211 | pid() | undefined.
212 |
213 | get(Group, Key, #varpool{groups = Groups}) ->
214 | #group{count_hash = CountHash,
215 | count_random = CountRandom,
216 | hash = Hash,
217 | random = Random,
218 | processes = GroupProcesses} = maps:get(Group, Groups),
219 | IndexHash = if
220 | CountHash > 1 ->
221 | Hash(Key, CountHash);
222 | CountHash =:= 1 ->
223 | 0
224 | end,
225 | IndexRandom = if
226 | CountRandom > 1 ->
227 | Random(CountRandom);
228 | CountRandom =:= 1 ->
229 | 0
230 | end,
231 | I = IndexHash * CountRandom + IndexRandom,
232 | Child = array:get(I, GroupProcesses),
233 | if
234 | Child =:= undefined ->
235 | get_process(I,
236 | IndexHash * CountRandom,
237 | (IndexHash + 1) * CountRandom - 1, GroupProcesses);
238 | is_pid(Child) ->
239 | Child
240 | end.
241 |
242 | %%%------------------------------------------------------------------------
243 | %%% Private functions
244 | %%%------------------------------------------------------------------------
245 |
246 | groups([], GroupsData, GroupsSupervisor, _) ->
247 | {GroupsData, lists:reverse(GroupsSupervisor)};
248 | groups([{Group, {M, F, A}, Options} | Groups],
249 | GroupsData, GroupsSupervisor, ShutdownDefault)
250 | when is_atom(M), is_atom(F), is_list(A), is_list(Options) ->
251 | Defaults = [
252 | {shutdown, ShutdownDefault},
253 | {count_hash, 1},
254 | {count_random, 1},
255 | {hash, ?DEFAULT_HASH},
256 | {random, ?DEFAULT_RANDOM}],
257 | [Shutdown, CountHash, CountRandom,
258 | Hash0, Random0] = take_values(Defaults, Options),
259 | true = is_integer(Shutdown) andalso
260 | (Shutdown >= 1) andalso (Shutdown =< ShutdownDefault),
261 | true = is_integer(CountHash) andalso (CountHash >= 1),
262 | true = is_integer(CountRandom) andalso (CountRandom >= 1),
263 | Hash1 = case Hash0 of
264 | {HashModule, HashFunction}
265 | when is_atom(HashModule), is_atom(HashFunction) ->
266 | true = erlang:function_exported(HashModule, HashFunction, 2),
267 | fun(HashArg1, HashArg2) ->
268 | HashModule:HashFunction(HashArg1, HashArg2)
269 | end;
270 | _ when is_function(Hash0, 2) ->
271 | Hash0
272 | end,
273 | Random1 = case Random0 of
274 | {RandomModule, RandomFunction}
275 | when is_atom(RandomModule), is_atom(RandomFunction) ->
276 | true = erlang:function_exported(RandomModule, RandomFunction, 1),
277 | fun(RandomArg1) ->
278 | RandomModule:RandomFunction(RandomArg1)
279 | end;
280 | _ when is_function(Random0, 1) ->
281 | Random0
282 | end,
283 | CountTotal = CountHash * CountRandom,
284 | GroupProcesses = array:new([{size, CountTotal}]),
285 | NewGroupsData = maps:put(Group,
286 | #group{count_hash = CountHash,
287 | count_random = CountRandom,
288 | count_total = CountTotal,
289 | hash = Hash1,
290 | random = Random1,
291 | processes = GroupProcesses},
292 | GroupsData),
293 | NewGroupsSupervisor = [{Group, CountTotal, M, F, A, Shutdown} |
294 | GroupsSupervisor],
295 | groups(Groups, NewGroupsData, NewGroupsSupervisor, ShutdownDefault).
296 |
297 | groups(Groups, ShutdownDefault) ->
298 | groups(Groups, #{}, [], ShutdownDefault).
299 |
300 | get_process(Istop, Istop, _, _, _) ->
301 | undefined;
302 | get_process(I, Istop, Istart, Iend, GroupProcesses) ->
303 | Child = array:get(I, GroupProcesses),
304 | if
305 | Child =:= undefined ->
306 | if
307 | I == Iend ->
308 | get_process(Istart, Istop, Istart, Iend, GroupProcesses);
309 | true ->
310 | get_process(I + 1, Istop, Istart, Iend, GroupProcesses)
311 | end;
312 | is_pid(Child) ->
313 | Child
314 | end.
315 |
316 | get_process(Iend, Istart, Iend, GroupProcesses) ->
317 | get_process(Istart, Iend, Istart, Iend, GroupProcesses);
318 | get_process(I, Istart, Iend, GroupProcesses) ->
319 | get_process(I + 1, I, Istart, Iend, GroupProcesses).
320 |
321 | take_values(DefaultList, List)
322 | when is_list(DefaultList), is_list(List) ->
323 | take_values([], DefaultList, List).
324 |
325 | take_values(Result, [], List) ->
326 | lists:reverse(Result) ++ List;
327 |
328 | take_values(Result, [{Key, Default} | DefaultList], List)
329 | when is_atom(Key) ->
330 | case lists:keytake(Key, 1, List) of
331 | false ->
332 | take_values([Default | Result], DefaultList, List);
333 | {value, {Key, Value}, RemainingList} ->
334 | take_values([Value | Result], DefaultList, RemainingList)
335 | end.
336 |
337 |
--------------------------------------------------------------------------------
/src/varpool_sup.erl:
--------------------------------------------------------------------------------
1 | %-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
2 | % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
3 | %%%
4 | %%%------------------------------------------------------------------------
5 | %%% @doc
6 | %%% ==Local Variable Pool Supervisor==
7 | %%% @end
8 | %%%
9 | %%% MIT License
10 | %%%
11 | %%% Copyright (c) 2015-2020 Michael Truog
12 | %%%
13 | %%% Permission is hereby granted, free of charge, to any person obtaining a
14 | %%% copy of this software and associated documentation files (the "Software"),
15 | %%% to deal in the Software without restriction, including without limitation
16 | %%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 | %%% and/or sell copies of the Software, and to permit persons to whom the
18 | %%% Software is furnished to do so, subject to the following conditions:
19 | %%%
20 | %%% The above copyright notice and this permission notice shall be included in
21 | %%% all copies or substantial portions of the Software.
22 | %%%
23 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 | %%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 | %%% DEALINGS IN THE SOFTWARE.
30 | %%%
31 | %%% @author Michael Truog
32 | %%% @copyright 2015-2020 Michael Truog
33 | %%% @version 2.0.1 {@date} {@time}
34 | %%%------------------------------------------------------------------------
35 |
36 | -module(varpool_sup).
37 | -author('mjtruog at protonmail dot com').
38 |
39 | %% external interface
40 | -export([start_link/4,
41 | stop_link/1]).
42 |
43 | %% internal callbacks
44 | -export([monitor_up/6,
45 | stop_link_process/0]).
46 |
47 | %% supervisor callbacks
48 | -export([init/1]).
49 |
50 | %%%------------------------------------------------------------------------
51 | %%% External interface functions
52 | %%%------------------------------------------------------------------------
53 |
54 | %%-------------------------------------------------------------------------
55 | %% @doc
56 | %% ===Start the Pool supervisor.===
57 | %% @end
58 | %%-------------------------------------------------------------------------
59 |
60 | -spec start_link(Parent :: pid(),
61 | MaxR :: non_neg_integer(),
62 | MaxT :: pos_integer(),
63 | Pool :: nonempty_list()) ->
64 | {'ok', pid()} |
65 | {'error', any()}.
66 |
67 | start_link(Parent, MaxR, MaxT, [_ | _] = Pool)
68 | when is_pid(Parent), is_integer(MaxR), MaxR >= 0,
69 | is_integer(MaxT), MaxT >= 1 ->
70 | supervisor:start_link(?MODULE, [Parent, MaxR, MaxT, Pool]).
71 |
72 | %%-------------------------------------------------------------------------
73 | %% @doc
74 | %% ===Stop the Pool supervisor.===
75 | %% @end
76 | %%-------------------------------------------------------------------------
77 |
78 | -spec stop_link(Supervisor :: pid()) ->
79 | 'ok'.
80 |
81 | stop_link(Supervisor)
82 | when is_pid(Supervisor) ->
83 | erlang:unlink(Supervisor),
84 | {ok, _} = supervisor:start_child(Supervisor,
85 | {shutdown,
86 | {?MODULE, stop_link_process, []},
87 | permanent, brutal_kill, worker, []}),
88 | ok.
89 |
90 | %%-------------------------------------------------------------------------
91 | %% @hidden
92 | %% Internal callback
93 | %% @end
94 | %%-------------------------------------------------------------------------
95 |
96 | -spec monitor_up(Parent :: pid(),
97 | M :: module(),
98 | F :: atom(),
99 | A :: list(),
100 | Group :: any(),
101 | I :: non_neg_integer()) ->
102 | any().
103 |
104 | monitor_up(Parent, M, F, A, Group, I) ->
105 | case erlang:apply(M, F, A) of
106 | {ok, Child} = Success ->
107 | ok = monitor_up_message(Parent, Child, Group, I),
108 | Success;
109 | {ok, Child, _Info} = Success ->
110 | ok = monitor_up_message(Parent, Child, Group, I),
111 | Success;
112 | ignore = Ignore ->
113 | Ignore;
114 | Error ->
115 | Error
116 | end.
117 |
118 | %%-------------------------------------------------------------------------
119 | %% @hidden
120 | %% Internal callback
121 | %% @end
122 | %%-------------------------------------------------------------------------
123 |
124 | -spec stop_link_process() ->
125 | {ok, pid()}.
126 |
127 | stop_link_process() ->
128 | Child = erlang:spawn_link(fun stop_link_process_kill/0),
129 | {ok, Child}.
130 |
131 | %%%------------------------------------------------------------------------
132 | %%% Callback functions from supervisor
133 | %%%------------------------------------------------------------------------
134 |
135 | init([Parent, MaxR, MaxT, Pool]) ->
136 | {ok, {{one_for_one, MaxR, MaxT}, child_specs(Pool, Parent)}}.
137 |
138 | %%%------------------------------------------------------------------------
139 | %%% Private functions
140 | %%%------------------------------------------------------------------------
141 |
142 | monitor_up_message(Parent, Child, Group, I) ->
143 | Parent ! {'UP', self(), process, Child, {Group, I}},
144 | ok.
145 |
146 | child_specs([], ChildSpecs, _) ->
147 | ChildSpecs;
148 | child_specs([{Group, Count, M, F, A, Shutdown} | Pool], ChildSpecs, Parent) ->
149 | NewChildSpecs = case module_behaviour(M) of
150 | undefined ->
151 | child_spec(Count - 1, Group, Parent, M, F, A,
152 | Shutdown, worker, [M]);
153 | gen_event ->
154 | child_spec(Count - 1, Group, Parent, M, F, A,
155 | Shutdown, worker, dynamic);
156 | supervisor ->
157 | child_spec(Count - 1, Group, Parent, M, F, A,
158 | infinity, supervisor, [M])
159 | end ++ ChildSpecs,
160 | child_specs(Pool, NewChildSpecs, Parent).
161 |
162 | child_specs(Pool, Parent) ->
163 | child_specs(Pool, [], Parent).
164 |
165 | child_spec_entry(I, Group, Parent, M, F, A, Shutdown, Type, Modules) ->
166 | {{Group, I}, {?MODULE, monitor_up, [Parent, M, F, A, Group, I]},
167 | permanent, Shutdown, Type, Modules}.
168 |
169 | child_spec(0 = I, Group, Parent, M, F, A, Shutdown, Type, Modules) ->
170 | [child_spec_entry(I, Group, Parent, M, F, A, Shutdown, Type, Modules)];
171 | child_spec(I, Group, Parent, M, F, A, Shutdown, Type, Modules) ->
172 | [child_spec_entry(I, Group, Parent, M, F, A, Shutdown, Type, Modules) |
173 | child_spec(I - 1, Group, Parent, M, F, A, Shutdown, Type, Modules)].
174 |
175 | special_behaviour([]) ->
176 | undefined;
177 | special_behaviour([supervisor = Behaviour | _]) ->
178 | Behaviour;
179 | special_behaviour([gen_event = Behaviour | _]) ->
180 | Behaviour;
181 | special_behaviour([_ | L]) ->
182 | special_behaviour(L).
183 |
184 | module_behaviour(M) ->
185 | special_behaviour(reltool_util:module_behaviours(M)).
186 |
187 | -spec stop_link_process_kill() -> no_return().
188 |
189 | stop_link_process_kill() ->
190 | erlang:exit(shutdown).
191 |
--------------------------------------------------------------------------------
/test/varpool_test_server.erl:
--------------------------------------------------------------------------------
1 | %-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
2 | % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
3 | %%%
4 | %%%------------------------------------------------------------------------
5 | %%% @doc
6 | %%% ==Local Variable Pool Test Server==
7 | %%% @end
8 | %%%
9 | %%% MIT License
10 | %%%
11 | %%% Copyright (c) 2015-2022 Michael Truog
12 | %%%
13 | %%% Permission is hereby granted, free of charge, to any person obtaining a
14 | %%% copy of this software and associated documentation files (the "Software"),
15 | %%% to deal in the Software without restriction, including without limitation
16 | %%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 | %%% and/or sell copies of the Software, and to permit persons to whom the
18 | %%% Software is furnished to do so, subject to the following conditions:
19 | %%%
20 | %%% The above copyright notice and this permission notice shall be included in
21 | %%% all copies or substantial portions of the Software.
22 | %%%
23 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 | %%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 | %%% DEALINGS IN THE SOFTWARE.
30 | %%%
31 | %%% @author Michael Truog
32 | %%% @copyright 2015-2022 Michael Truog
33 | %%% @version 2.0.5 {@date} {@time}
34 | %%%------------------------------------------------------------------------
35 |
36 | -module(varpool_test_server).
37 | -author('mjtruog at protonmail dot com').
38 |
39 | %% external interface
40 | -export([start_link/0,
41 | ping/1]).
42 |
43 | %%%------------------------------------------------------------------------
44 | %%% External interface functions
45 | %%%------------------------------------------------------------------------
46 |
47 | start_link() ->
48 | {ok, erlang:spawn_link(fun loop/0)}.
49 |
50 | ping(Pid) ->
51 | Pid ! {self(), ping},
52 | receive
53 | pong ->
54 | pong
55 | after
56 | 5000 ->
57 | pang
58 | end.
59 |
60 | %%%------------------------------------------------------------------------
61 | %%% Private functions
62 | %%%------------------------------------------------------------------------
63 |
64 | loop() ->
65 | receive
66 | {Pid, ping} ->
67 | Pid ! pong
68 | end,
69 | loop().
70 |
71 | -ifdef(TEST).
72 | -include_lib("eunit/include/eunit.hrl").
73 |
74 | -ifdef(CLOUDI_TEST_TIMEOUT).
75 | -define(TEST_TIMEOUT, ?CLOUDI_TEST_TIMEOUT). % seconds
76 | -else.
77 | -define(TEST_TIMEOUT, 10). % seconds
78 | -endif.
79 |
80 | module_test_() ->
81 | {timeout, ?TEST_TIMEOUT, [
82 | {"internal tests", ?_assertEqual(ok, t_basic())}
83 | ]}.
84 |
85 | t_basic() ->
86 | application:start(varpool),
87 | P0 = varpool:new([{groups,
88 | [{group_0, {varpool_test_server, start_link, []},
89 | [{count_hash, 4}, {count_random, 4}]}
90 | ]}]),
91 | P3 = lists:foldl(fun(_, P1) ->
92 | receive
93 | Up0 ->
94 | {'UP', _, process, _, {_, _}} = Up0,
95 | {updated, P2} = varpool:update(Up0, P1),
96 | P2
97 | end
98 | end, P0, lists:seq(1, 16)),
99 | Child0 = varpool:get(group_0, 0, P3),
100 | Child1 = varpool:get(group_0, 1, P3),
101 | true = (Child0 /= Child1),
102 | true = is_pid(Child0),
103 | true = is_pid(Child1),
104 | pong = varpool_test_server:ping(Child0),
105 | pong = varpool_test_server:ping(Child1),
106 | erlang:exit(Child0, kill),
107 | P5 = receive
108 | Down0 ->
109 | {'DOWN', _, process, _, _} = Down0,
110 | {updated, P4} = varpool:update(Down0, P3),
111 | P4
112 | end,
113 | PN = receive
114 | Up1 ->
115 | {'UP', _, process, _, {_, _}} = Up1,
116 | {updated, P6} = varpool:update(Up1, P5),
117 | P6
118 | end,
119 | Child0New = varpool:get(group_0, 0, PN),
120 | true = (Child0 /= Child0New),
121 | true = is_pid(Child0New),
122 | ok = varpool:destroy(PN),
123 | ok.
124 |
125 | -endif.
126 |
--------------------------------------------------------------------------------