├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── erlang.mk ├── examples └── simple_pool │ ├── Makefile │ ├── README.md │ ├── relx.config │ └── src │ ├── simple_pool.app.src │ ├── simple_pool.erl │ ├── simple_pool_app.erl │ └── simple_pool_sup.erl └── src ├── octopus.app.src ├── octopus.erl ├── octopus_app.erl ├── octopus_name_resolver.erl ├── octopus_pool_sup.erl ├── octopus_sup.erl └── pool ├── octopus_pool_config_server.erl ├── octopus_pool_task_server.erl └── octopus_pool_workers_sup.erl /.gitignore: -------------------------------------------------------------------------------- 1 | ebin/ 2 | .erlang.mk/ 3 | *.plt 4 | octopus.d 5 | xrefr 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Sergiy Kostyushkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT = octopus 2 | PROJECT_VERSION = 1.1.0 3 | 4 | include erlang.mk 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Octopus 2 | ============ 3 | 4 | Octopus is a small and flexible pool manager written in Erlang. 5 | 6 | Getting Started 7 | =============== 8 | 9 | ```erl 10 | 11 | %% Start pool 12 | 1> Opts = [ 13 | {host, "jamdb-sybase-dev.erlangbureau.dp.ua"}, 14 | {port, 5000}, 15 | {user, "jamdbtest"}, 16 | {password, "jamdbtest"}, 17 | {database, "jamdbtest"} 18 | ]. 19 | 20 | 2> octopus:start_pool(test_pool, [{pool_size, 3}, {worker, jamdb_sybase}], [Opts]). 21 | ok 22 | 23 | 3> {ok, Pid} = octopus:worker_lockout(test_pool). 24 | {ok,<0.120.0>} 25 | 26 | %% Execute task 27 | 4> jamdb_sybase:sql_query(Pid, "select 1 as one, 2 as two, 3 as three"). 28 | {ok,[{result_set,[<<"one">>,<<"two">>,<<"three">>], 29 | [], 30 | [[1,2,3]]}]} 31 | 32 | 5> octopus:worker_lockin(test_pool). 33 | ok 34 | 35 | ``` 36 | 37 | Alternatives 38 | ============ 39 | * [poolboy](https://github.com/devinus/poolboy) - A hunky Erlang worker pool factory 40 | * [pooler](https://github.com/seth/pooler) - An OTP Process Pool Application 41 | * [episcina](https://github.com/erlware/episcina) - A simple non intrusive resource pool for connections 42 | * [cuesport](https://github.com/goj/cuesport) - Simple Erlang pool of workers 43 | * [worker_pool](https://github.com/inaka/worker_pool) - Erlang worker pool 44 | -------------------------------------------------------------------------------- /examples/simple_pool/Makefile: -------------------------------------------------------------------------------- 1 | PROJECT = simple_pool 2 | 3 | DEPS = jamdb_sybase octopus 4 | 5 | dep_jamdb_sybase = git https://github.com/erlangbureau/jamdb_sybase.git 0.6.0 6 | dep_octopus = git https://github.com/erlangbureau/octopus.git master 7 | 8 | include ../../erlang.mk 9 | -------------------------------------------------------------------------------- /examples/simple_pool/README.md: -------------------------------------------------------------------------------- 1 | Simple pool example 2 | =================== 3 | 4 | To try this example, you need GNU `make` and `git` in your PATH. 5 | 6 | To build the example, run the following command: 7 | 8 | ``` bash 9 | $ make 10 | ``` 11 | 12 | To start the release in the console mode: 13 | 14 | ``` bash 15 | $ ./_rel/simple_pool_example/bin/simple_pool_example console 16 | ``` 17 | 18 | Example output 19 | -------------- 20 | 21 | ``` 22 | Pool Info before lockout: [{init,0},{ready,3},{busy,0}] 23 | Pool Info after lockout: [{init,0},{ready,2},{busy,1}] 24 | 25 | Execute query: "select 1 as one, 2 as two, 3 as three" 26 | Result: {ok,[{result_set,[<<"one">>,<<"two">>,<<"three">>],[],[[1,2,3]]}]} 27 | 28 | Pool Info after lockin: [{init,0},{ready,3},{busy,0}] 29 | ``` 30 | -------------------------------------------------------------------------------- /examples/simple_pool/relx.config: -------------------------------------------------------------------------------- 1 | {release, {simple_pool_example, "0.1.0"}, [simple_pool]}. 2 | {extended_start_script, true}. 3 | -------------------------------------------------------------------------------- /examples/simple_pool/src/simple_pool.app.src: -------------------------------------------------------------------------------- 1 | {application, simple_pool, [ 2 | {description, ""}, 3 | {vsn, "0.1.0"}, 4 | {id, "git"}, 5 | {modules, []}, 6 | {registered, []}, 7 | {applications, [ 8 | kernel, 9 | stdlib, 10 | octopus, 11 | jamdb_sybase 12 | ]}, 13 | {mod, {simple_pool_app, []}}, 14 | {env, []} 15 | ]}. 16 | -------------------------------------------------------------------------------- /examples/simple_pool/src/simple_pool.erl: -------------------------------------------------------------------------------- 1 | -module(simple_pool). 2 | 3 | %% API 4 | -export([start/0, stop/0]). 5 | 6 | %% API 7 | start() -> 8 | start(?MODULE). 9 | 10 | stop() -> 11 | application:stop(?MODULE). 12 | 13 | %% internal 14 | start(AppName) -> 15 | F = fun({App, _, _}) -> App end, 16 | ok = load(AppName), 17 | {ok, Dependencies} = application:get_key(AppName, applications), 18 | [begin 19 | RunningApps = lists:map(F, application:which_applications()), 20 | case lists:member(A, RunningApps) of 21 | true -> ok; 22 | false -> ok = start(A) 23 | end 24 | end || A <- Dependencies], 25 | ok = application:start(AppName). 26 | 27 | load(AppName) -> 28 | F = fun({App, _, _}) -> App end, 29 | LoadedApps = lists:map(F, application:loaded_applications()), 30 | case lists:member(AppName, LoadedApps) of 31 | true -> 32 | ok; 33 | false -> 34 | ok = application:load(AppName) 35 | end. 36 | -------------------------------------------------------------------------------- /examples/simple_pool/src/simple_pool_app.erl: -------------------------------------------------------------------------------- 1 | -module(simple_pool_app). 2 | -behaviour(application). 3 | 4 | %% API 5 | -export([start/2]). 6 | -export([stop/1]). 7 | 8 | %% private 9 | -export([sql_query/0]). 10 | 11 | %% API 12 | start(_Type, _Args) -> 13 | Opts = [ 14 | {host, "jamdb-sybase-dev.erlangbureau.dp.ua"}, 15 | {port, 5000}, 16 | {user, "jamdbtest"}, 17 | {password, "jamdbtest"}, 18 | {database, "jamdbtest"} 19 | ], 20 | ok = octopus:start_pool(test_pool, [{pool_size, 3}, {worker, jamdb_sybase}], [Opts]), 21 | _ = timer:apply_interval(3000, ?MODULE, sql_query, []), 22 | _ = sql_query(), 23 | simple_pool_sup:start_link(). 24 | 25 | stop(_State) -> 26 | ok. 27 | 28 | %% private 29 | sql_query() -> 30 | PoolInfo = octopus:pool_info(test_pool), 31 | io:format("Pool Info before lockout: ~p~n", [PoolInfo]), 32 | 33 | {ok, Pid} = octopus:worker_lockout(test_pool), 34 | PoolInfo2 = octopus:pool_info(test_pool), 35 | io:format("Pool Info after lockout: ~p~n~n", [PoolInfo2]), 36 | 37 | Query = "select 1 as one, 2 as two, 3 as three", 38 | Result = jamdb_sybase:sql_query(Pid, "select 1 as one, 2 as two, 3 as three"), 39 | io:format("Execute query: ~p~n", [Query]), 40 | io:format(" Result: ~p~n~n", [Result]), 41 | 42 | ok = octopus:worker_lockin(test_pool), 43 | PoolInfo3 = octopus:pool_info(test_pool), 44 | io:format("Pool Info after lockin: ~p~n~n", [PoolInfo3]), 45 | io:format("==============================~n~n", []), 46 | Result. 47 | -------------------------------------------------------------------------------- /examples/simple_pool/src/simple_pool_sup.erl: -------------------------------------------------------------------------------- 1 | -module(simple_pool_sup). 2 | -behaviour(supervisor). 3 | 4 | -export([start_link/0]). 5 | -export([init/1]). 6 | 7 | start_link() -> 8 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 9 | 10 | init([]) -> 11 | Procs = [], 12 | {ok, {{one_for_one, 1, 5}, Procs}}. 13 | -------------------------------------------------------------------------------- /src/octopus.app.src: -------------------------------------------------------------------------------- 1 | {application, octopus, [ 2 | {description, "Small and flexible pool manager"}, 3 | {vsn, "1.1.0"}, 4 | {modules, []}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {mod, {octopus_app, []}} 11 | ]}. 12 | -------------------------------------------------------------------------------- /src/octopus.erl: -------------------------------------------------------------------------------- 1 | -module(octopus). 2 | 3 | % API 4 | -export([start_pool/3]). 5 | -export([stop_pool/1]). 6 | -export([worker_lockout/1, worker_lockout/2]). 7 | -export([worker_lockin/1]). 8 | -export([perform/2, perform/3]). 9 | -export([get_pool_config/1, set_pool_config/3, set_pool_config/4]). 10 | -export([pool_info/1, pool_info/2]). 11 | -export([get_group/1, set_group/2]). 12 | -export([map/2, reduce/3]). 13 | 14 | -define(DEFAULT_TIMEOUT, 5000). 15 | 16 | % API 17 | -spec start_pool(PoolId, PoolOpts, WorkerArgs) -> ok | {error, Reason} 18 | when 19 | PoolId :: atom(), 20 | PoolOpts :: proplists:proplist(), 21 | WorkerArgs :: proplists:proplist(), 22 | Reason :: term(). 23 | 24 | start_pool(PoolId, PoolOpts, WorkerArgs) when 25 | is_atom(PoolId), is_list(PoolOpts), is_list(WorkerArgs) -> 26 | ok = add_pool_config(PoolId, PoolOpts, WorkerArgs), 27 | case octopus_sup:start_pool(PoolId) of 28 | {ok, _Pid} -> ok; 29 | {ok, _Pid, _Info} -> ok; 30 | {error, {already_started, _Pid}} -> {error, already_started}; 31 | Error -> Error 32 | end. 33 | 34 | 35 | -spec stop_pool(PoolId) -> ok 36 | when 37 | PoolId :: atom(). 38 | 39 | stop_pool(PoolId) when is_atom(PoolId) -> 40 | _ = octopus_sup:stop_pool(PoolId), 41 | _ = delete_pool_config(PoolId), 42 | ok. 43 | 44 | 45 | -spec pool_info(PoolId) -> #{Item => non_neg_integer()} 46 | when 47 | PoolId :: atom(), 48 | Item :: init | ready | busy. 49 | 50 | pool_info(PoolId) when is_atom(PoolId) -> 51 | octopus_pool_task_server:pool_info(PoolId). 52 | 53 | 54 | -spec pool_info(PoolId, Item) -> non_neg_integer() 55 | when 56 | PoolId :: atom(), 57 | Item :: init | ready | busy. 58 | 59 | pool_info(PoolId, Item) when is_atom(PoolId) -> 60 | Info = octopus_pool_task_server:pool_info(PoolId), 61 | maps:get(Item, Info). 62 | 63 | 64 | -spec worker_lockout(PoolId) -> {ok, Pid} | {error, Reason} 65 | when 66 | PoolId :: atom(), 67 | Pid :: pid(), 68 | Reason :: term(). 69 | 70 | worker_lockout(PoolId) when is_atom(PoolId) -> 71 | worker_lockout(PoolId, ?DEFAULT_TIMEOUT). 72 | 73 | -spec worker_lockout(PoolId, Timeout) -> {ok, Pid} | {error, Reason} 74 | when 75 | PoolId :: atom(), 76 | Timeout :: timeout(), 77 | Pid :: pid(), 78 | Reason :: term(). 79 | 80 | worker_lockout(PoolId, Timeout) when is_atom(PoolId) -> 81 | octopus_pool_task_server:worker_lockout(PoolId, Timeout). 82 | 83 | 84 | -spec worker_lockin(PoolId) -> ok 85 | when 86 | PoolId :: atom(). 87 | 88 | worker_lockin(PoolId) when is_atom(PoolId) -> 89 | octopus_pool_task_server:worker_lockin(PoolId). 90 | 91 | 92 | -spec perform(PoolId, Fun) -> FunResult | {error, Reason} 93 | when 94 | PoolId :: atom(), 95 | Fun :: fun((pid()) -> term()), 96 | FunResult :: term(), 97 | Reason :: term(). 98 | 99 | perform(PoolId, Fun) when is_atom(PoolId) -> 100 | perform(PoolId, Fun, ?DEFAULT_TIMEOUT). 101 | 102 | -spec perform(PoolId, Fun, Timeout) -> FunResult | {error, Reason} 103 | when 104 | PoolId :: atom(), 105 | Fun :: fun((pid()) -> term()), 106 | Timeout :: timeout(), 107 | FunResult :: term(), 108 | Reason :: term(). 109 | 110 | perform(PoolId, Fun, Timeout) -> 111 | case worker_lockout(PoolId, Timeout) of 112 | {ok, Pid} -> 113 | try 114 | Fun(Pid) 115 | after 116 | ok = worker_lockin(PoolId) 117 | end; 118 | Error -> 119 | Error 120 | end. 121 | 122 | 123 | -spec get_pool_config(PoolId) -> Tuple | false 124 | when 125 | PoolId :: atom(), 126 | Tuple :: tuple(). 127 | 128 | get_pool_config(PoolId) when is_atom(PoolId) -> 129 | Pools = application:get_env(?MODULE, pools, []), 130 | lists:keyfind(PoolId, 1, Pools). 131 | 132 | 133 | -spec set_pool_config(PoolId, PoolOpts, WorkerArgs) -> ok 134 | when 135 | PoolId :: atom(), 136 | PoolOpts :: proplists:proplist(), 137 | WorkerArgs :: proplists:proplist(). 138 | 139 | set_pool_config(PoolId, PoolOpts, WorkerArgs) when is_atom(PoolId) -> 140 | set_pool_config(PoolId, PoolOpts, WorkerArgs, []). 141 | 142 | 143 | -spec set_pool_config(PoolId, PoolOpts, WorkerArgs, ChangeOpts) -> ok 144 | when 145 | PoolId :: atom(), 146 | PoolOpts :: proplists:proplist(), 147 | WorkerArgs :: proplists:proplist(), 148 | ChangeOpts :: proplists:proplist(). 149 | 150 | set_pool_config(PoolId, PoolOpts, WorkerArgs, ChangeOpts) when is_atom(PoolId) -> 151 | ok = add_pool_config(PoolId, PoolOpts, WorkerArgs), 152 | octopus_pool_config_server:config_change(PoolId, ChangeOpts). 153 | 154 | 155 | -spec get_group(GroupId) -> PoolList when 156 | GroupId :: atom(), 157 | PoolList :: list(). 158 | 159 | get_group(GroupId) -> 160 | Groups = application:get_env(?MODULE, groups, []), 161 | proplists:get_value(GroupId, Groups, []). 162 | 163 | 164 | -spec set_group(GroupId, PoolList) -> ok when 165 | GroupId :: atom(), 166 | PoolList :: list(). 167 | 168 | set_group(GroupId, PoolList) -> 169 | Groups = application:get_env(?MODULE, groups, []), 170 | Groups2 = lists:keystore(GroupId, 1, Groups, {GroupId, PoolList}), 171 | application:set_env(?MODULE, groups, Groups2). 172 | 173 | 174 | -spec map(GroupId, Fun) -> list() when 175 | GroupId :: atom(), 176 | PoolId :: atom(), 177 | Fun :: fun((PoolId) -> term()). 178 | 179 | map(GroupId, Fun) when is_function(Fun) -> 180 | PoolList = get_group(GroupId), 181 | [Fun(PoolId) || PoolId <- PoolList]. 182 | 183 | -spec reduce(GroupId, Fun, InitState) -> term() when 184 | GroupId :: atom(), 185 | PoolId :: atom(), 186 | InitState :: term(), 187 | Fun :: fun((PoolId, InitState) -> term()). 188 | 189 | reduce(GroupId, Fun, InitState) when is_function(Fun) -> 190 | PoolList = get_group(GroupId), 191 | lists:foldl(Fun, InitState, PoolList). 192 | 193 | % internal 194 | add_pool_config(PoolId, PoolOpts, WorkerArgs) -> 195 | Pools = application:get_env(?MODULE, pools, []), 196 | PoolCfg = {PoolId, PoolOpts, WorkerArgs}, 197 | Pools2 = lists:keystore(PoolId, 1, Pools, PoolCfg), 198 | application:set_env(?MODULE, pools, Pools2). 199 | 200 | delete_pool_config(PoolId) -> 201 | Pools = application:get_env(?MODULE, pools, []), 202 | Pools2 = lists:keydelete(PoolId, 1, Pools), 203 | application:set_env(?MODULE, pools, Pools2). 204 | -------------------------------------------------------------------------------- /src/octopus_app.erl: -------------------------------------------------------------------------------- 1 | -module(octopus_app). 2 | -behaviour(application). 3 | 4 | %% application callbacks 5 | -export([start/2]). 6 | -export([stop/1]). 7 | 8 | %% application callbacks 9 | start(_StartType, _StartArgs) -> 10 | Result = octopus_sup:start_link(), 11 | ok = start_pools_from_config(), 12 | Result. 13 | 14 | stop(_State) -> 15 | ok. 16 | 17 | %% internal 18 | start_pools_from_config() -> 19 | Pools = application:get_env(octopus, pools, []), 20 | _ = [ok = octopus:start_pool(PoolName, PoolOpts, WorkerOpts) 21 | || {PoolName, PoolOpts, WorkerOpts} <- Pools], 22 | ok. 23 | -------------------------------------------------------------------------------- /src/octopus_name_resolver.erl: -------------------------------------------------------------------------------- 1 | -module(octopus_name_resolver). 2 | 3 | -export([get/2]). 4 | 5 | -define(BIN(Atom), (atom_to_binary(Atom, utf8))/binary). 6 | -define(ERR(Format, Args), error_logger:error_msg(Format, Args)). 7 | 8 | get(PoolId, Server) -> 9 | try 10 | binary_to_atom(<>, utf8) 11 | catch 12 | _:_ -> 13 | ?ERR("octopus_name_resolver error when processing PoolId:~p and Server: ~p", [PoolId, Server]), 14 | Server 15 | end. 16 | -------------------------------------------------------------------------------- /src/octopus_pool_sup.erl: -------------------------------------------------------------------------------- 1 | -module(octopus_pool_sup). 2 | -behaviour(supervisor). 3 | 4 | %% API 5 | -export([start_link/1]). 6 | 7 | %% supervisor callbacks 8 | -export([init/1]). 9 | 10 | %% API 11 | -spec start_link(PoolId) -> {ok, Pid} 12 | when 13 | PoolId :: atom(), 14 | Pid :: pid(). 15 | 16 | start_link(PoolId) -> 17 | supervisor:start_link(?MODULE, [PoolId]). 18 | 19 | 20 | %% supervisor callbacks 21 | init([PoolId]) -> 22 | Procs = [ 23 | #{ 24 | id => octopus_pool_workers_sup, 25 | start => {octopus_pool_workers_sup, start_link, [PoolId]}, 26 | restart => transient, 27 | shutdown => infinity, 28 | type => supervisor, 29 | modules => [octopus_pool_workers_sup] 30 | }, 31 | #{ 32 | id => octopus_pool_task_server, 33 | start => {octopus_pool_task_server, start_link, [PoolId]}, 34 | restart => transient, 35 | shutdown => 1000, 36 | type => worker, 37 | modules => [octopus_pool_task_server] 38 | }, 39 | #{ 40 | id => octopus_pool_config_server, 41 | start => {octopus_pool_config_server, start_link, [PoolId]}, 42 | restart => transient, 43 | shutdown => 1000, 44 | type => worker, 45 | modules => [octopus_pool_config_server] 46 | } 47 | ], 48 | {ok, {{one_for_all, 5, 1}, Procs}}. 49 | -------------------------------------------------------------------------------- /src/octopus_sup.erl: -------------------------------------------------------------------------------- 1 | -module(octopus_sup). 2 | -behaviour(supervisor). 3 | 4 | %% API 5 | -export([start_link/0]). 6 | -export([start_pool/1, stop_pool/1]). 7 | 8 | %% supervisor callbacks 9 | -export([init/1]). 10 | 11 | %% API 12 | -spec start_link() -> {ok, Pid} 13 | when 14 | Pid :: pid(). 15 | 16 | start_link() -> 17 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 18 | 19 | 20 | -spec start_pool(PoolId) -> Result 21 | when 22 | PoolId :: atom(), 23 | Result :: supervisor:startchild_ret(). 24 | 25 | start_pool(PoolId) -> 26 | Spec = #{ 27 | id => PoolId, 28 | start => {octopus_pool_sup, start_link, [PoolId]}, 29 | restart => transient, 30 | shutdown => infinity, 31 | type => supervisor, 32 | modules => [octopus_pool_sup] 33 | }, 34 | supervisor:start_child(?MODULE, Spec). 35 | 36 | 37 | -spec stop_pool(PoolId) -> ok 38 | when 39 | PoolId :: atom(). 40 | 41 | stop_pool(PoolId) -> 42 | _ = supervisor:terminate_child(?MODULE, PoolId), 43 | _ = supervisor:delete_child(?MODULE, PoolId), 44 | ok. 45 | 46 | 47 | %% supervisor callbacks 48 | init([]) -> 49 | {ok, {{one_for_one, 1, 5}, []}}. 50 | -------------------------------------------------------------------------------- /src/pool/octopus_pool_config_server.erl: -------------------------------------------------------------------------------- 1 | -module(octopus_pool_config_server). 2 | -behaviour(gen_server). 3 | 4 | %% API 5 | -export([start_link/1]). 6 | -export([config_change/2]). 7 | 8 | %% gen_server callbacks 9 | -export([init/1, terminate/2]). 10 | -export([handle_call/3, handle_cast/2, handle_info/2]). 11 | -export([code_change/3]). 12 | 13 | -record(state, { 14 | pool_id, 15 | pool_opts = [], 16 | worker_args = [] 17 | }). 18 | 19 | %% API 20 | -spec start_link(PoolId) -> {ok, Pid} 21 | when 22 | PoolId :: atom(), 23 | Pid :: pid(). 24 | 25 | start_link(PoolId) -> 26 | ServerName = octopus_name_resolver:get(PoolId, ?MODULE), 27 | gen_server:start_link({local, ServerName}, ?MODULE, [PoolId], []). 28 | 29 | 30 | -spec config_change(PoolId, Opts) -> ok 31 | when 32 | PoolId :: atom(), 33 | Opts :: proplists:proplist(). 34 | 35 | config_change(PoolId, Opts) -> 36 | ServerName = octopus_name_resolver:get(PoolId, ?MODULE), 37 | gen_server:cast(ServerName, {config_change, Opts}). 38 | 39 | %% gen_server callbacks 40 | init([PoolId]) -> 41 | State = case octopus:get_pool_config(PoolId) of 42 | {PoolId, PoolOpts, WorkerArgs} -> 43 | ok = config_change(PoolId, [], [], PoolOpts, WorkerArgs), 44 | #state{ 45 | pool_id = PoolId, 46 | pool_opts = PoolOpts, 47 | worker_args = WorkerArgs 48 | }; 49 | _ -> 50 | #state{pool_id = PoolId} 51 | end, 52 | {ok, State}. 53 | 54 | handle_call(_Request, _From, State) -> 55 | {reply, ignored, State}. 56 | 57 | handle_cast({config_change, _Opts}, State = #state{pool_id = PoolId, 58 | pool_opts = OldPoolOpts, worker_args = OldWorkerArgs}) -> 59 | State2 = case octopus:get_pool_config(PoolId) of 60 | {PoolId, NewPoolOpts, NewWorkerArgs} -> 61 | ok = config_change(PoolId, OldPoolOpts, OldWorkerArgs, 62 | NewPoolOpts, NewWorkerArgs), 63 | State#state{pool_opts = NewPoolOpts, worker_args = NewWorkerArgs}; 64 | _ -> 65 | State 66 | end, 67 | {noreply, State2}; 68 | handle_cast(_Msg, State) -> 69 | {noreply, State}. 70 | 71 | handle_info(_Info, State) -> 72 | {noreply, State}. 73 | 74 | terminate(_Reason, _State) -> 75 | ok. 76 | 77 | code_change(_OldVsn, State, _Extra) -> 78 | {ok, State}. 79 | 80 | %% internal 81 | config_change(PoolId, OldPoolOpts, OldWorkerArgs, NewPoolOpts, NewWorkerArgs) -> 82 | OldPoolSize = proplists:get_value(pool_size, OldPoolOpts, 0), 83 | NewPoolSize = proplists:get_value(pool_size, NewPoolOpts, 0), 84 | %% PoolSizeChange 85 | ok = pool_size_change(PoolId, OldPoolSize, NewPoolSize), 86 | %% WorkerConfigChange 87 | OldWorkerModule = proplists:get_value(worker, OldPoolOpts), 88 | NewWorkerModule = proplists:get_value(worker, NewPoolOpts), 89 | WorkerModuleChanged = NewWorkerModule =/= OldWorkerModule, 90 | WorkerArgsChanged = NewWorkerArgs =/= OldWorkerArgs, 91 | WorkerConfigChanged = WorkerModuleChanged orelse WorkerArgsChanged, 92 | _ = [octopus_pool_workers_sup:restart_worker(PoolId, WorkerId) 93 | || WorkerId <- lists:seq(1, OldPoolSize), WorkerConfigChanged], 94 | ok. 95 | 96 | pool_size_change(PoolId, OldSize, NewSize) when OldSize =< NewSize -> 97 | %% when OldSize less or equal NewSize then try to start new workers 98 | [begin 99 | ok = octopus_pool_workers_sup:start_worker(PoolId, WorkerId) 100 | end || WorkerId <- lists:seq(OldSize + 1, NewSize)], 101 | ok; 102 | pool_size_change(PoolId, OldSize, NewSize) -> 103 | %% when OldSize bigger NewSize then stop unnecessary workers 104 | [begin 105 | ok = octopus_pool_workers_sup:stop_worker(PoolId, WorkerId) 106 | end || WorkerId <- lists:seq(NewSize + 1, OldSize)], 107 | ok. 108 | -------------------------------------------------------------------------------- /src/pool/octopus_pool_task_server.erl: -------------------------------------------------------------------------------- 1 | -module(octopus_pool_task_server). 2 | -behaviour(gen_server). 3 | 4 | %% API 5 | -export([start_link/1]). 6 | -export([worker_start/2]). 7 | -export([worker_init/2]). 8 | -export([worker_ready/2]). 9 | -export([worker_lockout/2]). 10 | -export([worker_lockin/1]). 11 | -export([pool_info/1]). 12 | 13 | %% gen_server callbacks 14 | -export([init/1, terminate/2]). 15 | -export([handle_call/3, handle_cast/2, handle_info/2]). 16 | -export([code_change/3]). 17 | 18 | -record(state, { 19 | pool_id, 20 | init_type = sync, 21 | queue 22 | }). 23 | 24 | -define(DEF_INIT_TYPE, sync). 25 | 26 | %% API 27 | -spec start_link(PoolId) -> {ok, Pid} 28 | when 29 | PoolId :: atom(), 30 | Pid :: pid(). 31 | 32 | start_link(PoolId) -> 33 | ServerName = octopus_name_resolver:get(PoolId, ?MODULE), 34 | gen_server:start_link({local, ServerName}, ?MODULE, [PoolId], []). 35 | 36 | 37 | -spec worker_start(PoolId, WorkerId) -> 38 | {pk, Pid} | 39 | {ok, Pid, Extra} | 40 | {error, Reason} | 41 | term() 42 | when 43 | PoolId :: atom(), 44 | WorkerId :: term(), 45 | Extra :: term(), 46 | Reason :: term(). 47 | 48 | 49 | worker_start(PoolId, WorkerId) -> 50 | ServerName = octopus_name_resolver:get(PoolId, ?MODULE), 51 | {WorkerModule, WorkerArgs} = get_worker_config(PoolId, WorkerId), 52 | case apply(WorkerModule, start_link, WorkerArgs) of 53 | {ok, Pid} when is_pid(Pid) -> 54 | _ = ServerName ! {start, WorkerId, Pid}, 55 | {ok, Pid}; 56 | {ok, Pid, Extra} when is_pid(Pid) -> 57 | _ = ServerName ! {start, WorkerId, Pid}, 58 | {ok, Pid, Extra}; 59 | Other -> 60 | Other 61 | end. 62 | 63 | -spec worker_init(PoolId, WorkerId) -> ok 64 | when 65 | PoolId :: atom(), 66 | WorkerId :: term(). 67 | 68 | worker_init(PoolId, WorkerId) -> 69 | ServerName = octopus_name_resolver:get(PoolId, ?MODULE), 70 | _ = ServerName ! {init, WorkerId}, 71 | ok. 72 | 73 | -spec worker_ready(PoolId, WorkerId) -> ok 74 | when 75 | PoolId :: atom(), 76 | WorkerId :: term(). 77 | 78 | worker_ready(PoolId, WorkerId) -> 79 | ServerName = octopus_name_resolver:get(PoolId, ?MODULE), 80 | _ = ServerName ! {ready, WorkerId}, 81 | ok. 82 | 83 | -spec worker_lockout(PoolId, Timeout) -> {ok, Pid} | {error, Reason} 84 | when 85 | PoolId :: atom(), 86 | Timeout :: timeout(), 87 | Pid :: pid(), 88 | Reason :: term(). 89 | 90 | worker_lockout(PoolId, Timeout) -> 91 | ServerName = octopus_name_resolver:get(PoolId, ?MODULE), 92 | try 93 | gen_server:call(ServerName, worker_lockout, Timeout) 94 | catch 95 | _:_ -> 96 | gen_server:cast(ServerName, {remove_from_queue, self()}), 97 | {error, timeout} 98 | end. 99 | 100 | 101 | -spec worker_lockin(PoolId) -> ok 102 | when 103 | PoolId :: atom(). 104 | 105 | worker_lockin(PoolId) -> 106 | ServerName = octopus_name_resolver:get(PoolId, ?MODULE), 107 | gen_server:cast(ServerName, {worker_lockin, self()}). 108 | 109 | -spec pool_info(PoolId) -> #{Item => non_neg_integer()} 110 | when 111 | PoolId :: atom(), 112 | Item :: init | ready | busy. 113 | 114 | pool_info(PoolId) -> 115 | ServerName = octopus_name_resolver:get(PoolId, ?MODULE), 116 | gen_server:call(ServerName, pool_info). 117 | 118 | %% gen_server callbacks 119 | init([PoolId]) -> 120 | _ = erlang:process_flag(trap_exit, true), 121 | {PoolId, PoolOpts, _WorkerArgs} = octopus:get_pool_config(PoolId), 122 | InitType = proplists:get_value(init_type, PoolOpts, ?DEF_INIT_TYPE), 123 | State = #state{ 124 | pool_id = PoolId, 125 | init_type = InitType, 126 | queue = queue:new() 127 | }, 128 | {ok, State}. 129 | 130 | handle_call(worker_lockout, From, #state{pool_id = PoolId, queue = Queue} = State) -> 131 | Ready = lookup({PoolId, ready}), 132 | case Ready of 133 | [{WorkerId, Pid}|Ready2] -> 134 | ok = lockout(PoolId, WorkerId, Pid, From), 135 | ok = insert({PoolId, ready}, Ready2), 136 | {noreply, State}; 137 | [] -> 138 | {noreply, State#state{queue = queue:in(From, Queue)}} 139 | end; 140 | handle_call(pool_info, _From, #state{pool_id = PoolId} = State) -> 141 | Init = lookup({PoolId, init}), 142 | Ready = lookup({PoolId, ready}), 143 | Busy = lookup({PoolId, busy}), 144 | PoolInfo = #{ 145 | init => length(Init), 146 | ready => length(Ready), 147 | busy => length(Busy) 148 | }, 149 | {reply, PoolInfo, State}; 150 | handle_call(_Request, _From, State) -> 151 | {reply, ignored, State}. 152 | 153 | handle_cast({worker_lockin, FromPid}, #state{pool_id = PoolId, queue = Queue} = State) -> 154 | Busy = lookup({PoolId, busy}), 155 | Queue2 = case lists:keytake(FromPid, 3, Busy) of 156 | {value, {WorkerId, Pid, FromPid, Monitor}, Busy2} -> 157 | true = erlang:demonitor(Monitor), 158 | case queue:out(Queue) of 159 | {{value, From}, NewQueue} -> 160 | ok = lockout(PoolId, WorkerId, Pid, From), 161 | NewQueue; 162 | {empty, _Queue2} -> 163 | Ready = lookup({PoolId, ready}), 164 | Ready2 = lists:keystore(WorkerId, 1, Ready, {WorkerId, Pid}), 165 | ok = insert({PoolId, ready}, Ready2), 166 | ok = insert({PoolId, busy}, Busy2), 167 | Queue 168 | end; 169 | false -> 170 | Queue 171 | end, 172 | {noreply, State#state{queue = Queue2}}; 173 | handle_cast({remove_from_queue, FromPid}, #state{queue = Queue} = State) -> 174 | Queue2 = queue:filter(fun({Pid, _Tag}) -> Pid =/= FromPid end, Queue), 175 | _ = case Queue =:= Queue2 of 176 | true -> 177 | gen_server:cast(self(), {worker_lockin, FromPid}); 178 | false -> 179 | ignore 180 | end, 181 | {noreply, State#state{queue = Queue2}}; 182 | handle_cast(_Msg, State) -> 183 | {noreply, State}. 184 | 185 | handle_info({start, WorkerId, Pid}, #state{pool_id = PoolId, 186 | init_type = InitType} = State) -> 187 | _ = erlang:monitor(process, Pid), 188 | Init = lookup({PoolId, init}), 189 | Ready = lookup({PoolId, ready}), 190 | case InitType of 191 | sync -> 192 | Ready2 = lists:keystore(WorkerId, 1, Ready, {WorkerId, Pid}), 193 | _ = insert({PoolId, ready}, Ready2); 194 | async -> 195 | Ready2 = lists:keydelete(WorkerId, 1, Ready), 196 | Init2 = lists:keystore(WorkerId, 1, Init, {WorkerId, Pid}), 197 | ok = insert({PoolId, init}, Init2), 198 | ok = insert({PoolId, ready}, Ready2) 199 | end, 200 | {noreply, State}; 201 | handle_info({init, WorkerId}, #state{pool_id = PoolId} = State) -> 202 | Init = lookup({PoolId, init}), 203 | Ready = lookup({PoolId, ready}), 204 | case lists:keytake(WorkerId, 1, Ready) of 205 | {value, Tuple, Ready2} -> 206 | Init2 = lists:keystore(WorkerId, 1, Init, Tuple), 207 | ok = insert({PoolId, init}, Init2), 208 | ok = insert({PoolId, ready}, Ready2); 209 | false -> 210 | ok 211 | end, 212 | {noreply, State}; 213 | handle_info({ready, WorkerId}, #state{pool_id = PoolId} = State) -> 214 | Init = lookup({PoolId, init}), 215 | Ready = lookup({PoolId, ready}), 216 | case lists:keytake(WorkerId, 1, Init) of 217 | {value, Tuple, Init2} -> 218 | Ready2 = lists:keystore(WorkerId, 1, Ready, Tuple), 219 | ok = insert({PoolId, init}, Init2), 220 | ok = insert({PoolId, ready}, Ready2); 221 | false -> 222 | ok 223 | end, 224 | {noreply, State}; 225 | handle_info({'DOWN', MonitorRef, process, Pid, _Info}, 226 | #state{pool_id = PoolId} = State) -> 227 | Ready = lookup({PoolId, ready}), 228 | Busy = lookup({PoolId, busy}), 229 | case lists:keytake(Pid, 3, Busy) of 230 | {value, Tuple, Busy2} -> 231 | {WorkerId, WorkerPid, Pid, MonitorRef} = Tuple, 232 | Ready2 = lists:keystore(WorkerId, 1, Ready, {WorkerId, WorkerPid}), 233 | ok = insert({PoolId, ready}, Ready2), 234 | ok = insert({PoolId, busy}, Busy2); 235 | false -> 236 | Init = lookup({PoolId, init}), 237 | Init2 = lists:keydelete(Pid, 2, Init), 238 | Ready2 = lists:keydelete(Pid, 2, Ready), 239 | Busy2 = lists:keydelete(Pid, 2, Busy), 240 | ok = insert({PoolId, init}, Init2), 241 | ok = insert({PoolId, ready}, Ready2), 242 | ok = insert({PoolId, busy}, Busy2) 243 | end, 244 | {noreply, State}; 245 | handle_info(_Info, State) -> 246 | {noreply, State}. 247 | 248 | terminate(_Reason, _State) -> 249 | ok. 250 | 251 | code_change(_OldVsn, State, _Extra) -> 252 | {ok, State}. 253 | 254 | %% internal 255 | lockout(PoolId, WorkerId, Pid, {FromPid, _} = From) -> 256 | Busy = lookup({PoolId, busy}), 257 | Monitor = erlang:monitor(process, FromPid), 258 | Busy2 = lists:keystore(WorkerId, 1, Busy, {WorkerId, Pid, FromPid, Monitor}), 259 | ok = insert({PoolId, busy}, Busy2), 260 | _ = gen_server:reply(From, {ok, Pid}), 261 | ok. 262 | 263 | get_worker_config(PoolId, WorkerId) -> 264 | {PoolId, PoolOpts, WorkerArgs} = octopus:get_pool_config(PoolId), 265 | InitType = proplists:get_value(init_type, PoolOpts, ?DEF_INIT_TYPE), 266 | WorkerModule = proplists:get_value(worker, PoolOpts), 267 | case InitType of 268 | sync -> 269 | {WorkerModule, WorkerArgs}; 270 | async -> 271 | [WorkerOpts] = WorkerArgs, 272 | WorkerOpts2 = [ 273 | {pool_id, PoolId}, 274 | {worker_id, WorkerId}, 275 | {init_type, InitType}, 276 | {init_callback, {?MODULE, worker_init, [PoolId, WorkerId]}}, 277 | {ready_callback, {?MODULE, worker_ready, [PoolId, WorkerId]}} 278 | |WorkerOpts], 279 | {WorkerModule, [WorkerOpts2]} 280 | end. 281 | 282 | insert(Key, Value) -> 283 | _ = put(Key, Value), 284 | ok. 285 | 286 | lookup(Key) -> 287 | case get(Key) of 288 | undefined -> []; 289 | Value -> Value 290 | end. 291 | -------------------------------------------------------------------------------- /src/pool/octopus_pool_workers_sup.erl: -------------------------------------------------------------------------------- 1 | -module(octopus_pool_workers_sup). 2 | -behaviour(supervisor). 3 | 4 | %% API 5 | -export([start_link/1]). 6 | -export([start_worker/2]). 7 | -export([stop_worker/2]). 8 | -export([restart_worker/2]). 9 | 10 | %% supervisor callbacks 11 | -export([init/1]). 12 | 13 | %% API 14 | -spec start_link(PoolId) -> {ok, Pid} 15 | when 16 | PoolId :: atom(), 17 | Pid :: pid(). 18 | 19 | start_link(PoolId) -> 20 | SupName = octopus_name_resolver:get(PoolId, ?MODULE), 21 | supervisor:start_link({local, SupName}, ?MODULE, []). 22 | 23 | 24 | -spec start_worker(PoolId, WorkerId) -> ok 25 | when 26 | PoolId :: atom(), 27 | WorkerId :: term(). 28 | 29 | start_worker(PoolId, WorkerId) -> 30 | SupName = octopus_name_resolver:get(PoolId, ?MODULE), 31 | Spec = {WorkerId, 32 | {octopus_pool_task_server, worker_start, [PoolId, WorkerId]}, 33 | transient, 1000, worker, [octopus_pool_task_server]}, 34 | {ok, _} = supervisor:start_child(SupName, Spec), 35 | ok. 36 | 37 | 38 | %% TODO soft stop 39 | -spec stop_worker(PoolId, WorkerId) -> ok 40 | when 41 | PoolId :: atom(), 42 | WorkerId :: term(). 43 | 44 | stop_worker(PoolId, WorkerId) -> 45 | SupName = octopus_name_resolver:get(PoolId, ?MODULE), 46 | _ = supervisor:terminate_child(SupName, WorkerId), 47 | _ = supervisor:delete_child(SupName, WorkerId), 48 | ok. 49 | 50 | 51 | %% TODO soft restart 52 | -spec restart_worker(PoolId, WorkerId) -> ok 53 | when 54 | PoolId :: atom(), 55 | WorkerId :: term(). 56 | 57 | restart_worker(PoolId, WorkerId) -> 58 | ok = stop_worker(PoolId, WorkerId), 59 | ok = start_worker(PoolId, WorkerId), 60 | ok. 61 | 62 | 63 | %% supervisor callbacks 64 | -spec init([PoolId]) -> {ok, {{Strategy, MaxR, MaxT}, [ChildSpec]}} 65 | when 66 | PoolId :: atom(), 67 | Strategy :: supervisor:strategy(), 68 | MaxR :: non_neg_integer(), 69 | MaxT :: pos_integer(), 70 | ChildSpec :: supervisor:child_spec(). 71 | 72 | init([]) -> 73 | Procs = [], 74 | {ok, {{one_for_one, 100, 1}, Procs}}. 75 | --------------------------------------------------------------------------------