├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── director.jpg ├── include └── internal │ ├── director_child.hrl │ └── director_defaults.hrl ├── rebar.config ├── rebar.config.script ├── rebar3 ├── src ├── director.app.src ├── director.erl ├── director_child.erl ├── director_table.erl ├── director_table_ets.erl ├── director_table_list.erl ├── director_table_map.erl ├── director_table_mnesia.erl └── director_utils.erl └── test ├── director_SUITE.erl ├── director_SUITE_data └── src │ ├── director_callback.erl │ └── director_child_.erl ├── director_table_.erl ├── director_table_ets_SUITE.erl ├── director_table_list_SUITE.erl ├── director_table_map_SUITE.erl ├── director_table_mnesia_SUITE.erl ├── director_test_utils.erl └── director_utils_SUITE.erl /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 21.0 3 | 4 | 5 | before_script: 6 | - kerl list installations 7 | 8 | script: 9 | - make test 10 | 11 | after_success: 12 | - make cover 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | I love pull requests from everyone. But it's good to explain your idea in issues before. 2 | 3 | # Contributing 4 | Fork, then clone the repo: 5 | ```sh 6 | ~ $ git clone https://github.com/YOUR-USERNAME/director.git && cd director 7 | ``` 8 | 9 | Run tests before any changes and Make sure the tests pass: 10 | ```sh 11 | ~/director $ make test 12 | /projects/director/rebar3 ct && /projects/director/rebar3 dialyzer 13 | ===> Verifying dependencies... 14 | ===> Compiling director 15 | ===> Running Common Test suites... 16 | %%% director_SUITE ==> 1: OK 17 | %%% director_SUITE ==> 2: OK 18 | %%% director_SUITE ==> 3: OK 19 | %%% director_SUITE ==> 4: OK 20 | %%% director_SUITE ==> 5: OK 21 | %%% director_SUITE ==> 6: OK 22 | %%% director_SUITE ==> 7: OK 23 | %%% director_SUITE ==> 8: OK 24 | %%% director_SUITE ==> 9: OK 25 | %%% director_SUITE ==> 10: OK 26 | %%% director_table_ets_SUITE ==> 1: OK 27 | %%% director_table_ets_SUITE ==> 2: OK 28 | %%% director_table_ets_SUITE ==> 3: OK 29 | %%% director_table_ets_SUITE ==> 4: OK 30 | %%% director_table_ets_SUITE ==> 5: OK 31 | %%% director_table_ets_SUITE ==> 6: OK 32 | %%% director_table_list_SUITE ==> 1: OK 33 | %%% director_table_list_SUITE ==> 2: OK 34 | %%% director_table_list_SUITE ==> 3: OK 35 | %%% director_table_list_SUITE ==> 4: OK 36 | %%% director_table_list_SUITE ==> 5: OK 37 | %%% director_table_list_SUITE ==> 6: OK 38 | %%% director_table_mnesia_SUITE ==> 1: OK 39 | %%% director_table_mnesia_SUITE ==> 2: OK 40 | %%% director_table_mnesia_SUITE ==> 3: OK 41 | %%% director_table_mnesia_SUITE ==> 4: OK 42 | %%% director_table_mnesia_SUITE ==> 5: OK 43 | %%% director_table_mnesia_SUITE ==> 6: OK 44 | All 28 tests passed. 45 | ===> Verifying dependencies... 46 | ===> Compiling director 47 | ===> Dialyzer starting, this may take a while... 48 | ===> Updating plt... 49 | ===> Resolving files... 50 | ===> Checking 163 files in "/projects/director/_build/default/director_19.3_plt"... 51 | ===> Doing success typing analysis... 52 | ===> Resolving files... 53 | ===> Analyzing 7 files with "/projects/director/_build/default/director_19.3_plt"... 54 | ~/director $ 55 | ``` 56 | Make your changes and run tests. Make sure the tests pass again. Add tests for your changes and again **Make sure the tests pass**. Push to your fork and [submit a pull request](https://github.com/pouriya-jahanbakhsh/director/compare/). At this point you're waiting on me. 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | 4 | Director is available for use under the following license, commonly known as the 3-clause (or 5 | "modified") BSD license: 6 | 7 | Copyright (c) 2018-2019, Pouriya Jahanbakhsh 8 | (pouriya.jahanbakhsh@gmail.com) All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without modification, are permitted 11 | provided that the following conditions are met: 12 | 13 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions 14 | and the following disclaimer. 15 | 16 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of 17 | conditions and the following disclaimer in the documentation and/or other materials provided 18 | with the distribution. 19 | 20 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to 21 | endorse or promote products derived from this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 29 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 30 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR = $(CURDIR)/rebar3 2 | BUILD_PATH := _build 3 | LIB_PATH := $(BUILD_PATH)/default/lib 4 | 5 | .PHONY: all compile compile-examples shell docs test clean distclean 6 | 7 | all: compile 8 | 9 | compile: compile-examples 10 | @ $(REBAR) compile 11 | 12 | compile-examples: 13 | @ echo Todo: after adding examples, compile them here 14 | 15 | shell: compile 16 | @ erl -pa $(LIB_PATH)/director/ebin 17 | 18 | docs: 19 | @ $(REBAR) as doc edoc 20 | 21 | test: compile-examples 22 | $(REBAR) ct && $(REBAR) dialyzer 23 | 24 | cover: 25 | $(REBAR) cover && $(REBAR) coveralls send 26 | 27 | clean: 28 | @ $(REBAR) clean 29 | 30 | distclean: clean 31 | @ rm -rf $(BUILD_PATH) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![directror travis test status](https://travis-ci.org/Pouriya-Jahanbakhsh/director.png?branch=master) [![Coverage Status](https://coveralls.io/repos/github/Pouriya-Jahanbakhsh/director/badge.svg?branch=master)](https://coveralls.io/github/Pouriya-Jahanbakhsh/director?branch=master) 2 | 3 | # Welcome 4 | 5 | ![directror](director.jpg) 6 | 7 | **Director** is a production-ready **supervisor** and **manager** for Erlang/Elixir processes that focuses on speed, performance and flexibility. Don't worry about replacing **Director** with OTP/Supervisor Because a **Director** process is responsive for all API functions of OTP/Supervisor module and has its own useful API functions too. This is more flexible than OTP/supervisor. Since **Director** calls callback-function to dealing with process crash, By changing code you can change strategy! To seeing all advantages just read this readme file. 8 | 9 | ## What is a supervisor? (for newbies) 10 | According to the Erlang's manual:   11 | >A supervisor is a process that supervises other processes called child processes. A child process can either be another supervisor or a worker process. Supervisors are used to build a hierarchical process structure called a supervision tree, a nice way to structure a fault-tolerant application.   12 | >In Erlang we tell supervisors to start other processes. Every child process has its own options called childspec. 13 | 14 | ## Features 15 | * Useful API functions:   16 | * `director:get_pid(DirectorRef, ChildId)` gives pid of child if child is alive.   17 | * `director:get_pids(DirectorRef)` gives pids of alive children.   18 | * `director:terminate_and_delete_child(DirectorRef, ChildId)` terminates and deletes a child in one request.   19 | * `director:become_supervisor(DirectorRef, Pid, ChildSpec)` makes **Director** supervisor of an alive process.   20 | * `director:get_restart_count(DirectorRef, ChildId)` gives restart count of child (useful for debug). 21 | * All functions not listed here. 22 | * **Director** is an Erlang behaviour and every callback-module of this behaviour should has following callback-functions (See "How to use?" section for detailed explanation): 23 | * `init/1`: For initialization. Here you can define children database type, debug mode and childspecs of some children that you want to start them in initialize state. 24 | * `handle_start/4`: Will be called after starting each child process. 25 | * `handle_exit/5`: Will be called after crashing a child process. This callback-function should tell **Director** how to deal with process crash. Restart child? Restart it after time interval? Delete child from children? Do nothing? Terminate yourself? 26 | * `handle_terminate/5`: Will be called when **Director** terminates a child process. 27 | * `terminate/2`: Will be called for termination of **Director** itself. 28 | * Use different databases for keeping children. By default **Director** uses an Erlang list. It has three other modes for keeping children In a map or ETS or Mnesia table. 29 | * A number of **Director** processes can use one ETS table on same node (sharing ETS table). 30 | * A number of **Director** processes can use one Mnesia table on cluster of nodes (sharing Mnesia table). 31 | * By sharing table **Director**s can start, restart, terminate, etc a number of children simultaneously. 32 | * By using ETS or Mnesia you can read children info directly from table using API functions of `director_table_ets` and `director_table_mnesia` modules instead of getting them from **Director** process. For example `director_table_ets:get_pids(Tab)` or `director_table_mnesia:count_children(Tab)`. 33 | * You can define your own database for keeping children by implementing `director_table` behaviour. Also some test cases are ready for testing your module. 34 | * Understandable debug output for every operation. 35 | 36 | All features not listed here. For more info see Guide and examples. For contributing see [`CONTRIBUTING.md`](CONTRIBUTING.md) file. 37 | 38 | 39 | ## How to use? 40 | Since **Director** is an Erlang behaviour; So before explaining its workflow, I'll explain that "What a behaviour is?" for newbies. 41 | > In Erlang, a behaviour is a design pattern implemented in a module/library. It provides functionality in a fashion similar to inheritance in object-oriented programming. A number of functions called callback-functions must be defined for each behavior to work in a module called callback-module. 42 | 43 | When you want to start a **Director** process, you should specify your callback-module which has **Director**'s callback-functions defined and exported. In run-time, **Director** process calls those callback-functions in different states. I'll explain those callback-functions in below. 44 | ### `init/1` 45 | For starting a linked **Director** process, you should call one of the API functions: 46 | ```erlang 47 | director:start_link(Module::module(), InitArg::any()). 48 | director:start_link(Module::module(), InitArg::any(), Opts::director:start_options()). 49 | director:start_link(RegisterName::director:register_name(), Module::module(), InitArg::any()). 50 | director:start_link(RegisterName::director:register_name(), Module::module(), InitArg::any(), Opts::director:start_options()). 51 | ``` 52 | For starting an stand-alone **Director** process, you should call one of the API functions: 53 | ```erlang 54 | director:start(Module::module(), InitArg::any()). 55 | director:start(Module::module(), InitArg::any(), Opts::director:start_options()). 56 | director:start(RegisterName::director:register_name(), Module::module(), InitArg::any()). 57 | director:start(RegisterName::director:register_name(), Module::module(), InitArg::any(), Opts::director:start_options()). 58 | ``` 59 | After calling, **Director** process calls `Module:init(InitArg)`, possible return values of `init/1` are: 60 | ```erlang 61 | -type init_return() :: 'ok' % will be {ok, undefined, [], director:default_childspec(), []} 62 | | {'ok', director:state()} 63 | | {'ok', director:state(), [director:childspec()]|[]} 64 | | {'ok', director:state(), [director:childspec()]|[], director:default_childspec()} 65 | | {'ok', director:state(), [director:childspec()]|[], director:start_options()} 66 | | {'ok', director:state(), [director:childspec()]|[], director:default_childspec(), director:start_options()} 67 | | 'ignore' 68 | | {'stop', Reason::any()}. 69 | ``` 70 | #### Childspec 71 | A childspec is an Erlang map containing some mandatory and optional keys that belongs to one child process. I will explain these keys below. 72 | * **id:** This key should be unique for child and can be any erlang term. We will understand usage of this key later. This key is mandatory. 73 | * **start:** This key should be an `mfa()` (module, function and its arguments) and is mandatory. **Director** calls `erlang:apply(Mod, Func, Args)` using value of this key for starting child. Possible values of this key are: 74 | * `mfa()`. 75 | * `{module(), Func::atom()}`. Will be `{module(), Func::atom(), []}`. 76 | * `module()`. Will be `{module(), start_link, []}`. 77 | * **state:** This key is optional and if not defined, its default value will be atom `undefined`. This is state data of child process inside **Director** process. We will understand usage of this key later. 78 | * **type:** A child process can be a worker or another supervisor. This key is optional and default value is atom `worker` and possible values are: 79 | * `supervisor`. 80 | * `sup` (Short for `supervisor`). 81 | * `worker`. 82 | * `w` (Short for `worker`). 83 | * `s` (Short for `supervisor`). 84 | * **terminate_timeout:** When one **Director** process is terminating or when you call `director:terminate_child/2` or `director:terminate_and_delete_child/2` for a child, It terminates its alive children or that child using `erlang:exit(ChildPid, Reason)`. Possible values are atom `infinity` and `0` and positive integers. When value is `0`, **Director** calls `erlang:exit(ChildPid, kill)` otherwise when this value is `X`, it calls `erlang:exit(ChildPid, shutdown)` and waits `X` milli-second for termination. If child did not terminate after `X`, It calls `erlang:exit(ChildPid, kill)`. This key is optional and if key `type` defined to `worker` its default value will be `5000` otherwise `infinity`. 85 | * **delete:** When **Director** process is in termination state, It deletes all children from its database (from list, ETS, Mnesia, etc). When you have some **Director** processes and they are using one ETS, Mnesia, etc table for keeping their children (Using shared table), If one **Director** stops, You can restart its children from another **Director** which has access to that table for every child that has this key `=> false` in its childspec. Default is `true`. 86 | * **modules:** This key is optional and its default value will be 1st element of its start `mfa`. Possible values are: 87 | * `[module()]`. 88 | * `dynamic` (Where name of callback module will determine in future. for example in `gen_event` process). 89 | * **append:** What is usage of `director:default_childspec()` in above? If you want to have a number of children with similar childspec options, you can define a default childspec in return value of `init/1` and all children with `append => true` in their childspec will combine with that default childspec. Default childspec is like normal childspec except that this has not `id` and `append` keys. Default default childspec is: 90 | ```erlang 91 | #{terminate_timeout => 0, modules => [], type => worker, delete => true, state => undefined} 92 | ``` 93 | 94 | #### Start options 95 | You can define start options in two places, in calling `director:start/2-4` or `director:start_link/2-4` and in return value of `init/1`. Start options is an Erlang proplist with following items: 96 | * **db:** Should be another erlang proplist with following items: 97 | * **table:** Default is `list`. Can be one of `list`, `ets` or `mnesia`. If you defined new table `x`,name your module `director_table_x` and define value of `table` to `x`. 98 | * **init_arg:** If you are using `list` for keeping children, Its value not matters. for `mnesia` and `ets` must be table name. For new database callback module if this value not defined, atom `undefined` will be given to callback-function `director_table_x:create/1` and if defined, argument `{value, Value}` will be given. 99 | * **delete:** Should be one of `true` or `false`. If defined to `true`. In termination of **Director** itself, It deletes the table. Default is `true`. 100 | * **debug:** Standard OTP/sys debug options: 101 | ```erlang 102 | -type dbg_opts() :: [dbg_opt()] | []. 103 | -type dbg_opt() :: {'trace', 'true'} 104 | | {'log', 105 | {N :: non_neg_integer(), 106 | [{Event :: system_event(), 107 | FuncState :: _, 108 | FormFunc :: format_fun()}]}} 109 | | {'statistics', {file:date_time(), 110 | {'reductions', non_neg_integer()}, 111 | MessagesIn :: non_neg_integer(), 112 | MessagesOut :: non_neg_integer()}} 113 | | {'log_to_file', file:io_device()} 114 | | {Func :: dbg_fun(), FuncState :: term()}. 115 | ``` 116 | Default is nothing or `[]`. For more info see OTP `sys` module. 117 | * **spawn_opt:**: List of spawn options. For more info see OTP type `proc_lib:spawn_option()`. You cannot use this option in return value of `init/1`. 118 | * **timeout:** How much time process needs for initialization? You cannot use this option in return value of `init/1`. 119 | 120 | #### `init/1` examples 121 | ```erlang 122 | -module(director_test). 123 | ... 124 | -export([init/1]). % Do not forget to export it 125 | ... 126 | -record(state, {}). %% State record for Director itself 127 | -record(chstate, {}). %% State record for Director children 128 | 129 | init(MyInitArg) -> 130 | Child_1 = #{id => child_1 131 | ,start => {child_1_module, start_link, [MyInitArg]} 132 | ,state => #chstate{} 133 | ,terminate_timeout => 1000}, 134 | Child_2 = #{id => child_2 135 | ,start => {child_2_module, start_link, [MyInitArg, arg_2, arg_3]} 136 | ,state => #chstate{}}, 137 | {ok, #state{}, [Child_1, Child2], [{db, [{table, ets}, {init_arg, my_table}]}]}. 138 | %% In above if you want some children with similar childspecs, you can use default childspec: 139 | % Children = [#{id => Id, append => true} || _ <- lists:seq(1, 100)], 140 | % DefChildSpec = #{start => {foo, start, [arg_1, arg_2]}}, 141 | % {ok, #state{}, Children, DefChildSpec}. 142 | %% You will have 100 childspecs with ids 1-100 and start {foo, start, [arg_1, arg_2]} 143 | 144 | %% If you want simple_one_for_one strategy of OTP/Supervisor: 145 | % Children = [#{id => Id, append => true, start => {foo, start, [arg_3]}} || _ <- lists:seq(1, 100)], 146 | % DefChildSpec = #{start => {foo, start, [arg_1, arg_2]}}, 147 | % {ok, #state{}, Children, DefChildSpec}. 148 | %% You will have 100 childspecs with ids 1-100 and start {foo, start, [arg_1, arg_2, arg_3]} 149 | ... 150 | ``` 151 | 152 | ### `handle_start/4` 153 | When **Director** starts a child process, It calls: 154 | ```erlang 155 | YourCallbackModule:handle_start(ChildId, ChildState, DirectorState, Metadata) 156 | ``` 157 | In above example when **Director** starts `Child_1`, It calls: 158 | ```erlang 159 | director_test:handle_start(child_1, #chstate{}, #state{}, #{restart_count := 0, pid := PidOfChild_1}) 160 | ``` 161 | This callback-function should yield: 162 | ```erlang 163 | -type handle_start_return() :: {'ok', NewChildState::director:child_state(), NewDirectorState::director:state(), director:callback_return_options()} 164 | | {'stop', director:child_state(), Reason::any(), director:callback_return_options()}. 165 | ``` 166 | Example of `handle_start/4` which just tells **Director** to don't call `error_logger` about starting any child: 167 | ```erlang 168 | handle_start(_, ChState, State, _) -> 169 | {ok, ChState, State, [{log, false}]}. 170 | ``` 171 | 172 | 173 | ### `handle_exit/5` 174 | When a child process crashes, Its **Director** will receive child's exit signal and calls: 175 | ```erlang 176 | YourCallbackModule:handle_exit(ChildId, ChildState, ReasonOfChildTermination, DirectorState, MetaData) 177 | ``` 178 | In above example when `Child_1` exits with reason `oops`, **Director** calls: 179 | ```erlang 180 | director_test:handle_exit(child_1, #chstate{}, oops, #state{}, #{restart_count := 1}) 181 | ``` 182 | This callback-function should yield: 183 | ```erlang 184 | -type handle_exit_return() :: {director:action(), director:child_state(), director:state(), director:callback_return_options()}. 185 | -type action() :: 'restart' 186 | | {'restart', pos_integer()} 187 | | 'delete' 188 | | 'wait' 189 | | 'stop' 190 | | {'stop', Reason::any()}. 191 | ``` 192 | Example of `handle_exit/5` which tells **Director** to restart child after 1000 milli-seconds: 193 | ```erlang 194 | handle_exit(_, ChState, _, State, _) -> 195 | {{restart, 1000}, ChState, State, []}. 196 | ``` 197 | If you define `delete` as action, Child will be removed from children table. If you define `wait` as action, **Director** does nothing and you have to call `director:restart_child(DirectorProc, ChildId)` for restarting child. If you define `stop`, **Director** will terminate itself with error reason of child crash. 198 | What if you define `restart` or `{restart, Int}` and child does not restart? **Director** will restart child again, So calls `handle_exit/5` again which its metadata argument has `restart_count` key plus one. For example in following code **Director** will restart child id `foo` for 5 times, then restarts it after 1000 milli-seconds for 6th time, and finally terminates itself with reason `{max_restart, foo}` for 7th time: 199 | ```erlang 200 | handle_exit(foo, ChState, _Reason, State, #{restart_count := RC}) when RC < 6 -> 201 | {restart, ChState, State, []}; 202 | handle_exit(foo, ChState, _Reason, State, #{restart_count := 6}) -> 203 | {{restart, 1000}, ChState, State, []}; 204 | handle_exit(foo, ChState, _Reason, State, _) -> 205 | {{stop, {max_restart, foo}}, ChState, State, []}. 206 | ``` 207 | 208 | ### `handle_terminate/5` 209 | When you call `director:terminate_child/2-3` or `director:delete_and_terminate_child/2-3`, **Director** terminates child process and calls: 210 | ```erlang 211 | YourCallbackModule:handle_terminate(ChildId, ChildState, ReasonOfChildTermination::shutdown|kill|term(), DirectorState, MetaData) 212 | ``` 213 | Also it calls `handle_terminate/5` when it is in terminate state and is terminating its alive children. 214 | For above example when you call `director:terminate_child(DirectorProc, child_2)`, It calls: 215 | ```erlang 216 | director_test:handle_exit(child_2, #chstate{}, shutdown, #state{}, #{restart_count := 0}) 217 | ``` 218 | This callback-function should yield: 219 | ```erlang 220 | -type handle_terminate_return() :: {'ok', director:child_state(), director:state(), director:callback_return_options()}. 221 | ``` 222 | For example following code tells **Director** don't call `error_logger` for termination of child id `bar` and call it for other children: 223 | ```erlang 224 | handle_terminate(bar, ChildState, _, DirectorState, _) -> 225 | {ok, ChildState, DirectorState, [{log, false}]}; 226 | handle_terminate(_, ChildState, _, DirectorState, _) -> 227 | {ok, ChildState, DirectorState, []}. % Default log value is true 228 | ``` 229 | 230 | ### `terminate/2` 231 | When director is terminating itself, after terminating its alive children, It calls: 232 | ```erlang 233 | YourCallbackModule:terminate(ReasonOfTermination, DirectorState) 234 | ``` 235 | For above example if **Director** is terminating with reason `normal`, it calls: 236 | ```erlang 237 | director_test:terminate(normal, #state{}) 238 | ``` 239 | This callback-function should yield: 240 | ```erlang 241 | -type terminate_return() :: {'ok', callback_return_options()} 242 | | {'new_error', NewReason::any(), callback_return_options()} 243 | | any(). 244 | ``` 245 | For example following code tells **Director** to change crash reason `oops` to `normal` and do not call `error_logger` about terminating yourself: 246 | ```erlang 247 | terminate(oops, State) -> 248 | {new_error, normal, [{log, false}]}. 249 | ``` 250 | Anything other than {`ok`, Opts}` and `{new_error, _, Opts}` causes **Director** to call `error_logger` and exit with its crash reason. 251 | 252 | # Build 253 | ## Compile 254 | ```sh 255 | ~/director $ make 256 | ===> Verifying dependencies... 257 | ===> Compiling director 258 | ``` 259 | 260 | ## Use as dependency 261 | ##### Rebar3 262 | Put this in deps in rebar.config: 263 | ```erlang 264 | {director, "18.4.29"} 265 | ``` 266 | 267 | ##### Rebar 268 | Put this in deps in rebar.config: 269 | ```erlang 270 | {director, ".*", {git, "https://github.com/Pouriya-Jahanbakhsh/director.git", {tag, "18.4.29"}}} 271 | ``` 272 | 273 | ##### Mix 274 | Put this in deps in mix.exs: 275 | ```elixir 276 | {:director, "~> 18.4.29"} 277 | ``` 278 | 279 | ##### erlang.mk 280 | ```make 281 | dep_director = hex 18.4.29 282 | ``` 283 | 284 | #### API documentation 285 | ```sh 286 | 287 | /projects/director $ make doc 288 | ===> Verifying dependencies... 289 | ===> Fetching edown ({pkg,"edown","0.8.1"}) 290 | ===> Compiling edown 291 | ===> Compiling director 292 | ===> Running edoc for director 293 | 294 | /projects/director $ ls doc/ | grep .md 295 | director.md 296 | director_table_ets.md 297 | director_table_mnesia.md 298 | README.md 299 | ``` 300 | 301 | ### Todo 302 | * Add test for having something like OTP/Supervisor `simple_one_for_one` strategy. 303 | * Add complete examples. 304 | * Add documentation for writing new `director_table` behaviour based module. 305 | 306 | 307 | ### License 308 | **`BSD 3-Clause`** 309 | 310 | 311 | ### Author 312 | **`pouriya.jahanbakhsh@gmail.com`** 313 | 314 | 315 | ### Hex version 316 | [**`18.4.29`**](https://hex.pm/packages/director) 317 | -------------------------------------------------------------------------------- /director.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pouriya/director/919c30db18c83330290fba9e68de330000978674/director.jpg -------------------------------------------------------------------------------- /include/internal/director_child.hrl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | 34 | -define(CHILD, director_child). 35 | -record(?CHILD, {id 36 | ,pid 37 | ,append 38 | ,restart_count 39 | ,start 40 | ,timer 41 | ,terminate_timeout 42 | ,extra 43 | ,modules 44 | ,type 45 | ,supervisor 46 | ,state 47 | ,delete}). -------------------------------------------------------------------------------- /include/internal/director_defaults.hrl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | 34 | -define(DEF_START_OPTIONS, []). 35 | -define(DEF_STOP_TIMEOUT, 'infinity'). 36 | -define(DEF_CALL_TIMEOUT, 5000). 37 | 38 | -define(DEF_DEBUG_OPTIONS, []). 39 | -define(GEN_CALL_TAG, '$gen_call'). 40 | -define(COUNT_CHILDREN_TAG, 'count_children'). 41 | -define(DELETE_CHILD_TAG, 'delete_child'). 42 | -define(GET_CHILDSPEC_TAG, 'get_childspec'). 43 | -define(RESTART_CHILD_TAG, 'restart_child'). 44 | -define(START_CHILD_TAG, 'start_child'). 45 | -define(TERMINATE_CHILD_TAG, 'terminate_child'). 46 | -define(WHICH_CHILDREN_TAG, 'which_children'). 47 | -define(GET_PID_TAG, 'get_pid'). 48 | -define(GET_PIDS_TAG, 'get_pids'). 49 | -define(CHANGE_PLAN_TAG, 'change_plan'). 50 | -define(CHANGE_COUNT_TAG, 'change_count'). 51 | -define(GET_PLAN_TAG, 'get_plan'). 52 | -define(GET_RESTART_COUNT_TAG, 'get_restart_count'). 53 | -define(GET_DEF_CHILDSPEC, 'get_default_childspec'). 54 | -define(CHANGE_DEF_CHILDSPEC, 'change_default_childspec'). 55 | -define(TERMINATE_AND_DELETE_CHILD_TAG, 'terminate_and_delete_child'). 56 | -define(DEF_LOG, 'true'). 57 | -define(DEF_TABLE_MODE, 'list'). 58 | -define(DEF_TABLE_INIT_ARG, 'undefined'). 59 | -define(DEF_DELETE_TABLE, 'true'). 60 | -define(DEF_CHILDSPEC_STATE, 'undefined'). 61 | -define(BECOME_SUPERVISOR_TAG, 'become_supervisor'). 62 | -define(CHANGE_PARENT_TAG, 'change_parent'). 63 | -define(DELETE_RUNNING_CHILD_TAG, 'delete_running_child'). 64 | -define(DEF_DELETE_BEFORE_TERMINATE, 'true'). 65 | -define(DEF_ACTION, wait). 66 | 67 | -define(DEF_PLAN, fun director:plan/4). 68 | -define(DEF_TYPE, worker). 69 | -define(DEF_APPEND, false). 70 | -define(DEF_WORKER_TERMINATE_TIMEOUT, 1000). 71 | -define(DEF_SUPERVISOR_TERMINATE_TIMEOUT, infinity). 72 | 73 | -define(DEF_DEF_CHILDSPEC_TERMINATE_TIMEOUT, 0). 74 | -define(DEF_DEF_CHILDSPEC_MODULES, []). 75 | -define(DEF_DEF_CHILDSPEC, #{terminate_timeout => ?DEF_DEF_CHILDSPEC_TERMINATE_TIMEOUT 76 | ,modules => ?DEF_DEF_CHILDSPEC_MODULES}). -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info, warnings_as_errors]}. 2 | 3 | {minimum_otp_vsn, "18"}. 4 | 5 | {profiles, [{doc, [{deps, [edown]}, {edoc_opts, [{doclet, edown_doclet}]}]}]}. 6 | 7 | {dialyzer, [{warnings, [no_return]} 8 | ,{plt_apps, top_level_deps} 9 | ,{plt_prefix, "director"}]}. 10 | 11 | %%{plugins, [rebar3_hex]}. 12 | -------------------------------------------------------------------------------- /rebar.config.script: -------------------------------------------------------------------------------- 1 | case os:getenv("TRAVIS") of 2 | "true" -> 3 | [{plugins, [coveralls]} 4 | ,{cover_enabled, true} 5 | ,{cover_export_enabled, true} 6 | ,{coveralls_coverdata, "_build/test/cover/ct.coverdata"} 7 | ,{coveralls_service_name, "travis-ci"} 8 | ,{coveralls_service_job_id, os:getenv("TRAVIS_JOB_ID")} 9 | |CONFIG]; 10 | _ -> 11 | CONFIG 12 | end. -------------------------------------------------------------------------------- /rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pouriya/director/919c30db18c83330290fba9e68de330000978674/rebar3 -------------------------------------------------------------------------------- /src/director.app.src: -------------------------------------------------------------------------------- 1 | {application 2 | ,director 3 | ,[{description, "It is a production-ready supervisor and manager for Erlang/Elixir processes that focuses on speed, performance and flexibility."} 4 | ,{vsn, "18.9.30"} 5 | ,{applications, [kernel, stdlib]} 6 | ,{maintainers, ["pouriya.jahanbakhsh@gmail.com"]} 7 | ,{licenses, ["BSD 3-Clause"]} 8 | ,{links, [{"GitHub", "https://github.com/Pouriya-Jahanbakhsh/director"}]}]}. 9 | -------------------------------------------------------------------------------- /src/director_child.erl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | %% @author Pouriya Jahanbakhsh 34 | %% @version 18.2.20 35 | %% @hidden 36 | %% ------------------------------------------------------------------------------------------------- 37 | -module(director_child). 38 | -author("pouriya.jahanbakhsh@gmail.com"). 39 | %% ------------------------------------------------------------------------------------------------- 40 | %% Exports: 41 | 42 | %% API: 43 | -export([child_to_proplist/1 44 | ,child_to_childspec/1 45 | ,childspec_to_child/1 46 | ,check_childspecs/1 47 | ,check_childspecs/2 48 | ,check_childspec/2 49 | ,check_default_childspec/1 50 | ,combine_child/2 51 | ,separate_child/2]). 52 | 53 | %% ------------------------------------------------------------------------------------------------- 54 | %% Records & Macros & Includes: 55 | 56 | -include("internal/director_child.hrl"). 57 | -include("internal/director_defaults.hrl"). 58 | 59 | %% ------------------------------------------------------------------------------------------------- 60 | %% API Functions: 61 | 62 | 63 | child_to_proplist(#?CHILD{pid = Pid 64 | ,id = Id 65 | ,restart_count = ResCount 66 | ,start = Start 67 | ,timer= Timer 68 | ,terminate_timeout = TerminateTimeout 69 | ,extra = Extra 70 | ,modules = Mods 71 | ,type = Type 72 | ,append = Append 73 | ,supervisor = Sup 74 | ,state = State 75 | ,delete = DelBeforeTerminate}) -> 76 | [{id, Id} 77 | ,{pid, Pid} 78 | ,{restart_count, ResCount} 79 | ,{mfargs, Start} 80 | ,{timer, Timer} 81 | ,{restart_type, temporary} 82 | ,{shutdown, case TerminateTimeout of 83 | 0 -> 84 | brutal_kill; 85 | Timeout -> 86 | Timeout 87 | end} 88 | ,{child_type, Type} 89 | ,{extra, Extra} 90 | ,{modules, Mods} 91 | ,{append, Append} 92 | ,{supervisor, Sup} 93 | ,{state, State} 94 | ,{delete, DelBeforeTerminate}]. 95 | 96 | 97 | child_to_childspec(#?CHILD{id = Id 98 | ,start = Start 99 | ,terminate_timeout = TerminateTimeout 100 | ,modules = Modules 101 | ,type = Type 102 | ,append = Append 103 | ,state = State 104 | ,delete = DelBeforeTerminate}) -> 105 | #{id => Id 106 | ,start => Start 107 | ,terminate_timeout => TerminateTimeout 108 | ,modules => Modules 109 | ,type => Type 110 | ,append => Append 111 | ,state => State 112 | ,delete => DelBeforeTerminate}. 113 | 114 | 115 | childspec_to_child(#{id := Id 116 | ,start := Start 117 | ,terminate_timeout := TerminateTimeout 118 | ,modules := Mods 119 | ,type := Type 120 | ,append := Append 121 | ,state := State 122 | ,delete := DelBeforeTerminate}) -> 123 | #?CHILD{id = Id 124 | ,pid = undefined 125 | ,restart_count = 0 126 | ,start = Start 127 | ,timer = undefined 128 | ,terminate_timeout = TerminateTimeout 129 | ,extra = undefined 130 | ,modules = Mods 131 | ,type = Type 132 | ,append = Append 133 | ,supervisor = erlang:self() 134 | ,state = State 135 | ,delete = DelBeforeTerminate}. 136 | 137 | 138 | check_childspecs(ChildSpecs) -> 139 | check_childspecs(ChildSpecs, ?DEF_DEF_CHILDSPEC). 140 | 141 | 142 | check_childspecs([], _DefChildSpec) -> 143 | {ok, []}; 144 | check_childspecs(ChildSpecs, DefChildSpec) -> 145 | check_childspecs(ChildSpecs, DefChildSpec, []). 146 | 147 | 148 | check_default_childspec(ChildSpec) when erlang:is_map(ChildSpec) -> 149 | Keys = [{start, fun filter_start/1} 150 | ,{count, fun filter_count/1} 151 | ,{type, fun filter_type/1} 152 | ,{terminate_timeout, fun filter_terminate_timeout/1} 153 | ,{modules, fun filter_modules/1} 154 | ,{state, fun(St) -> {ok, St} end} 155 | ,{delete, fun filter_delete/1}], 156 | check_map2(ChildSpec, Keys, #{}); 157 | check_default_childspec(Other) -> 158 | {error, {default_childspec_type, [{childspec, Other}]}}. 159 | 160 | 161 | 162 | check_childspec(ChildSpec, DefChildSpec) when erlang:is_map(ChildSpec) -> 163 | Keys = [{append, fun filter_append/1, ?DEF_APPEND}], 164 | {ok, #{append := Append}} = {ok, ChildSpec2} = check_map(ChildSpec, Keys, #{}), 165 | StartKey = 166 | if 167 | Append -> 168 | case DefChildSpec of 169 | #{start := {Mod, Func, _Args}} -> 170 | {start, fun filter_start/1, {Mod, Func, []}}; 171 | _Other -> 172 | {start, fun filter_start/1} 173 | end; 174 | true -> 175 | {start, fun filter_start/1} 176 | end, 177 | Keys2 = [id 178 | ,StartKey 179 | ,{type, fun filter_type/1, ?DEF_TYPE} 180 | ,{state, fun(St) -> {ok, St} end, ?DEF_CHILDSPEC_STATE} 181 | ,{delete, fun filter_delete/1, ?DEF_DELETE_BEFORE_TERMINATE}], 182 | case check_map(ChildSpec, Keys2, ChildSpec2) of 183 | {ok, ChildSpec3} -> 184 | DefTerminateTimeout = 185 | case maps:get(type, ChildSpec3) of 186 | worker -> 187 | ?DEF_WORKER_TERMINATE_TIMEOUT; 188 | _ -> % supervisor 189 | ?DEF_SUPERVISOR_TERMINATE_TIMEOUT 190 | end, 191 | DefMods = [erlang:element(1, maps:get(start, ChildSpec3))], 192 | Keys3 = [{terminate_timeout, fun filter_terminate_timeout/1, DefTerminateTimeout} 193 | ,{modules, fun filter_modules/1, DefMods}], 194 | case check_map(ChildSpec, Keys3, ChildSpec3) of 195 | {ok, ChildSpec4} -> 196 | {ok, childspec_to_child((combine_child(ChildSpec4, DefChildSpec)))}; 197 | {error, _Reason}=Error -> 198 | Error 199 | end; 200 | {error, _Reason}=Error -> 201 | Error 202 | end; 203 | check_childspec(Other, _DefChildSpec) -> 204 | {error, {childspec_value, [{childspec, Other}]}}. 205 | 206 | 207 | separate_child(ChildSpec, DefChildSpec) -> 208 | case maps:get(append, ChildSpec) of 209 | true -> 210 | maps:fold(fun separate_child/3, DefChildSpec, ChildSpec); 211 | _ -> % false 212 | ChildSpec 213 | end. 214 | 215 | 216 | combine_child(ChildSpec, DefChildSpec) -> 217 | case maps:get(append, ChildSpec) of 218 | true -> 219 | maps:fold(fun combine_child/3, DefChildSpec, ChildSpec); 220 | _ -> % false 221 | ChildSpec 222 | end. 223 | %% ------------------------------------------------------------------------------------------------- 224 | %% Internal functions: 225 | 226 | check_childspecs([Elem|Elems], DefChildSpec, Children) -> 227 | case check_childspec(Elem, DefChildSpec) of 228 | {ok, ChildSpec} -> 229 | check_childspecs(Elems, DefChildSpec, [ChildSpec|Children]); 230 | {error, _}=Err -> 231 | Err 232 | end; 233 | check_childspecs([], _DefChildSpec, Children) -> 234 | {ok, lists:reverse(Children)}; 235 | check_childspecs(Other, _DefChildSpec, _Children) -> 236 | {error, {childspecs_type, [{childspecs, Other}]}}. 237 | 238 | 239 | is_whole_integer(Int) when erlang:is_integer(Int) -> 240 | if 241 | Int >= 0 -> 242 | true; 243 | true -> 244 | false 245 | end; 246 | is_whole_integer(_Other) -> 247 | false. 248 | 249 | 250 | filter_count(infinity) -> 251 | {ok, infinity}; 252 | filter_count(Count) -> 253 | case is_whole_integer(Count) of 254 | true -> 255 | {ok, Count}; 256 | false -> 257 | {error, {childspec_value, [{count, Count}]}} 258 | end. 259 | 260 | 261 | filter_terminate_timeout(infinity) -> 262 | {ok, infinity}; 263 | filter_terminate_timeout(TerminateTimeout) -> 264 | case is_whole_integer(TerminateTimeout) of 265 | true -> 266 | {ok, TerminateTimeout}; 267 | false -> 268 | {error, {childspec_value, [{terminate_timeout, TerminateTimeout}]}} 269 | end. 270 | 271 | 272 | filter_type(worker) -> 273 | {ok, worker}; 274 | filter_type(supervisor) -> 275 | {ok, supervisor}; 276 | filter_type(sup) -> 277 | {ok, supervisor}; 278 | filter_type(w) -> 279 | {ok, worker}; 280 | filter_type(s) -> 281 | {ok, supervisor}; 282 | filter_type(Other) -> 283 | {error, {childspec_value, [{type, Other}]}}. 284 | 285 | 286 | filter_modules(dynamic) -> 287 | {ok, dynamic}; 288 | filter_modules(Mod) when erlang:is_atom(Mod) -> 289 | {ok, [Mod]}; 290 | filter_modules(Mods) when erlang:is_list(Mods) -> 291 | {ok, Mods}; 292 | filter_modules(Other) -> 293 | {error, {childspec_value, [{modules, Other}]}}. 294 | 295 | 296 | filter_append(Bool) when erlang:is_boolean(Bool) -> 297 | {ok, Bool}; 298 | filter_append(Other) -> 299 | {error, {childspec_value, [{append, Other}]}}. 300 | 301 | 302 | filter_delete(Bool) when erlang:is_boolean(Bool) -> 303 | {ok, Bool}; 304 | filter_delete(Other) -> 305 | {error, {childspec_value, [{delete, Other}]}}. 306 | 307 | 308 | check_map(ChildSpec, [{Key, Filter, DEF}|Keys], ChildSpec2) -> 309 | try maps:get(Key, ChildSpec) of 310 | Value -> 311 | case Filter(Value) of 312 | {ok, Value2} -> 313 | check_map(ChildSpec, Keys, maps:put(Key, Value2, ChildSpec2)); 314 | {error, Rsn} -> 315 | {error, {childspec_value, [{key, Key}, {reason, Rsn}]}} 316 | end 317 | catch 318 | _:_ -> 319 | check_map(ChildSpec, Keys, maps:put(Key, DEF, ChildSpec2)) 320 | 321 | end; 322 | check_map(ChildSpec, [{Key, Filter}|Keys], ChildSpec2) -> 323 | try maps:get(Key, ChildSpec) of 324 | Value -> 325 | case Filter(Value) of 326 | {ok, Value2} -> 327 | check_map(ChildSpec, Keys, maps:put(Key, Value2, ChildSpec2)); 328 | {error, Rsn} -> 329 | {error, {childspec_value, [{key, Key}, {reason, Rsn}]}} 330 | end 331 | catch 332 | _:_-> 333 | {error, {key_not_found, [{key, Key}, {childspec, ChildSpec}]}} 334 | end; 335 | check_map(ChildSpec, [Key|Keys], ChildSpec2) -> 336 | try maps:get(Key, ChildSpec) of 337 | Value -> 338 | check_map(ChildSpec, Keys, maps:put(Key, Value, ChildSpec2)) 339 | catch 340 | _:_ -> 341 | {error, {key_not_found, [{key, Key}, {childspec, ChildSpec}]}} 342 | end; 343 | check_map(_ChidlSpec, [], ChildSpec2) -> 344 | {ok, ChildSpec2}. 345 | 346 | 347 | check_map2(ChildSpec, [{Key, Filter}|Keys], ChildSpec2) -> 348 | try maps:get(Key, ChildSpec) of 349 | Value -> 350 | case Filter(Value) of 351 | {ok, Value2} -> 352 | check_map2(ChildSpec, Keys, maps:put(Key, Value2, ChildSpec2)); 353 | {error, Rsn} -> 354 | {error, {childspec_value, [{key, Key}, {reason, Rsn}]}} 355 | end 356 | catch 357 | _:_ -> 358 | check_map2(ChildSpec, Keys, ChildSpec2) 359 | end; 360 | check_map2(_ChidlSpec, [], ChildSpec2) -> 361 | {ok, ChildSpec2}. 362 | 363 | 364 | filter_start({Mod, Func, Args}=Start)when erlang:is_atom(Mod) andalso 365 | erlang:is_atom(Func) andalso 366 | erlang:is_list(Args) -> 367 | {ok, Start}; 368 | filter_start({Mod, Func}) when erlang:is_atom(Mod) andalso erlang:is_atom(Func) -> 369 | {ok, {Mod, Func, []}}; 370 | filter_start(Mod) when erlang:is_atom(Mod) -> 371 | {ok, {Mod, start_link, []}}; 372 | filter_start(Other) -> 373 | {error, {childspec_value, [{start, Other}]}}. 374 | 375 | 376 | combine_child(start, {Mod, Func, Args}, #{start := {Mod2, Func2, Args2}}=Map) -> 377 | if 378 | Mod =:= Mod2 andalso Func =:= Func2 -> 379 | Map#{start => {Mod, Func, director_utils:concat(Args2, Args)}}; 380 | true -> 381 | Map 382 | end; 383 | combine_child(terminate_timeout, TerminateTimeout, #{terminate_timeout := TerminateTimeout2}=Map) -> 384 | if 385 | erlang:is_integer(TerminateTimeout) andalso erlang:is_integer(TerminateTimeout2) -> 386 | Map#{terminate_timeout => TerminateTimeout2 + TerminateTimeout}; 387 | true -> 388 | Map 389 | end; 390 | combine_child(modules, Mods, #{modules := Mods2}=Map) -> 391 | if 392 | erlang:is_list(Mods) andalso erlang:is_list(Mods2) -> 393 | Map#{modules => director_utils:concat(Mods2, Mods)}; 394 | true -> 395 | Map 396 | end; 397 | combine_child(type, _Type, #{type := _Type2}=Map) -> 398 | Map; 399 | combine_child(state, _State, #{state := _State2}=Map) -> 400 | Map; 401 | combine_child(delete, _DelBeforeTerminate, #{delete := _DelBeforeTerminate2}=Map) -> 402 | Map; 403 | combine_child(Key, Val, Map) -> 404 | maps:put(Key, Val, Map). 405 | 406 | 407 | separate_child(start, {Mod, Func, Args}, #{start := {_Mod2, _Func2, Args2}}=Map) -> 408 | Map#{start => {Mod, Func, Args -- Args2}}; 409 | separate_child(terminate_timeout, infinity, Map) -> 410 | Map#{terminate_timeout => infinity}; 411 | separate_child(terminate_timeout 412 | ,TerminateTimeout 413 | ,#{terminate_timeout := TerminateTimeout2}=Map) -> 414 | if 415 | TerminateTimeout2 =:= infinity -> 416 | Map#{terminate_timeout => TerminateTimeout}; 417 | true -> 418 | Map#{terminate_timeout => TerminateTimeout - TerminateTimeout2} 419 | end; 420 | separate_child(modules, dynamic, Map) -> 421 | Map#{modules => dynamic}; 422 | separate_child(modules, Mods, #{modules := Mods2}=Map) -> 423 | if 424 | Mods2 =:= dynamic -> 425 | Map#{modules => Mods}; 426 | true -> 427 | Map#{modules => Mods -- Mods2} 428 | end; 429 | separate_child(Key, Value, Map) -> 430 | maps:put(Key, Value, Map). -------------------------------------------------------------------------------- /src/director_table.erl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | %% @author Pouriya Jahanbakhsh 34 | %% @version 18.2.20 35 | %% @hidden 36 | %% @doc 37 | %% API functions for keeping, updating and fetching 38 | %% childspecs data.
39 | %% director supports tho type of tables: list, ets. 40 | %% @end 41 | %% ------------------------------------------------------------------------------------------------- 42 | 43 | 44 | -module(director_table). 45 | -author("pouriya.jahanbakhsh@gmail.com"). 46 | 47 | 48 | %% ------------------------------------------------------------------------------------------------- 49 | %% Exports: 50 | 51 | %% Director's API: 52 | -export([create/2 53 | ,insert/3 54 | ,delete/3 55 | ,lookup_id/3 56 | ,lookup_pid/3 57 | ,lookup_appended/2 58 | ,combine_children/3 59 | ,separate_children/3 60 | ,count/2 61 | ,delete_table/2 62 | ,tab2list/2 63 | ,handle_message/3 64 | ,change_parent/3]). 65 | 66 | %% Callback module API: 67 | -export([count_children/2 68 | ,which_children/2 69 | ,get_childspec/3 70 | ,get_pid/3 71 | ,get_pids/2 72 | ,get_restart_count/3]). 73 | 74 | %% ------------------------------------------------------------------------------------------------- 75 | %% Records & Macros & Includes: 76 | 77 | %% Dependencies: 78 | %% #?CHILD{} 79 | -include("internal/director_child.hrl"). 80 | 81 | %% ------------------------------------------------------------------------------------------------- 82 | %% Behavior information: 83 | 84 | -callback 85 | create({'value', InitArgument::any()} | 'undefined') -> 86 | {'ok', State::any()} | {'hard_error', {Reason::atom(), ErrorParams::list()}}. 87 | 88 | 89 | -callback 90 | insert(State::any(), Child::#?CHILD{}) -> 91 | {'ok', NewState::any()} | {'hard_error', {Reason::atom(), ErrorParams::list()}}. 92 | 93 | 94 | -callback 95 | delete(State::any(), Child::#?CHILD{}) -> 96 | {'ok', NewState::any()} | 97 | {'soft_error', Reason::'not_found'} | 98 | {'soft_error', NewState::any(), Reason::'not_found'} | 99 | {'hard_error', {Reason::atom(), ErrorParams::list()}}. 100 | 101 | 102 | -callback 103 | lookup_id(State::any(), Id::any()) -> 104 | {'ok', Child::#?CHILD{}} | 105 | {'soft_error', Reason::'not_found'} | 106 | {'soft_error', NewState::any(), Reason::'not_found'} | 107 | {'hard_error', {Reason::atom(), ErrorParams::list()}}. 108 | 109 | 110 | -callback 111 | lookup_pid(State::any(), Pid::pid()) -> 112 | {'ok', Child::#?CHILD{}} | 113 | {'soft_error', Reason::'not_found'} | 114 | {'soft_error', NewState::any(), Reason::'not_found'} | 115 | {'hard_error', {Reason::atom(), ErrorParams::list()}}. 116 | 117 | 118 | -callback 119 | lookup_appended(State::any()) -> 120 | {'ok', [Child::#?CHILD{}] | []} | {'hard_error', {Reason::atom(), ErrorParams::list()}}. 121 | 122 | 123 | -callback 124 | count(State::any()) -> 125 | {'ok', Count::non_neg_integer()} | {'hard_error', {Reason::atom(), ErrorParams::list()}}. 126 | 127 | 128 | -callback 129 | tab2list(State::any()) -> 130 | {'ok', [Child::#?CHILD{}] | []} | {'hard_error', {Reason::atom(), ErrorParams::list()}}. 131 | 132 | 133 | -callback 134 | delete_table(State::any()) -> 135 | 'ok' | {'hard_error', {Reason::atom(), ErrorParams::list()}}. 136 | 137 | 138 | -callback 139 | handle_message(State::any(), Msg::any()) -> 140 | {'ok', Child::#?CHILD{}} | 141 | {'soft_error', Reason::'unknown'} | 142 | {'soft_error', NewState::any(), Reason::'unknown'} | 143 | {'hard_error', {Reason::atom(), ErrorParams::list()}}. 144 | 145 | 146 | -callback 147 | change_parent(State::any(), Child::#?CHILD{}) -> 148 | {'ok', NewState::any()} | 149 | {'soft_error', Reason::'not_parent'} | 150 | {'soft_error', NewState::any(), Reason::'not_parent'}| 151 | {'hard_error', {Reason::atom(), ErrorParams::list()}}. 152 | 153 | %% ------------------------------------------------------------------------------------------------- 154 | %% Callback module API: 155 | 156 | count_children(Mod, State) -> 157 | case tab2list(Mod, State) of 158 | {ok, Children} -> 159 | Fun = 160 | fun(#?CHILD{pid = Pid, type = Type}, {Specs, Actives, Sups, Workers}) -> 161 | Actives2 = 162 | if 163 | erlang:is_pid(Pid) -> 164 | Actives+1; 165 | true -> 166 | Actives 167 | end, 168 | {Sups2, Workers2} = 169 | if 170 | Type =:= supervisor -> 171 | {Sups+1, Workers}; 172 | Type =:= worker -> 173 | {Sups, Workers+1} 174 | end, 175 | {Specs+1, Actives2, Sups2, Workers2} 176 | end, 177 | {Specs, Actives, Sups, Workers} = lists:foldl(Fun, {0, 0, 0, 0}, Children), 178 | [{specs, Specs}, {active, Actives}, {supervisors, Sups}, {workers, Workers}]; 179 | {hard_error, Rsn} -> 180 | {error, Rsn} 181 | end. 182 | 183 | 184 | which_children(Mod, State) -> 185 | case director_table:tab2list(Mod, State) of 186 | {ok, Children} -> 187 | [{Id, Pid, Type, Mods} || #?CHILD{id = Id 188 | ,pid = Pid 189 | ,type = Type 190 | ,modules = Mods} <- Children]; 191 | {hard_error, Rsn} -> 192 | {error, Rsn} 193 | end. 194 | 195 | 196 | get_childspec(Mod, State, PidOrId) -> 197 | SearchFunc = 198 | if 199 | erlang:is_pid(PidOrId) -> 200 | lookup_pid; 201 | true -> 202 | lookup_id 203 | end, 204 | case ?MODULE:SearchFunc(Mod, State, PidOrId) of 205 | {ok, Child} -> 206 | {ok, director_child:child_to_childspec(Child)}; 207 | {soft_error, _, Rsn} -> 208 | {error, Rsn}; 209 | {hard_error, Rsn} -> 210 | {error, Rsn} 211 | end. 212 | 213 | 214 | get_pid(Mod, State, Id) -> 215 | case lookup_id(Mod, State, Id) of 216 | {ok, #?CHILD{pid = Pid}} -> 217 | {ok, Pid}; 218 | {soft_error, _, Rsn} -> 219 | {error, Rsn}; 220 | {hard_error, Rsn} -> 221 | {error, Rsn} 222 | end. 223 | 224 | 225 | get_pids(Mod, State) -> 226 | case director_table:tab2list(Mod, State) of 227 | {ok, Children} -> 228 | {ok, [{Id, Pid} || #?CHILD{id = Id, pid = Pid} <- Children, erlang:is_pid(Pid)]}; 229 | {hard_error, Rsn} -> 230 | {error, Rsn} 231 | end. 232 | 233 | 234 | get_restart_count(Mod, State, Id) -> 235 | case lookup_id(Mod, State, Id) of 236 | {ok, #?CHILD{restart_count = ResCount}} -> 237 | {ok, ResCount}; 238 | {soft_error, _, Rsn} -> 239 | {error, Rsn}; 240 | {hard_error, Rsn} -> 241 | {error, Rsn} 242 | end. 243 | 244 | %% ------------------------------------------------------------------------------------------------- 245 | %% API functions: 246 | 247 | create(Mod, InitArg) -> 248 | try Mod:create(InitArg) of 249 | {ok, _}=Ok -> 250 | Ok; 251 | {hard_error, {Rsn, ErrParams}} when erlang:is_atom(Rsn) andalso erlang:is_list(ErrParams) -> 252 | {hard_error, {Rsn, ErrParams ++ [{module, Mod} 253 | ,{function, create} 254 | ,{arguments, [InitArg]}]}}; 255 | Other -> 256 | {hard_error, {table_return, [{value, Other} 257 | ,{module, Mod} 258 | ,{function, create} 259 | ,{arguments, [InitArg]}]}} 260 | catch 261 | _:Rsn:Stacktrace -> 262 | {hard_error, {table_crash, [{reason, Rsn} 263 | ,{stacktrace, Stacktrace} 264 | ,{module, Mod} 265 | ,{function, create} 266 | ,{arguments, [InitArg]}]}} 267 | end. 268 | 269 | 270 | delete_table(Mod, State) -> 271 | try Mod:delete_table(State) of 272 | ok -> 273 | ok; 274 | {hard_error, {Rsn, ErrParams}} when erlang:is_atom(Rsn) andalso erlang:is_list(ErrParams) -> 275 | {hard_error, {Rsn, ErrParams ++ [{module, Mod} 276 | ,{function, delete_table} 277 | ,{arguments, [State]}]}}; 278 | Other -> 279 | {hard_error, {table_return, [{value, Other} 280 | ,{module, Mod} 281 | ,{function, delete_table} 282 | ,{arguments, [State]}]}} 283 | catch 284 | _:Rsn:Stacktrace -> 285 | {hard_error, {table_crash, [{reason, Rsn} 286 | ,{stacktrace, Stacktrace} 287 | ,{module, Mod} 288 | ,{function, delete_table} 289 | ,{arguments, [State]}]}} 290 | end. 291 | 292 | 293 | lookup_id(Mod, State, Id) -> 294 | try Mod:lookup_id(State, Id) of 295 | {ok, #?CHILD{}}=Ok -> 296 | Ok; 297 | {soft_error, not_found} -> 298 | {soft_error, State, not_found}; 299 | {soft_error, _, not_found}=SErr -> 300 | SErr; 301 | {hard_error, {Rsn, ErrParams}} when erlang:is_atom(Rsn) andalso erlang:is_list(ErrParams) -> 302 | {hard_error, {Rsn, ErrParams ++ [{module, Mod} 303 | ,{function, lookup_id} 304 | ,{arguments, [State, Id]}]}}; 305 | Other -> 306 | {hard_error, {table_return, [{value, Other} 307 | ,{module, Mod} 308 | ,{function, lookup_id} 309 | ,{arguments, [State, Id]}]}} 310 | catch 311 | _:Rsn:Stacktrace -> 312 | {hard_error, {table_crash, [{reason, Rsn} 313 | ,{stacktrace, Stacktrace} 314 | ,{module, Mod} 315 | ,{function, lookup_id} 316 | ,{arguments, [State, Id]}]}} 317 | end. 318 | 319 | 320 | count(Mod, State) -> 321 | try Mod:count(State) of 322 | {ok, Count}=Ok when erlang:is_integer(Count) andalso Count > -1 -> 323 | Ok; 324 | {hard_error, {Rsn, ErrParams}} when erlang:is_atom(Rsn) andalso erlang:is_list(ErrParams) -> 325 | {hard_error, {Rsn, ErrParams ++ [{module, Mod} 326 | ,{function, count} 327 | ,{arguments, [State]}]}}; 328 | Other -> 329 | {hard_error, {table_return, [{value, Other} 330 | ,{module, Mod} 331 | ,{function, count} 332 | ,{arguments, [State]}]}} 333 | catch 334 | _:Rsn:Stacktrace -> 335 | {hard_error, {table_crash, [{reason, Rsn} 336 | ,{stacktrace, Stacktrace} 337 | ,{module, Mod} 338 | ,{function, count} 339 | ,{arguments, [State]}]}} 340 | end. 341 | 342 | 343 | lookup_pid(Mod, State, Pid) -> 344 | try Mod:lookup_pid(State, Pid) of 345 | {ok, #?CHILD{}}=Ok -> 346 | Ok; 347 | {soft_error, not_found} -> 348 | {soft_error, State, not_found}; 349 | {soft_error, _, not_found}=SErr -> 350 | SErr; 351 | {hard_error, {Rsn, ErrParams}} when erlang:is_atom(Rsn) andalso erlang:is_list(ErrParams) -> 352 | {hard_error, {Rsn, ErrParams ++ [{module, Mod} 353 | ,{function, lookup_pid} 354 | ,{arguments, [State, Pid]}]}}; 355 | Other -> 356 | {hard_error, {table_return, [{value, Other} 357 | ,{module, Mod} 358 | ,{function, lookup_pid} 359 | ,{arguments, [State, Pid]}]}} 360 | catch 361 | _:Rsn:Stacktrace -> 362 | {hard_error, {table_crash, [{reason, Rsn} 363 | ,{stacktrace, Stacktrace} 364 | ,{module, Mod} 365 | ,{function, lookup_pid} 366 | ,{arguments, [State, Pid]}]}} 367 | end. 368 | 369 | 370 | lookup_appended(Mod, State) -> 371 | try Mod:lookup_appended(State) of 372 | {ok, List}=Ok when erlang:is_list(List) -> 373 | case validate_children(List) of 374 | true -> 375 | Ok; 376 | false -> 377 | {hard_error, {table_return, [{value, Ok} 378 | ,{module, Mod} 379 | ,{function, lookup_appended} 380 | ,{arguments, [State]}]}} 381 | end; 382 | {hard_error, {Rsn, ErrParams}} when erlang:is_atom(Rsn) andalso erlang:is_list(ErrParams) -> 383 | {hard_error, {Rsn, ErrParams ++ [{module, Mod} 384 | ,{function, lookup_appended} 385 | ,{arguments, [State]}]}}; 386 | Other -> 387 | {hard_error, {table_return, [{value, Other} 388 | ,{module, Mod} 389 | ,{function, lookup_appended} 390 | ,{arguments, [State]}]}} 391 | catch 392 | _:Rsn:Stacktrace -> 393 | {hard_error, {table_crash, [{reason, Rsn} 394 | ,{stacktrace, Stacktrace} 395 | ,{module, Mod} 396 | ,{function, lookup_appended} 397 | ,{arguments, [State]}]}} 398 | end. 399 | 400 | 401 | insert(Mod, State, Child) -> 402 | try Mod:insert(State, Child) of 403 | {ok, _}=Ok -> 404 | Ok; 405 | {hard_error, {Rsn, ErrParams}} when erlang:is_atom(Rsn) andalso erlang:is_list(ErrParams) -> 406 | {hard_error, {Rsn, ErrParams ++ [{module, Mod} 407 | ,{function, insert} 408 | ,{arguments, [State, Child]}]}}; 409 | Other -> 410 | {hard_error, {table_return, [{value, Other} 411 | ,{module, Mod} 412 | ,{function, insert} 413 | ,{arguments, [State, Child]}]}} 414 | catch 415 | _:Rsn:Stacktrace -> 416 | {hard_error, {table_crash, [{reason, Rsn} 417 | ,{stacktrace, Stacktrace} 418 | ,{module, Mod} 419 | ,{function, insert} 420 | ,{arguments, [State, Child]}]}} 421 | end. 422 | 423 | 424 | delete(Mod, State, Child) -> 425 | try Mod:delete(State, Child#?CHILD.id) of 426 | {ok, _}=Ok -> 427 | Ok; 428 | {soft_error, not_found} -> 429 | {soft_error, State, not_found}; 430 | {soft_error, _, not_found}=SErr -> 431 | SErr; 432 | {hard_error, {Rsn, ErrParams}} when erlang:is_atom(Rsn) andalso erlang:is_list(ErrParams) -> 433 | {hard_error, {Rsn, ErrParams ++ [{module, Mod} 434 | ,{function, delete} 435 | ,{arguments, [State, Child]}]}}; 436 | Other -> 437 | {hard_error, {table_return, [{value, Other} 438 | ,{module, Mod} 439 | ,{function, delete} 440 | ,{arguments, [State, Child]}]}} 441 | catch 442 | _:Rsn:Stacktrace -> 443 | {hard_error, {table_crash, [{reason, Rsn} 444 | ,{stacktrace, Stacktrace} 445 | ,{module, Mod} 446 | ,{function, delete} 447 | ,{arguments, [State, Child]}]}} 448 | end. 449 | 450 | 451 | tab2list(Mod, State) -> 452 | try Mod:tab2list(State) of 453 | {ok, List}=Ok when erlang:is_list(List) -> 454 | case validate_children(List) of 455 | true -> 456 | Ok; 457 | false -> 458 | {hard_error, {table_return, [{value, Ok} 459 | ,{module, Mod} 460 | ,{function, tab2list} 461 | ,{arguments, [State]}]}} 462 | end; 463 | {hard_error, {Rsn, ErrParams}} when erlang:is_atom(Rsn) andalso erlang:is_list(ErrParams) -> 464 | {hard_error, {Rsn, ErrParams ++ [{module, Mod} 465 | ,{function, tab2list} 466 | ,{arguments, [State]}]}}; 467 | Other -> 468 | {hard_error, {table_return, [{value, Other} 469 | ,{module, Mod} 470 | ,{function, tab2list} 471 | ,{arguments, [State]}]}} 472 | catch 473 | _:Rsn:Stacktrace -> 474 | {hard_error, {table_crash, [{reason, Rsn} 475 | ,{stacktrace, Stacktrace} 476 | ,{module, Mod} 477 | ,{function, tab2list} 478 | ,{arguments, [State]}]}} 479 | end. 480 | 481 | 482 | combine_children(Mod, State, DefChildSpec) -> 483 | case lookup_appended(Mod, State) of 484 | {ok, Appended} -> 485 | case validate_parent(Appended) of 486 | true -> 487 | Combine = 488 | fun(Child) -> 489 | ChildSpec = director_child:child_to_childspec(Child), 490 | Combined = director_child:combine_child(ChildSpec, DefChildSpec), 491 | director_child:childspec_to_child(Combined) 492 | end, 493 | CombinedChildren = [Combine(Child) || Child <- Appended], 494 | case insert_children(Mod, State, CombinedChildren) of 495 | {ok, _}=Ok -> 496 | Ok; 497 | {hard_error, _}=HErr -> 498 | HErr 499 | end; 500 | false -> 501 | {soft_error, State, not_parent} 502 | end; 503 | {hard_error, {Rsn, ErrParams}} -> 504 | {hard_error, {Rsn, lists:keyreplace(function 505 | ,1 506 | ,ErrParams 507 | ,{function, combine_children})}} 508 | end. 509 | 510 | 511 | separate_children(Mod, State, DefChildSpec) -> 512 | case lookup_appended(Mod, State) of 513 | {ok, Appended} -> 514 | case validate_parent(Appended) of 515 | true -> 516 | Separate = 517 | fun(Child) -> 518 | ChildSpec = director_child:child_to_childspec(Child), 519 | Separated = director_child:separate_child(ChildSpec, DefChildSpec), 520 | director_child:childspec_to_child(Separated) 521 | end, 522 | SeparatedChildren = [Separate(Child) || Child <- Appended], 523 | case insert_children(Mod, State, SeparatedChildren) of 524 | {ok, _}=Ok -> 525 | Ok; 526 | {hard_error, _}=HErr -> 527 | HErr 528 | end; 529 | false -> 530 | {soft_error, State, not_parent} 531 | end; 532 | {hard_error, {Rsn, ErrParams}} -> 533 | {hard_error, {Rsn, lists:keyreplace(function 534 | ,1 535 | ,ErrParams 536 | ,{function, separate_children})}} 537 | end. 538 | 539 | 540 | handle_message(Mod, State, Msg) -> 541 | try Mod:handle_message(State, Msg) of 542 | {ok, _}=Ok -> 543 | Ok; 544 | {soft_error, unknown} -> 545 | {soft_error, State, unknown}; 546 | {soft_error, _, unknown}=SErr -> 547 | SErr; 548 | {hard_error, {Rsn, ErrParams}} when erlang:is_atom(Rsn) andalso erlang:is_list(ErrParams) -> 549 | {hard_error, {Rsn, ErrParams ++ [{module, Mod} 550 | ,{function, handle_message} 551 | ,{arguments, [State, Msg]}]}}; 552 | Other -> 553 | {hard_error, {table_return, [{value, Other} 554 | ,{module, Mod} 555 | ,{function, handle_message} 556 | ,{arguments, [State, Msg]}]}} 557 | catch 558 | _:Rsn:Stacktrace -> 559 | {hard_error, {table_crash, [{reason, Rsn} 560 | ,{stacktrace, Stacktrace} 561 | ,{module, Mod} 562 | ,{function, handle_message} 563 | ,{arguments, [State, Msg]}]}} 564 | end. 565 | 566 | 567 | change_parent(Mod, State, Child) -> 568 | try Mod:change_parent(State, Child) of 569 | {ok, _}=Ok -> 570 | Ok; 571 | {soft_error, not_parent} -> 572 | {soft_error, State, not_parent}; 573 | {soft_error, _, not_parent}=SErr -> 574 | SErr; 575 | {hard_error, {Rsn, ErrParams}} when erlang:is_atom(Rsn) andalso erlang:is_list(ErrParams) -> 576 | {hard_error, {Rsn, ErrParams ++ [{module, Mod} 577 | ,{function, change_parent} 578 | ,{arguments, [State, Child]}]}}; 579 | Other -> 580 | {hard_error, {table_return, [{value, Other} 581 | ,{module, Mod} 582 | ,{function, change_parent} 583 | ,{arguments, [State, Child]}]}} 584 | catch 585 | _:Rsn:Stacktrace -> 586 | {hard_error, {table_crash, [{reason, Rsn} 587 | ,{stacktrace, Stacktrace} 588 | ,{module, Mod} 589 | ,{function, change_parent} 590 | ,{arguments, [State, Child]}]}} 591 | end. 592 | 593 | %% ------------------------------------------------------------------------------------------------- 594 | %% Internal functions: 595 | 596 | insert_children(Mod, State, [Child|Children]) -> 597 | case insert(Mod, State, Child) of 598 | {ok, State2} -> 599 | insert_children(Mod, State2, Children); 600 | {hard_error, _}=Err -> 601 | Err 602 | end; 603 | insert_children(_, State, []) -> 604 | {ok, State}. 605 | 606 | 607 | validate_children(Children) -> 608 | lists:all(fun(Child) -> erlang:is_record(Child, ?CHILD) end, Children). 609 | 610 | 611 | validate_parent(Children) -> 612 | lists:all(fun(#?CHILD{supervisor = Sup}) -> Sup =:= erlang:self() end, Children). -------------------------------------------------------------------------------- /src/director_table_ets.erl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | %% @author Pouriya Jahanbakhsh 34 | %% @version 18.2.20 35 | %% @doc 36 | %% API functions for interacting with ETS table as backend children table. 37 | %% @end 38 | %% ------------------------------------------------------------------------------------------------- 39 | -module(director_table_ets). 40 | -author("pouriya.jahanbakhsh@gmail.com"). 41 | -behaviour(director_table). 42 | %% ------------------------------------------------------------------------------------------------- 43 | %% Exports: 44 | 45 | %% API: 46 | -export([count_children/1 47 | ,which_children/1 48 | ,get_childspec/2 49 | ,get_pid/2 50 | ,get_pids/1 51 | ,get_restart_count/2 52 | ,options/0]). 53 | 54 | %% director's API: 55 | -export([create/1 56 | ,insert/2 57 | ,delete/2 58 | ,lookup_id/2 59 | ,lookup_pid/2 60 | ,lookup_appended/1 61 | ,count/1 62 | ,delete_table/1 63 | ,tab2list/1 64 | ,handle_message/2 65 | ,change_parent/2]). 66 | 67 | %% ------------------------------------------------------------------------------------------------- 68 | %% Records & Macros & Includes: 69 | 70 | -define(TABLE_OPTIONS, [public, named_table, set, {keypos, 2}]). 71 | 72 | %% Dependencies: 73 | %% #?CHILD{} 74 | -include("internal/director_child.hrl"). 75 | 76 | -define(is_valid_type(Type), (Type =:= set orelse Type =:= ordered_set)). 77 | 78 | %% ------------------------------------------------------------------------------------------------- 79 | %% API: 80 | 81 | -spec 82 | count_children(atom()) -> 83 | [{'specs', non_neg_integer()} 84 | |{'active', non_neg_integer()} 85 | |{'supervisors', non_neg_integer()} 86 | |{'workers', non_neg_integer()}] | 87 | {'error', term()}. 88 | %% @doc 89 | %% Returns count of children.
90 | %% Error is for reading from table. 91 | %% @end 92 | count_children(Tab) -> 93 | director_table:count_children(?MODULE, Tab). 94 | 95 | -spec 96 | which_children(atom()) -> 97 | [{director:id(), director:type(), pid()|'restarting'|'undefined', director:modules()}] | 98 | [] | 99 | {'error', term()}. 100 | %% @doc 101 | %% Returns information about each child.
102 | %% Error maybe occur for reading from table. 103 | %% @end 104 | which_children(Tab) -> 105 | director_table:which_children(?MODULE, Tab). 106 | 107 | -spec 108 | get_childspec(atom(), director:id() | pid()) -> 109 | {'ok', director:childspec()} | {'error', 'not_found' | term()}. 110 | %% @doc 111 | %% Returns childspec of child id or child pid.
112 | %% Error maybe occur for reading from table. 113 | %% @end 114 | get_childspec(Tab, Id) -> 115 | director_table:get_childspec(?MODULE, Tab, Id). 116 | 117 | -spec 118 | get_pid(atom(), director:id()) -> 119 | {'ok', pid()} | {'error', 'not_found'|'restarting'|'undefined'|term()}. 120 | %% @doc 121 | %% Returns pid of child id if child is running.
122 | %% Error maybe occur for reading from table. 123 | %% @end 124 | get_pid(Tab, Id) -> 125 | director_table:get_pid(?MODULE, Tab, Id). 126 | 127 | -spec 128 | get_pids(atom()) -> 129 | {'ok', [{director:id(), pid()}] | []} | {'error', term()}. 130 | %% @doc 131 | %% Returns list of {id, pid}s for all running children.
132 | %% Error is for reading from table. 133 | %% @end 134 | get_pids(Tab) -> 135 | director_table:get_pids(?MODULE, Tab). 136 | 137 | -spec 138 | get_restart_count(atom(), director:id()) -> 139 | {'ok', non_neg_integer()} | {'error', 'not_found'|term()}. 140 | %% @doc 141 | %% Returns restart count of child id.
142 | %% Error maybe occur for reading from table. 143 | %% @end 144 | get_restart_count(Tab, Id) -> 145 | director_table:get_restart_count(?MODULE, Tab, Id). 146 | 147 | -spec 148 | options() -> 149 | list(). 150 | %% @doc 151 | %% Returns mandatory options of ETS table for creating table for a director process. 152 | %% @end 153 | options() -> 154 | ?TABLE_OPTIONS. 155 | 156 | %% ------------------------------------------------------------------------------------------------- 157 | %% Director's API functions: 158 | 159 | %% @hidden 160 | create({value, TabName}) -> 161 | case is_table(TabName) of 162 | true -> 163 | Self = erlang:self(), 164 | case {ets:info(TabName, protection) 165 | ,ets:info(TabName, owner) 166 | ,ets:info(TabName, type) 167 | ,ets:info(TabName, keypos)} of 168 | {public, _, Type, 2} when ?is_valid_type(Type) -> 169 | {ok, TabName}; 170 | {public, _, Type, Keypos} when ?is_valid_type(Type) -> 171 | {hard_error, {table_info, [{keypos, Keypos}]}}; 172 | {public, _, Type, _} -> 173 | {hard_error, {table_info, [{type, Type}]}}; 174 | {_, Self, Type, 2} when ?is_valid_type(Type) -> 175 | {ok, TabName}; 176 | {_, Self, Type, KeyPos} when ?is_valid_type(Type) -> 177 | {hard_error, {table_info, [{keypos, KeyPos}]}}; 178 | {_, Self, Type, _} -> 179 | {hard_error, {table_info, [{type, Type}]}}; 180 | {Protection, Pid, _Type, _Keypos} -> 181 | {hard_error, {table_info, [{protection, Protection}, {owner, Pid}]}} 182 | end; 183 | false -> 184 | try 185 | {ok, ets:new(TabName, ?TABLE_OPTIONS)} 186 | catch 187 | _:Reason -> 188 | {hard_error, {table_create, [{reason, Reason}]}} 189 | end 190 | end. 191 | 192 | 193 | %% @hidden 194 | delete_table(Tab) -> 195 | try ets:delete(Tab) of 196 | _ -> 197 | ok 198 | catch 199 | _:_ -> 200 | table_error(Tab) 201 | end. 202 | 203 | 204 | %% @hidden 205 | lookup_id(Tab, Id) -> 206 | try ets:lookup(Tab, Id) of 207 | [Child] -> 208 | {ok, Child}; 209 | [] -> 210 | {soft_error, not_found} 211 | catch 212 | _:_ -> 213 | table_error(Tab) 214 | end. 215 | 216 | 217 | %% @hidden 218 | count(Tab) -> 219 | case ets:info(Tab, size) of 220 | undefined -> 221 | {hard_error, {table_existence, []}}; 222 | Size -> 223 | {ok, Size} 224 | end. 225 | 226 | 227 | %% @hidden 228 | lookup_pid(Tab, Pid) -> 229 | try ets:match_object(Tab, #?CHILD{pid = Pid, _ = '_'}) of 230 | [Child] -> 231 | {ok, Child}; 232 | [] -> 233 | {soft_error, not_found} 234 | catch 235 | _:_ -> 236 | table_error(Tab) 237 | end. 238 | 239 | 240 | %% @hidden 241 | lookup_appended(Tab) -> 242 | try 243 | {ok, ets:match_object(Tab, #?CHILD{append = true, _ = '_'})} 244 | catch 245 | _:_ -> 246 | table_error(Tab) 247 | end. 248 | 249 | 250 | %% @hidden 251 | insert(Tab, Child) -> 252 | try 253 | _ = ets:insert(Tab, Child), 254 | {ok, Tab} 255 | catch 256 | _:_ -> 257 | table_error(Tab) 258 | end. 259 | 260 | 261 | %% @hidden 262 | delete(Tab, Id) -> 263 | try 264 | _ = ets:delete(Tab, Id), 265 | {ok, Tab} 266 | catch 267 | _:_ -> 268 | table_error(Tab) 269 | end. 270 | 271 | 272 | %% @hidden 273 | tab2list(Tab) -> 274 | try 275 | {ok, ets:tab2list(Tab)} 276 | catch 277 | _:_ -> 278 | table_error(Tab) 279 | end. 280 | 281 | 282 | %% @hidden 283 | handle_message(Tab, _) -> 284 | {soft_error, Tab, unknown}. 285 | 286 | 287 | %% @hidden 288 | change_parent(Tab, #?CHILD{id = Id}=Child) -> 289 | try ets:lookup(Tab, Id) of 290 | [#?CHILD{supervisor = Pid}] when erlang:self() =/= Pid andalso Pid =/= undefined -> 291 | {soft_error, not_parent}; 292 | _ -> 293 | _ = ets:insert(Tab, Child), 294 | {ok, Tab} 295 | catch 296 | _:_ -> 297 | table_error(Tab) 298 | end. 299 | 300 | %% ------------------------------------------------------------------------------------------------- 301 | %% Internal functions: 302 | 303 | table_error(Tab) -> 304 | case is_table(Tab) of 305 | true -> 306 | {hard_error, {table_info, [{protection, ets:info(Tab, protection)} 307 | ,{owner, ets:info(Tab, owner)}]}}; 308 | false -> 309 | {hard_error, {table_existence, []}} 310 | end. 311 | 312 | 313 | is_table(Tab) -> 314 | lists:member(Tab, ets:all()). -------------------------------------------------------------------------------- /src/director_table_list.erl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | %% @author Pouriya Jahanbakhsh 34 | %% @version 18.2.20 35 | %% @hidden 36 | %% ------------------------------------------------------------------------------------------------- 37 | 38 | 39 | -module(director_table_list). 40 | -author("pouriya.jahanbakhsh@gmail.com"). 41 | -behaviour(director_table). 42 | 43 | 44 | %% ------------------------------------------------------------------------------------------------- 45 | %% Exports: 46 | 47 | %% Director's API: 48 | -export([create/1 49 | ,insert/2 50 | ,delete/2 51 | ,lookup_id/2 52 | ,lookup_pid/2 53 | ,lookup_appended/1 54 | ,count/1 55 | ,delete_table/1 56 | ,tab2list/1 57 | ,handle_message/2 58 | ,change_parent/2]). 59 | 60 | %% ------------------------------------------------------------------------------------------------- 61 | %% Records & Macros & Includes: 62 | 63 | %% Dependencies: 64 | %% #?CHILD{} 65 | -include("internal/director_child.hrl"). 66 | 67 | %% ------------------------------------------------------------------------------------------------- 68 | %% API functions: 69 | 70 | create(_Arg) -> 71 | {ok, []}. 72 | 73 | 74 | delete_table(_Tab) -> 75 | ok. 76 | 77 | 78 | lookup_id(Tab, Id) -> 79 | case lists:keyfind(Id, 2, Tab) of 80 | false -> 81 | {soft_error, not_found}; 82 | Child -> 83 | {ok, Child} 84 | end. 85 | 86 | 87 | count(Tab) -> 88 | {ok, erlang:length(Tab)}. 89 | 90 | 91 | lookup_pid(Tab, Pid) -> 92 | case lists:keyfind(Pid, 3, Tab) of 93 | false -> 94 | {soft_error, not_found}; 95 | Child -> 96 | {ok, Child} 97 | end. 98 | 99 | 100 | lookup_appended(Tab) -> 101 | {ok, [Child || #?CHILD{append = Append}=Child <- Tab, Append == true]}. 102 | 103 | 104 | insert(Tab, #?CHILD{id = Id}=Child) -> 105 | case lists:keyfind(Id, 2, Tab) of 106 | false -> 107 | {ok, [Child|Tab]}; 108 | _ -> 109 | {value, _, Tab2} = lists:keytake(Id, 2, Tab), 110 | {ok, [Child|Tab2]} 111 | end. 112 | 113 | 114 | delete(Tab, Id) -> 115 | {ok, lists:keydelete(Id, 2, Tab)}. 116 | 117 | 118 | tab2list(Tab) -> 119 | {ok, Tab}. 120 | 121 | 122 | handle_message(_, _) -> 123 | {soft_error, unknown}. 124 | 125 | 126 | change_parent(Tab, #?CHILD{id = Id}=Child) -> 127 | case lists:keyfind(Id, 2, Tab) of 128 | false -> 129 | {ok, [Child|Tab]}; 130 | _ -> 131 | {value, _, Tab2} = lists:keytake(Id, 2, Tab), 132 | {ok, [Child|Tab2]} 133 | end. -------------------------------------------------------------------------------- /src/director_table_map.erl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | %% @author Pouriya Jahanbakhsh 34 | %% @version 18.2.29 35 | %% @hidden 36 | %% ------------------------------------------------------------------------------------------------- 37 | 38 | 39 | -module(director_table_map). 40 | -author("pouriya.jahanbakhsh@gmail.com"). 41 | -behaviour(director_table). 42 | 43 | 44 | %% ------------------------------------------------------------------------------------------------- 45 | %% Exports: 46 | 47 | %% Director's API: 48 | -export([create/1 49 | ,insert/2 50 | ,delete/2 51 | ,lookup_id/2 52 | ,lookup_pid/2 53 | ,lookup_appended/1 54 | ,count/1 55 | ,delete_table/1 56 | ,tab2list/1 57 | ,handle_message/2 58 | ,change_parent/2]). 59 | 60 | %% ------------------------------------------------------------------------------------------------- 61 | %% Records & Macros & Includes: 62 | 63 | %% Dependencies: 64 | %% #?CHILD{} 65 | -include("internal/director_child.hrl"). 66 | 67 | %% ------------------------------------------------------------------------------------------------- 68 | %% API functions: 69 | 70 | create(_Arg) -> 71 | {ok, {#{}, #{}}}. 72 | 73 | 74 | delete_table(_Tab) -> 75 | ok. 76 | 77 | 78 | lookup_id({Tab, _}, Id) -> 79 | try maps:get(Id, Tab) of 80 | Child -> 81 | {ok, Child} 82 | catch 83 | _:_ -> 84 | {soft_error, not_found} 85 | end. 86 | 87 | 88 | count({Tab, _}) -> 89 | {ok, erlang:map_size(Tab)}. 90 | 91 | 92 | lookup_pid({Tab, Pids}, Pid) -> 93 | try maps:get(Pid, Pids) of 94 | Id -> 95 | {ok, maps:get(Id, Tab)} 96 | catch 97 | _:_ -> 98 | {soft_error, not_found} 99 | end. 100 | 101 | 102 | lookup_appended({Tab, _}) -> 103 | LookupFun = 104 | fun(_, #?CHILD{append = Append}=Child, Acc) -> 105 | if 106 | Append -> 107 | [Child|Acc]; 108 | true -> 109 | Acc 110 | end 111 | end, 112 | {ok, maps:fold(LookupFun, [], Tab)}. 113 | 114 | 115 | insert({Tab, Pids}, #?CHILD{id = Id, pid = Pid}=Child) -> 116 | Tab2 = maps:put(Id, Child, Tab), 117 | if 118 | erlang:is_pid(Pid) -> 119 | {ok, {Tab2, maps:put(Pid, Id, Pids)}}; 120 | true -> 121 | {ok, {Tab2, maps:remove(Pid, Pids)}} 122 | end. 123 | 124 | 125 | delete({Tab, Pids}=Tab2, Id) -> 126 | case lookup_id(Tab2, Id) of 127 | {ok, #?CHILD{pid = Pid}} -> 128 | {ok, {maps:remove(Id, Tab), maps:remove(Pid, Pids)}}; 129 | _ -> 130 | {ok, {maps:remove(Id, Tab), Pids}} 131 | end. 132 | 133 | 134 | tab2list({Tab, _}) -> 135 | ToListFun = 136 | fun(_, Child, Acc) -> 137 | [Child|Acc] 138 | end, 139 | {ok, maps:fold(ToListFun, [], Tab)}. 140 | 141 | 142 | handle_message(_, _) -> 143 | {soft_error, unknown}. 144 | 145 | 146 | change_parent({Tab, Pids}, #?CHILD{id = Id, pid = Pid}=Child) -> 147 | Tab2 = maps:put(Id, Child, Tab), 148 | if 149 | erlang:is_pid(Pid) -> 150 | {ok, {Tab2, maps:put(Pid, Id, Pids)}}; 151 | true -> 152 | {ok, {Tab2, maps:remove(Pid, Pids)}} 153 | end. -------------------------------------------------------------------------------- /src/director_table_mnesia.erl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | %% @author Pouriya Jahanbakhsh 34 | %% @version 18.2.20 35 | %% @doc 36 | %% API functions for interacting with Mnesia table as backend children table. 37 | %% @end 38 | %% ------------------------------------------------------------------------------------------------- 39 | 40 | 41 | -module(director_table_mnesia). 42 | -author("pouriya.jahanbakhsh@gmail.com"). 43 | -behaviour(director_table). 44 | 45 | 46 | %% ------------------------------------------------------------------------------------------------- 47 | %% Exports: 48 | 49 | %% API: 50 | -export([count_children/1 51 | ,which_children/1 52 | ,get_childspec/2 53 | ,get_pid/2 54 | ,get_pids/1 55 | ,get_restart_count/2 56 | ,options/0]). 57 | 58 | %% director's API: 59 | -export([create/1 60 | ,insert/2 61 | ,delete/2 62 | ,lookup_id/2 63 | ,lookup_pid/2 64 | ,lookup_appended/1 65 | ,count/1 66 | ,delete_table/1 67 | ,tab2list/1 68 | ,handle_message/2 69 | ,change_parent/2]). 70 | 71 | %% ------------------------------------------------------------------------------------------------- 72 | %% Records & Macros & Includes: 73 | 74 | %% Dependencies: 75 | %% #?CHILD{} 76 | -include("internal/director_child.hrl"). 77 | 78 | -define(TABLE_OPTIONS, [{attributes, record_info(fields, ?CHILD)} 79 | ,{type, set} 80 | ,{record_name, ?CHILD}]). 81 | 82 | -define(is_valid_type(Type), (Type =:= set orelse Type =:= ordered_set)). 83 | 84 | %% ------------------------------------------------------------------------------------------------- 85 | %% API: 86 | 87 | -spec 88 | count_children(atom()) -> 89 | [{'specs', non_neg_integer()} 90 | |{'active', non_neg_integer()} 91 | |{'supervisors', non_neg_integer()} 92 | |{'workers', non_neg_integer()}] | 93 | {'error', term()}. 94 | %% @doc 95 | %% Returns count of children.
96 | %% Error is for reading from table. 97 | %% @end 98 | count_children(Tab) -> 99 | director_table:count_children(?MODULE, Tab). 100 | 101 | -spec 102 | which_children(atom()) -> 103 | [{director:id(), director:type(), pid()|'restarting'|'undefined', director:modules()}] | 104 | [] | 105 | {'error', term()}. 106 | %% @doc 107 | %% Returns information about each child.
108 | %% Error maybe occur for reading from table. 109 | %% @end 110 | which_children(Tab) -> 111 | director_table:which_children(?MODULE, Tab). 112 | 113 | -spec 114 | get_childspec(atom(), director:id() | pid()) -> 115 | {'ok', director:childspec()} | {'error', 'not_found' | term()}. 116 | %% @doc 117 | %% Returns childspec of child id or child pid.
118 | %% Error maybe occur for reading from table. 119 | %% @end 120 | get_childspec(Tab, Id) -> 121 | director_table:get_childspec(?MODULE, Tab, Id). 122 | 123 | -spec 124 | get_pid(atom(), director:id()) -> 125 | {'ok', pid()} | {'error', 'not_found'|'restarting'|'undefined'|term()}. 126 | %% @doc 127 | %% Returns pid of child id if child is running.
128 | %% Error maybe occur for reading from table. 129 | %% @end 130 | get_pid(Tab, Id) -> 131 | director_table:get_pid(?MODULE, Tab, Id). 132 | 133 | -spec 134 | get_pids(atom()) -> 135 | {'ok', [{director:id(), pid()}] | []} | {'error', term()}. 136 | %% @doc 137 | %% Returns list of {id, pid}s for all running children.
138 | %% Error is for reading from table. 139 | %% @end 140 | get_pids(Tab) -> 141 | director_table:get_pids(?MODULE, Tab). 142 | 143 | -spec 144 | get_restart_count(atom(), director:id()) -> 145 | {'ok', non_neg_integer()} | {'error', 'not_found'|term()}. 146 | %% @doc 147 | %% Returns restart count of child id.
148 | %% Error maybe occur for reading from table. 149 | %% @end 150 | get_restart_count(Tab, Id) -> 151 | director_table:get_restart_count(?MODULE, Tab, Id). 152 | 153 | -spec 154 | options() -> 155 | list(). 156 | %% @doc 157 | %% Returns mandatory options of Mnesia table for creating table for a director process. 158 | %% @end 159 | options() -> 160 | ?TABLE_OPTIONS. 161 | 162 | %% ------------------------------------------------------------------------------------------------- 163 | %% Director's API functions: 164 | 165 | %% @hidden 166 | create({value, TabName}) -> 167 | case is_table(TabName) of 168 | true -> 169 | case {mnesia:table_info(TabName, access_mode) 170 | ,mnesia:table_info(TabName, arity) 171 | ,mnesia:table_info(TabName, type)} of 172 | {read_write, Size, Type} when ?is_valid_type(Type) andalso 173 | erlang:tuple_size(#?CHILD{}) =:= Size -> 174 | _ = mnesia:subscribe(system), 175 | {ok, TabName}; 176 | {read_only, _, _} -> 177 | {hard_error, {table_info, [{access_mode, read_only}]}}; 178 | {_, Size, _} when erlang:tuple_size(#?CHILD{}) =:= Size -> 179 | {hard_error, {table_info, [{record_size, Size}]}}; 180 | {_, _, Type} -> 181 | {hard_error, {table_info, [{type, Type}]}} 182 | end; 183 | false -> 184 | try mnesia:create_table(TabName, ?TABLE_OPTIONS) of 185 | {atomic, ok} -> 186 | _ = mnesia:subscribe(system), 187 | {ok, TabName}; 188 | {aborted, Rsn} -> 189 | {hard_error, {table_create, [{reason, Rsn}]}} 190 | catch 191 | _:Reason -> 192 | {hard_error, {table_create, [{reason, Reason}]}} 193 | end; 194 | error -> 195 | {hard_error, {table_create, [{reason, not_started}]}} 196 | end. 197 | 198 | 199 | %% @hidden 200 | delete_table(Tab) -> 201 | try mnesia:delete_table(Tab) of 202 | {atomic, ok} -> 203 | _ = mnesia:unsubscribe(system), 204 | ok; 205 | {aborted, Rsn} -> 206 | {hard_error, {table_delete, [{reason, Rsn}]}} 207 | catch 208 | _:Rsn -> 209 | table_error(Tab, Rsn) 210 | end. 211 | 212 | 213 | %% @hidden 214 | lookup_id(Tab, Id) -> 215 | TA = 216 | fun() -> 217 | case mnesia:read(Tab, Id, read) of 218 | [Child] -> 219 | {ok, Child}; 220 | [] -> 221 | {soft_error, not_found} 222 | end 223 | end, 224 | transaction(Tab, TA). 225 | 226 | 227 | %% @hidden 228 | count(Tab) -> 229 | try 230 | {ok, mnesia:table_info(Tab, size)} 231 | catch 232 | _:Rsn -> 233 | table_error(Tab, Rsn) 234 | end. 235 | 236 | 237 | %% @hidden 238 | lookup_pid(Tab, Pid) -> 239 | TA = 240 | fun() -> 241 | case mnesia:select(Tab, [{#?CHILD{pid = '$1', _='_'}, [{'=:=', '$1', Pid}], ['$_']}]) of 242 | [Child] -> 243 | {ok, Child}; 244 | [] -> 245 | {soft_error, not_found} 246 | end 247 | end, 248 | transaction(Tab, TA). 249 | 250 | 251 | %% @hidden 252 | lookup_appended(Tab) -> 253 | TA = 254 | fun() -> 255 | {ok 256 | ,mnesia:select(Tab, [{#?CHILD{append = '$1', _='_'}, [{'=:=', '$1', true}], ['$_']}])} 257 | end, 258 | transaction(Tab, TA). 259 | 260 | 261 | %% @hidden 262 | insert(Tab, Child) -> 263 | TA = 264 | fun() -> 265 | mnesia:write(Tab, Child, write), 266 | {ok, Tab} 267 | end, 268 | transaction(Tab, TA). 269 | 270 | 271 | %% @hidden 272 | delete(Tab, Id) -> 273 | TA = 274 | fun() -> 275 | case mnesia:read(Tab, Id, read) of 276 | [Child] -> 277 | mnesia:delete_object(Tab, Child, write), 278 | {ok, Tab}; 279 | [] -> 280 | {soft_error, not_found} 281 | end 282 | end, 283 | transaction(Tab, TA). 284 | 285 | 286 | %% @hidden 287 | tab2list(Tab) -> 288 | TA = 289 | fun() -> 290 | Fold = 291 | fun(Child, Acc) -> 292 | [Child|Acc] 293 | end, 294 | {ok, mnesia:foldl(Fold, [], Tab)} 295 | end, 296 | transaction(Tab, TA). 297 | 298 | 299 | %% @hidden 300 | handle_message(Tab, {mnesia_system_event, {mnesia_down, Node}}) -> 301 | TA = 302 | fun() -> 303 | Fold = 304 | fun 305 | (#?CHILD{supervisor = Sup 306 | ,delete = false 307 | ,pid = Pid}=Child 308 | ,{ShouldRestart, ShouldDelete}) when erlang:node(Sup) =:= Node andalso 309 | erlang:is_pid(Pid) -> 310 | {[Child|ShouldRestart], ShouldDelete}; 311 | (#?CHILD{supervisor = Sup, delete = true}=Child 312 | ,{ShouldRestart, ShouldDelete}) when erlang:node(Sup) =:= Node -> 313 | {ShouldRestart, [Child|ShouldDelete]}; 314 | (_, Acc) -> 315 | Acc 316 | end, 317 | mnesia:foldl(Fold, {[], []}, Tab) 318 | end, 319 | case transaction(Tab, TA) of 320 | {hard_error, _}=HErr -> 321 | HErr; 322 | {[], []} -> 323 | {ok, Tab}; 324 | {ShouldRestart, ShouldDelete} -> 325 | DeleteTA = 326 | fun(Child) -> 327 | fun () -> 328 | ok = mnesia:delete_object(Tab, Child, write) 329 | end 330 | end, 331 | DeleteTAs = [DeleteTA(Child) || Child <- ShouldDelete], 332 | case transactions(Tab, DeleteTAs) of 333 | {ok, _} -> 334 | ShouldRestartTA = 335 | fun(#?CHILD{id = Id}=Child) -> 336 | TimerPid = director:self_start(Id), 337 | fun() -> 338 | ok = mnesia:write(Tab 339 | ,Child#?CHILD{supervisor = undefined 340 | ,timer= TimerPid 341 | ,pid = undefined 342 | ,extra = undefined} 343 | ,write) 344 | end 345 | end, 346 | ShouldRestartTAs = [ShouldRestartTA(Child) || Child <- ShouldRestart], 347 | transactions(Tab, ShouldRestartTAs); 348 | {hard_error, _}=HErr -> 349 | HErr 350 | end 351 | end; 352 | handle_message(Tab, {mnesia_system_event, _}) -> 353 | {ok, Tab}; 354 | handle_message(_, _) -> 355 | {soft_error, unknown}. 356 | 357 | 358 | %% @hidden 359 | change_parent(Tab, #?CHILD{id = Id}=Child) -> 360 | Self = erlang:self(), 361 | TA = 362 | fun() -> 363 | case mnesia:read(Tab, Id, write) of 364 | [#?CHILD{supervisor = Pid}] when Pid =/= Self andalso Pid =/= undefined -> 365 | {soft_error, Tab, not_parent}; 366 | _ -> 367 | _ = mnesia:write(Tab, Child, write), 368 | {ok, Tab} 369 | end 370 | end, 371 | transaction(Tab, TA). 372 | 373 | %% ------------------------------------------------------------------------------------------------- 374 | %% Internal functions: 375 | 376 | table_error(Tab, Rsn) -> 377 | case is_table(Tab) of 378 | true -> 379 | {hard_error, {table_info, [{access_mode, mnesia:table_info(Tab, access_mode)} 380 | ,{arity, mnesia:table_info(Tab, arity)} 381 | ,{type, mnesia:table_info(Tab, type)}]}}; 382 | false -> 383 | {hard_error, {table_existence, []}}; 384 | error -> 385 | {hard_error, {table_error, [{reason, Rsn}]}} 386 | end. 387 | 388 | 389 | is_table(Tab) -> 390 | try 391 | lists:member(Tab, mnesia:system_info(tables)) 392 | catch 393 | _:_ -> 394 | error 395 | end. 396 | 397 | 398 | transaction(Tab, TA) -> 399 | try mnesia:transaction(TA) of 400 | {atomic, Rslt} -> 401 | Rslt; 402 | {aborted, Rsn} -> 403 | {hard_error, {table_transaction, [{reason, Rsn}]}} 404 | catch 405 | _:Rsn -> 406 | table_error(Tab, Rsn) 407 | end. 408 | 409 | transactions(Tab, [TA|TAs]) -> 410 | case transaction(Tab, TA) of 411 | {hard_error, _}=HErr -> 412 | HErr; 413 | _ -> 414 | transactions(Tab, TAs) 415 | end; 416 | transactions(Tab, []) -> 417 | {ok, Tab}. -------------------------------------------------------------------------------- /src/director_utils.erl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | %% @author Pouriya Jahanbakhsh 34 | %% @version 18.2.20 35 | %% @hidden 36 | %% ------------------------------------------------------------------------------------------------- 37 | -module(director_utils). 38 | -author("pouriya.jahanbakhsh@gmail.com"). 39 | %% ------------------------------------------------------------------------------------------------- 40 | %% Exports: 41 | 42 | %% API: 43 | -export([concat/2 44 | ,proper/2 45 | ,option/4 46 | ,value/3 47 | ,has_duplicate/1]). 48 | 49 | %% ------------------------------------------------------------------------------------------------- 50 | %% Records & Macros & Includes: 51 | 52 | -include("internal/director_child.hrl"). 53 | -include("internal/director_defaults.hrl"). 54 | 55 | %% ------------------------------------------------------------------------------------------------- 56 | %% Functions: 57 | 58 | concat(List1, List2) -> 59 | proper(proper(List1, []), lists:reverse(proper(List2, []))). 60 | 61 | 62 | option(Key, Opts, Filter, Def) -> 63 | case lists:keyfind(Key, 1, Opts) of 64 | {_, Val} -> 65 | Filter(Val); 66 | false -> 67 | Def 68 | end. 69 | 70 | 71 | value(Key, List, Def) -> 72 | case lists:keyfind(Key, 1, List) of 73 | {_, Val} -> 74 | Val; 75 | _ -> % {Key,..., ...} & false 76 | Def 77 | end. 78 | 79 | 80 | has_duplicate(L) -> 81 | has_duplicate(L, []). 82 | 83 | 84 | proper([H|T], Ret) when erlang:is_list(T) -> 85 | proper(T, [H | Ret]); 86 | proper([H|T], Ret) -> 87 | proper([], [T, H | Ret]); 88 | proper([], Ret) -> 89 | Ret. 90 | 91 | %% ------------------------------------------------------------------------------------------------- 92 | %% Internal functions: 93 | 94 | has_duplicate([Id|Ids], Ids2) -> 95 | case lists:member(Id, Ids2) of 96 | true -> 97 | {true, Id}; 98 | false -> 99 | has_duplicate(Ids, [Id|Ids2]) 100 | end; 101 | has_duplicate(_, _) -> % ([], Ids2) 102 | false. -------------------------------------------------------------------------------- /test/director_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | %% @author Pouriya Jahanbakhsh 34 | %% @version 18.4.29 35 | %% ------------------------------------------------------------------------------------------------- 36 | -module(director_SUITE). 37 | -author("pouriya.jahanbakhsh@gmail.com"). 38 | %% ------------------------------------------------------------------------------------------------- 39 | %% Exports: 40 | 41 | %% ct callbacks: 42 | -export([init_per_suite/1 43 | ,end_per_suite/1 44 | ,all/0 45 | ,init_per_testcase/2 46 | ,end_per_testcase/2]). 47 | 48 | -export(['1'/1 49 | ,'2'/1 50 | ,'3'/1 51 | ,'4'/1 52 | ,'5'/1 53 | ,'6'/1 54 | ,'7'/1 55 | ,'8'/1 56 | ,'9'/1 57 | ,'10'/1 58 | ,'11'/1 59 | ,'12'/1]). 60 | 61 | %% ------------------------------------------------------------------------------------------------- 62 | %% Records & Macros & Includes: 63 | 64 | -define(DIRECTOR, director_name). 65 | -define(CHILD, child_name). 66 | -define(CALLBACK, director_callback). 67 | -define(CHILD_MODULE, director_child_). 68 | -define(TAB_NAME, director_table). 69 | -define(TAB, ets). 70 | -define(DB_OPTS, [{table, ?TAB}, {init_arg, ?TAB_NAME}]). 71 | -define(START_OPTIONS, [{debug, [trace]} 72 | ,{db, ?DB_OPTS}]). 73 | 74 | -include_lib("common_test/include/ct.hrl"). 75 | -include_lib("eunit/include/eunit.hrl"). 76 | 77 | %% ------------------------------------------------------------------------------------------------- 78 | %% ct callbacks: 79 | 80 | 81 | all() -> 82 | [erlang:list_to_atom(erlang:integer_to_list(Int)) 83 | || Int <- lists:seq(1, erlang:length(?MODULE:module_info(exports))-8)]. 84 | 85 | 86 | init_per_suite(Config) -> 87 | application:start(sasl), 88 | application:start(mnesia), 89 | Config. 90 | 91 | 92 | end_per_suite(Config) -> 93 | application:stop(sasl), 94 | application:stop(mnesia), 95 | Config. 96 | 97 | 98 | init_per_testcase(_TestCase, Config) -> 99 | erlang:process_flag(trap_exit, true), 100 | Config. 101 | 102 | 103 | end_per_testcase(_TestCase, _Config) -> 104 | catch erlang:exit(erlang:whereis(?DIRECTOR), kill), 105 | director_test_utils:flush_handle_returns(), 106 | catch mnesia:delete_table(?TAB_NAME), 107 | ok. 108 | 109 | %% ------------------------------------------------------------------------------------------------- 110 | %% Test cases: 111 | 112 | 113 | '1'(_Config) -> 114 | InitArg = erlang:self(), 115 | Start = 116 | fun() -> 117 | erlang:process_flag(trap_exit, true), 118 | InitArg ! director:start_link({local, ?DIRECTOR}, ?CALLBACK, InitArg) 119 | end, 120 | 121 | spawn_link(Start), 122 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {stop, foo} end), 123 | ?assertEqual(ok, receive {error, foo} -> ok after 500 -> error end), 124 | 125 | spawn_link(Start), 126 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> ignore end), 127 | ?assertEqual(ok, receive ignore -> ok after 500 -> error end), 128 | 129 | spawn_link(Start), 130 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> bar end), 131 | ?assertEqual(ok, receive {error, {return, [{value, bar}|_]}} -> ok after 500 -> error end), 132 | 133 | spawn_link(Start), 134 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> fun() -> erlang:exit(baz) end end), 135 | ?assertEqual(ok, receive {error, {crash, [{reason, baz}|_]}} -> ok after 500 -> error end), 136 | 137 | spawn_link(Start), 138 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg} end), 139 | StartResult = receive {ok, _}=Ok -> Ok after 500 -> error end, 140 | ?assertEqual(StartResult, {ok, erlang:whereis(?DIRECTOR)}), 141 | Stop = 142 | fun(Rsn) -> 143 | ?assertEqual(ok, director:stop(?DIRECTOR, Rsn, 500)) 144 | end, 145 | spawn_link(Stop), 146 | director_test_utils:handle_return(?CALLBACK, terminate, fun([normal, Arg]) when Arg =:= InitArg -> ok end), 147 | timer:sleep(50), 148 | ?assertEqual(undefined, erlang:whereis(?DIRECTOR)), 149 | 150 | spawn_link(Start), 151 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, []} end), 152 | StartResult2 = receive {ok, _}=Ok2 -> Ok2 after 500 -> error end, 153 | ?assertEqual(StartResult2, {ok, erlang:whereis(?DIRECTOR)}), 154 | spawn_link(Stop), 155 | director_test_utils:handle_return(?CALLBACK, terminate, fun([normal, Arg]) when Arg =:= InitArg -> ok end), 156 | timer:sleep(50), 157 | ?assertEqual(undefined, erlang:whereis(?DIRECTOR)), 158 | 159 | spawn_link(Start), 160 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [], #{}} end), 161 | StartResult3 = receive {ok, _}=Ok3 -> Ok3 after 500 -> error end, 162 | ?assertEqual(StartResult3, {ok, erlang:whereis(?DIRECTOR)}), 163 | spawn_link(Stop), 164 | director_test_utils:handle_return(?CALLBACK, terminate, fun([normal, Arg]) when Arg =:= InitArg -> ok end), 165 | timer:sleep(50), 166 | ?assertEqual(undefined, erlang:whereis(?DIRECTOR)), 167 | 168 | spawn_link(Start), 169 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [], []} end), 170 | StartResult4 = receive {ok, _}=Ok4 -> Ok4 after 500 -> error end, 171 | ?assertEqual(StartResult4, {ok, erlang:whereis(?DIRECTOR)}), 172 | spawn_link(Stop), 173 | director_test_utils:handle_return(?CALLBACK, terminate, fun([normal, Arg]) when Arg =:= InitArg -> ok end), 174 | timer:sleep(50), 175 | ?assertEqual(undefined, erlang:whereis(?DIRECTOR)), 176 | 177 | spawn_link(Start), 178 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [], #{}, []} end), 179 | StartResult5 = receive {ok, _}=Ok5 -> Ok5 after 500 -> error end, 180 | ?assertEqual(StartResult5, {ok, erlang:whereis(?DIRECTOR)}), 181 | spawn_link(Stop), 182 | director_test_utils:handle_return(?CALLBACK, terminate, fun([normal, Arg]) when Arg =:= InitArg -> ok end), 183 | timer:sleep(50), 184 | ?assertEqual(undefined, erlang:whereis(?DIRECTOR)). 185 | 186 | 187 | '2'(_Config) -> 188 | InitArg = erlang:self(), 189 | Id = foo, 190 | ChState = child_state, 191 | Mods = [?CHILD_MODULE], 192 | ChildSpec = #{id => Id 193 | ,start => {?CHILD_MODULE, start_link, [{local, ?CHILD}, fun() -> {ok, undefined} end]} 194 | ,terminate_timeout => 0 195 | ,modules => Mods 196 | ,append => false 197 | ,type => worker 198 | ,state => ChState 199 | ,delete => true}, 200 | 201 | ?assertEqual(ok, director:check_childspec(ChildSpec)), 202 | 203 | Start = 204 | fun() -> 205 | InitArg ! director:start_link({local, ?DIRECTOR}, ?CALLBACK, InitArg), 206 | receive after infinity -> ok end 207 | end, 208 | erlang:spawn_link(Start), 209 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [ChildSpec], ?START_OPTIONS} end), 210 | director_test_utils:handle_return(?CALLBACK 211 | ,handle_start 212 | ,fun([Id2, ChState2, State2, #{pid := Pid, restart_count := 0}]) when Id2 =:= Id andalso 213 | ChState2 =:= ChState andalso 214 | State2 =:= InitArg andalso 215 | erlang:is_pid(Pid) -> 216 | {ok, ChState2, State2, [{log, true}]} 217 | end), 218 | StartResult = receive {ok, _}=Ok -> Ok after 500 -> error end, 219 | ?assertEqual(StartResult, {ok, erlang:whereis(?DIRECTOR)}), 220 | ?assertEqual({ok, ChildSpec}, director:get_childspec(?DIRECTOR, Id)), 221 | 222 | ?assertMatch([{Id, _Pid, worker, Mods}], director:which_children(?DIRECTOR)), 223 | 224 | count_children(?DIRECTOR, 1,1,1,0), 225 | 226 | ?assertMatch({ok, _Pid}, director:get_pid(?DIRECTOR, Id)), 227 | 228 | ?assertMatch({ok, [{Id, _Pid}]}, director:get_pids(?DIRECTOR)), 229 | 230 | ?assertEqual({error, running}, director:delete_child(?DIRECTOR, Id)), 231 | 232 | ?assert(erlang:is_process_alive(erlang:whereis(?CHILD))), 233 | 234 | Exit = 235 | fun(Rsn) -> 236 | {ok, PidX} = director:get_pid(?DIRECTOR, Id), 237 | erlang:exit(PidX, Rsn) 238 | end, 239 | Exit(kill), 240 | director_test_utils:handle_return(?CALLBACK 241 | ,handle_exit 242 | ,fun([Id2, ChState2, killed, State2, #{restart_count := 1}]) when Id2 =:= Id andalso 243 | ChState2 =:= ChState andalso 244 | State2 =:= InitArg -> 245 | {restart, ChState2, State2, [{log, true}]} 246 | end), 247 | director_test_utils:handle_return(?CALLBACK 248 | ,handle_start 249 | ,fun([Id2, ChState2, State2, #{pid := Pid, restart_count := 1}]) when Id2 =:= Id andalso 250 | ChState2 =:= ChState andalso 251 | State2 =:= InitArg andalso 252 | erlang:is_pid(Pid) -> 253 | {ok, ChState2, State2, [{log, true}]} 254 | end), 255 | timer:sleep(50), 256 | ?assertMatch({ok, _Pid}, director:get_pid(?DIRECTOR, Id)), 257 | 258 | Exit(oops), 259 | director_test_utils:handle_return(?CALLBACK 260 | ,handle_exit 261 | ,fun([Id2, ChState2, oops, State2, #{restart_count := 2}]) when Id2 =:= Id andalso 262 | ChState2 =:= ChState andalso 263 | State2 =:= InitArg -> 264 | {{restart, 500}, ChState2, State2, [{log, true}]} 265 | end), 266 | timer:sleep(50), 267 | ?assertEqual([{Id, restarting, worker, [?CHILD_MODULE]}], director:which_children(?DIRECTOR)), 268 | timer:sleep(50), 269 | director_test_utils:handle_return(?CALLBACK 270 | ,handle_start 271 | ,fun([Id2, ChState2, State2, #{pid := Pid, restart_count := 2}]) when Id2 =:= Id andalso 272 | ChState2 =:= ChState andalso 273 | State2 =:= InitArg andalso 274 | erlang:is_pid(Pid) -> 275 | {ok, ChState2, State2, [{log, true}]} 276 | end), 277 | timer:sleep(50), 278 | ?assertMatch({ok, _Pid}, director:get_pid(?DIRECTOR, Id)), 279 | 280 | Exit(aah), 281 | director_test_utils:handle_return(?CALLBACK 282 | ,handle_exit 283 | ,fun([Id2, ChState2, aah, State2, #{restart_count := 3}]) when Id2 =:= Id andalso 284 | ChState2 =:= ChState andalso 285 | State2 =:= InitArg -> 286 | {wait, ChState2, State2, [{log, true}]} 287 | end), 288 | timer:sleep(50), 289 | ?assertEqual([{Id, undefined, worker, [?CHILD_MODULE]}], director:which_children(?DIRECTOR)), 290 | erlang:spawn_link(fun() -> ?assertMatch({ok, _}, director:restart_child(?DIRECTOR, Id)), receive after infinity -> ok end end), 291 | director_test_utils:handle_return(?CALLBACK 292 | ,handle_start 293 | ,fun([Id2, ChState2, State2, #{pid := Pid, restart_count := 3}]) when Id2 =:= Id andalso 294 | ChState2 =:= ChState andalso 295 | State2 =:= InitArg andalso 296 | erlang:is_pid(Pid) -> 297 | {ok, ChState2, State2, [{log, true}]} 298 | end), 299 | timer:sleep(50), 300 | ?assertMatch({ok, _Pid}, director:get_pid(?DIRECTOR, Id)), 301 | 302 | Exit(aah), 303 | director_test_utils:handle_return(?CALLBACK 304 | ,handle_exit 305 | ,fun([Id2, ChState2, aah, State2, #{restart_count := 4}]) when Id2 =:= Id andalso 306 | ChState2 =:= ChState andalso 307 | State2 =:= InitArg -> 308 | {delete, ChState2, State2, [{log, true}]} 309 | end), 310 | timer:sleep(50), 311 | ?assertEqual([], director:which_children(?DIRECTOR)), 312 | ?assertEqual({error, not_found}, director:get_pid(?DIRECTOR, Id)), 313 | 314 | erlang:spawn_link(fun() -> ?assertMatch({ok, _}, director:start_child(?DIRECTOR, ChildSpec)), receive after infinity -> ok end end), 315 | director_test_utils:handle_return(?CALLBACK 316 | ,handle_start 317 | ,fun([Id2, ChState2, State2, #{pid := Pid, restart_count := 0}]) when Id2 =:= Id andalso 318 | ChState2 =:= ChState andalso 319 | State2 =:= InitArg andalso 320 | erlang:is_pid(Pid) -> 321 | {ok, ChState2, State2, [{log, true}]} 322 | end), 323 | 324 | Exit(bye), 325 | director_test_utils:handle_return(?CALLBACK 326 | ,handle_exit 327 | ,fun([Id2, ChState2, bye, State2, #{restart_count := 1}]) when Id2 =:= Id andalso 328 | ChState2 =:= ChState andalso 329 | State2 =:= InitArg -> 330 | {stop, ChState2, State2, [{log, true}]} 331 | end), 332 | director_test_utils:handle_return(?CALLBACK, terminate, fun([bye, Arg]) when Arg =:= InitArg -> ok end), 333 | timer:sleep(50), 334 | ?assertEqual(ok, receive {'EXIT', _, bye} -> ok after 500 -> error end), 335 | ?assertEqual(undefined, erlang:whereis(?DIRECTOR)), 336 | 337 | erlang:spawn_link(Start), 338 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [ChildSpec], ?START_OPTIONS} end), 339 | director_test_utils:handle_return(?CALLBACK 340 | ,handle_start 341 | ,fun([Id2, ChState2, State2, #{pid := Pid, restart_count := 0}]) when Id2 =:= Id andalso 342 | ChState2 =:= ChState andalso 343 | State2 =:= InitArg andalso 344 | erlang:is_pid(Pid) -> 345 | {ok, ChState2, State2, [{log, true}]} 346 | end), 347 | Exit(shutup), 348 | director_test_utils:handle_return(?CALLBACK 349 | ,handle_exit 350 | ,fun([Id2, ChState2, shutup, State2, #{restart_count := 1}]) when Id2 =:= Id andalso 351 | ChState2 =:= ChState andalso 352 | State2 =:= InitArg -> 353 | {{stop, '_'}, ChState2, State2, [{log, true}]} 354 | end), 355 | director_test_utils:handle_return(?CALLBACK, terminate, fun(['_', Arg]) when Arg =:= InitArg -> ok end), 356 | timer:sleep(50), 357 | ?assertEqual(ok, receive {'EXIT', _, '_'} -> ok after 500 -> error end), 358 | ?assertEqual(undefined, erlang:whereis(?DIRECTOR)). 359 | 360 | 361 | 362 | 363 | '3'(_Config) -> 364 | InitArg = erlang:self(), 365 | Start = {?CHILD_MODULE, start_link, [{local, ?CHILD}, fun() -> {ok, undefined} end]}, 366 | Mods = [?CHILD_MODULE], 367 | DefChildSpec = #{start => Start 368 | ,terminate_timeout => 1000 369 | ,modules => Mods}, 370 | StartDir = 371 | fun() -> 372 | InitArg ! director:start_link({local, ?DIRECTOR}, ?CALLBACK, InitArg), 373 | receive after infinity -> ok end 374 | end, 375 | erlang:spawn_link(StartDir), 376 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [], DefChildSpec} end), 377 | ?assertEqual(DefChildSpec, director:get_default_childspec(?DIRECTOR)), 378 | Id = foo, 379 | ChildSpec = #{id => Id, append => true, modules => []}, 380 | erlang:spawn(fun() -> ?assertMatch({ok, _Pid}, director:start_child(?DIRECTOR, ChildSpec)) end), 381 | director_test_utils:handle_return(?CALLBACK 382 | ,handle_start 383 | ,fun([Id2, ChState2, State2, #{pid := Pid, restart_count := 0}]) when Id2 =:= Id andalso 384 | ChState2 =:= undefined andalso 385 | State2 =:= InitArg andalso 386 | erlang:is_pid(Pid) -> 387 | {ok, ChState2, State2, [{log, true}]} 388 | end), 389 | 390 | {ok, ChildSpec2} = director:get_childspec(?DIRECTOR, Id), 391 | ?assertEqual(Start, maps:get(start, ChildSpec2)), 392 | ?assertEqual(2000, maps:get(terminate_timeout, ChildSpec2)), 393 | ?assertEqual(Mods, maps:get(modules, ChildSpec2)), 394 | 395 | Start2 = {?CHILD_MODULE, start_link, [fun() -> {ok, undefined} end]}, 396 | DefChildSpec2 = #{start => Start2, modules => [?CHILD_MODULE]}, 397 | ?assertEqual(ok, director:change_default_childspec(?DIRECTOR, DefChildSpec2)), 398 | {ok, ChildSpec3} = director:get_childspec(?DIRECTOR, Id), 399 | ?assertEqual(Start2, maps:get(start, ChildSpec3)), 400 | ?assertEqual(1000, maps:get(terminate_timeout, ChildSpec3)), 401 | ?assertEqual([?CHILD_MODULE], maps:get(modules, ChildSpec3)). 402 | 403 | 404 | '4'(_config) -> 405 | InitArg = erlang:self(), 406 | Id = foo, 407 | F = fun() -> {ok, undefined} end, 408 | ChildSpec = #{id => Id, start => {?CHILD_MODULE, start_link, [{local, ?CHILD}, F]}}, 409 | ChildSpec2 = #{id => Id 410 | ,start => {?CHILD_MODULE, start_link, [{local, ?CHILD}, F]} 411 | ,terminate_timeout => 1000 412 | ,modules => [?CHILD_MODULE] 413 | ,append => false 414 | ,type => worker 415 | ,state => undefined 416 | ,delete => true}, 417 | StartDir = 418 | fun() -> 419 | InitArg ! director:start_link({local, ?DIRECTOR}, ?CALLBACK, InitArg), 420 | receive after infinity -> ok end 421 | end, 422 | erlang:spawn_link(StartDir), 423 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [ChildSpec]} end), 424 | director_test_utils:handle_return(?CALLBACK 425 | ,handle_start 426 | ,fun([Id2, ChState2, State2, #{pid := Pid, restart_count := 0}]) when Id2 =:= Id andalso 427 | ChState2 =:= undefined andalso 428 | State2 =:= InitArg andalso 429 | erlang:is_pid(Pid) -> 430 | {ok, ChState2, State2, [{log, true}]} 431 | end), 432 | {ok, ChildSpec3} = director:get_childspec(?DIRECTOR, Id), 433 | ?assertEqual(ChildSpec2, ChildSpec3), 434 | Id2 = bar, 435 | ChildSpec4 = #{id => Id2 436 | ,start => {?CHILD_MODULE, start_link, [F]} 437 | ,type => supervisor}, 438 | erlang:spawn(fun() -> ?assertMatch({ok, _Pid}, director:start_child(?DIRECTOR, ChildSpec4)) end), 439 | director_test_utils:handle_return(?CALLBACK 440 | ,handle_start 441 | ,fun([Id3, ChState2, State2, #{pid := Pid, restart_count := 0}]) when Id3 =:= Id2 andalso 442 | ChState2 =:= undefined andalso 443 | State2 =:= InitArg andalso 444 | erlang:is_pid(Pid) -> 445 | {ok, ChState2, State2, [{log, true}]} 446 | end), 447 | {ok, ChildSpec5} = director:get_childspec(?DIRECTOR, Id2), 448 | ?assertEqual(infinity, maps:get(terminate_timeout, ChildSpec5)). 449 | 450 | 451 | '5'(Config) -> 452 | Src = "-module(director_callback2).\n" 453 | "-export([init/1\n" 454 | " ,terminate/2\n" 455 | " ,handle_start/4\n" 456 | " ,handle_terminate/5]).\n\n" 457 | "init(_InitArg) ->\n" 458 | " {ok\n" 459 | " ,undefined\n" 460 | " ,~s}.\n\n\n" 461 | "handle_terminate(_, ChState, _, State, _) ->\n" 462 | " {ok, ChState, State, [{log, true}]}.\n\n\n" 463 | "handle_start(foo, undefined, undefined, #{pid := Pid, restart_count := 0}) when erlang:is_pid(Pid) ->\n" 464 | " {ok, undefined, undefined, [{log, true}]}~s\n\n\n" 465 | "terminate(_, _) ->\n" 466 | " ok.\n", 467 | 468 | Callback = director_callback2, 469 | Id1 = foo, 470 | ChildMod1 = director_child_, 471 | Children1 = io_lib:format("[#{id => ~s\n" 472 | " ,start => {~s, start_link, [fun() -> {ok, undefined} end]}}]" 473 | ,[Id1, ChildMod1]), 474 | Src1 = io_lib:format(Src, [Children1, "."]), 475 | ct:pal("Source 1:\n~s\n", [Src1]), 476 | 477 | Dir = filename:join(?config(data_dir, Config), "src"), 478 | code:add_patha(Dir), 479 | File = filename:join([Dir, erlang:atom_to_list(Callback) ++ ".erl"]), 480 | 481 | ?assertEqual(ok, file:write_file(File, Src1)), 482 | ?assertEqual({ok, Callback}, compile:file(File, [return_errors, {outdir, Dir}])), 483 | ?assertMatch({ok, _Pid}, director:start_link({local, ?DIRECTOR} 484 | ,Callback 485 | ,undefined 486 | ,?START_OPTIONS)), 487 | ?assertMatch({ok, [{foo, _}]}, director:get_pids(?DIRECTOR)), 488 | 489 | Id2 = bar, 490 | ChildMod2 = director_child_, 491 | Children2 = io_lib:format("[#{id => ~s\n" 492 | " ,start => {~s, start_link, [fun() -> {ok, undefined} end]}}\n" 493 | ",#{id => ~s\n" 494 | " ,start => {~s, start_link, [fun() -> {ok, undefined} end]}}]" 495 | ,[Id1, ChildMod1, Id2, ChildMod2]), 496 | HandleStartClause2 = ";\nhandle_start(bar, undefined, undefined, #{pid := Pid, restart_count := 0}) when erlang:is_pid(Pid) ->\n" 497 | " {ok, undefined, undefined, [{log, true}]}.", 498 | Src2 = io_lib:format(Src, [Children2, HandleStartClause2]), 499 | ct:pal("Source 2:\n~s\n", [Src2]), 500 | 501 | ?assertEqual(ok, sys:suspend(?DIRECTOR)), 502 | 503 | ?assertEqual(ok, file:write_file(File, Src2)), 504 | ?assertEqual({ok, Callback}, compile:file(File, [return_errors, {outdir, Dir}])), 505 | ?assertEqual({module, Callback}, c:l(Callback)), 506 | 507 | ?assertEqual(ok, sys:change_code(?DIRECTOR, Callback, undefined, undefined)), 508 | ?assertEqual(ok, sys:resume(?DIRECTOR)), 509 | ct:pal("Childrens:\n~p\n", [director:which_children(?DIRECTOR)]), 510 | ?assertMatch({ok, _}, director:restart_child(?DIRECTOR, Id2)), 511 | director:stop(?DIRECTOR), 512 | file:delete(File). 513 | 514 | 515 | '6'(_Config) -> 516 | InitArg = erlang:self(), 517 | Tab = mnesia, 518 | TabOpts = [{table, Tab}, {init_arg, ?TAB_NAME}], 519 | Id = foo, 520 | F = fun() -> {ok, undefined} end, 521 | ChildSpec = #{id => Id, start => {?CHILD_MODULE, start_link, [{local, ?CHILD}, F]}}, 522 | StartDir = 523 | fun() -> 524 | InitArg ! director:start_link({local, ?DIRECTOR}, ?CALLBACK, InitArg), 525 | receive after infinity -> ok end 526 | end, 527 | erlang:spawn_link(StartDir), 528 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [ChildSpec], [{db, TabOpts}|?START_OPTIONS]} end), 529 | director_test_utils:handle_return(?CALLBACK 530 | ,handle_start 531 | ,fun([Id2, ChState2, State2, #{pid := Pid, restart_count := 0}]) when Id2 =:= Id andalso 532 | ChState2 =:= undefined andalso 533 | State2 =:= InitArg andalso 534 | erlang:is_pid(Pid) -> 535 | {ok, ChState2, State2, [{log, true}]} 536 | end), 537 | ?assertEqual(ok, receive {ok, _} -> ok after 500 -> error end), 538 | StartDir2 = 539 | fun() -> 540 | InitArg ! director:start_link({local, director_name_2}, ?CALLBACK, InitArg, [{db, TabOpts}|?START_OPTIONS]), 541 | receive after infinity -> ok end 542 | end, 543 | erlang:spawn_link(StartDir2), 544 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [ChildSpec], [{db, TabOpts}|?START_OPTIONS]} end), 545 | ?assertEqual(ok, receive {ok, _} -> ok after 500 -> error end), 546 | Pid = erlang:whereis(?CHILD), 547 | ?assertEqual({ok, Pid}, director:get_pid(?DIRECTOR, Id)), 548 | ?assertEqual({ok, Pid}, director:get_pid(director_name_2, Id)), 549 | erlang:spawn(fun() -> ?assertEqual(ok, director:terminate_and_delete_child(?DIRECTOR, Id)) end), 550 | director_test_utils:handle_return(?CALLBACK, handle_terminate, fun([foo, undefined, shutdown, _, _]) -> {ok, undefined, undefined, [{log, true}]} end), 551 | timer:sleep(50), 552 | ?assertEqual({error, not_found}, director:get_pid(director_name_2, Id)). 553 | 554 | 555 | '7'(_Config) -> 556 | InitArg = erlang:self(), 557 | Tab = mnesia, 558 | TabOpts = [{table, Tab}, {init_arg, ?TAB_NAME}, {delete, false}], 559 | Id = foo, 560 | F = fun() -> {ok, undefined} end, 561 | ChildSpec1 = #{id => Id 562 | ,start => {?CHILD_MODULE, start_link, [F]} 563 | ,delete => false}, 564 | Id2 = bar, 565 | ChildSpec2 = #{id => Id2 566 | ,start => {?CHILD_MODULE, start_link, [F]} 567 | ,delete => true}, 568 | StartDir = 569 | fun() -> 570 | InitArg ! director:start_link(?CALLBACK 571 | ,InitArg 572 | ,[{db, TabOpts}|?START_OPTIONS]), 573 | receive after infinity -> ok end 574 | end, 575 | erlang:spawn_link(StartDir), 576 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [ChildSpec1]} end), 577 | director_test_utils:handle_return(?CALLBACK 578 | ,handle_start 579 | ,fun([Id3, ChState2, State2, #{pid := Pid, restart_count := 0}]) when Id3 =:= Id andalso 580 | ChState2 =:= undefined andalso 581 | State2 =:= InitArg andalso 582 | erlang:is_pid(Pid) -> 583 | {ok, ChState2, State2, [{log, true}]} 584 | end), 585 | Res1 = 586 | receive 587 | Msg -> 588 | Msg 589 | end, 590 | ?assertMatch({ok, _}, Res1), 591 | Pid1 = erlang:element(2, Res1), 592 | 593 | erlang:spawn_link(StartDir), 594 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [ChildSpec1, ChildSpec2]} end), 595 | director_test_utils:handle_return(?CALLBACK 596 | ,handle_start 597 | ,fun([Id3, ChState2, State2, #{pid := Pid, restart_count := 0}]) when Id3 =:= Id2 andalso 598 | ChState2 =:= undefined andalso 599 | State2 =:= InitArg andalso 600 | erlang:is_pid(Pid) -> 601 | {ok, ChState2, State2, [{log, true}]} 602 | end), 603 | 604 | Res2 = 605 | receive 606 | Msg2 -> 607 | Msg2 608 | end, 609 | ?assertMatch({ok, _}, Res2), 610 | Pid2 = erlang:element(2, Res2), 611 | 612 | ?assertMatch({ok, _Pid}, director:get_pid(Pid1, Id)), 613 | ResGetPid1 = director:get_pid(Pid1, Id), 614 | ?assertMatch({ok, _Pid}, director:get_pid(Pid2, Id)), 615 | ResGetPid2 = director:get_pid(Pid2, Id), 616 | ?assertEqual(ResGetPid1, ResGetPid2), 617 | erlang:spawn_link(fun() -> ?assertEqual(ok, director:stop(Pid1)) end), 618 | director_test_utils:handle_return(?CALLBACK, handle_terminate, fun([foo, undefined, shutdown, _, _]) -> {ok, undefined, undefined, [{log, true}]} end), 619 | director_test_utils:handle_return(?CALLBACK, terminate, fun([normal, _]) -> {ok, [{log, true}]} end), 620 | ?assertEqual({error, undefined}, director:get_pid(Pid2, Id)), 621 | ?assertMatch({ok, _}, director:get_pid(Pid2, Id2)), 622 | erlang:spawn_link(StartDir), 623 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [ChildSpec1]} end),Res3 = 624 | receive 625 | {ok, _}=Msg3 -> 626 | Msg3 627 | end, 628 | ?assertMatch({ok, _}, Res3), 629 | Pid3 = erlang:element(2, Res3), 630 | 631 | ?assertEqual({error, undefined}, director:get_pid(Pid3, Id)), 632 | ?assertMatch({ok, _}, director:get_pid(Pid3, Id2)), 633 | 634 | erlang:spawn_link(fun() -> ?assertMatch({ok, _Pid}, director:restart_child(Pid3, Id)) end), 635 | director_test_utils:handle_return(?CALLBACK 636 | ,handle_start 637 | ,fun([Id3, ChState2, State2, #{pid := Pid, restart_count := 0}]) when Id3 =:= Id andalso 638 | ChState2 =:= undefined andalso 639 | State2 =:= InitArg andalso 640 | erlang:is_pid(Pid) -> 641 | {ok, ChState2, State2, [{log, true}]} 642 | end), 643 | Res4 = director:get_pid(Pid2, Id), 644 | ?assertMatch({ok, _Pid}, Res4), 645 | ?assert(erlang:is_pid(erlang:element(2, Res4))). 646 | 647 | 648 | '8'(_Config) -> 649 | InitArg = erlang:self(), 650 | StartDir = 651 | fun() -> 652 | InitArg ! director:start_link({local, ?DIRECTOR}, ?CALLBACK, InitArg, ?START_OPTIONS), 653 | receive after infinity -> ok end 654 | end, 655 | erlang:spawn_link(StartDir), 656 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg} end), 657 | ?CALLBACK = supervisor:get_callback_module(?DIRECTOR). 658 | 659 | 660 | '9'(_Cfg) -> 661 | InitArg = erlang:self(), 662 | StartDir = 663 | fun() -> 664 | InitArg ! director:start_link({local, ?DIRECTOR}, ?CALLBACK, InitArg, ?START_OPTIONS), 665 | receive after infinity -> ok end 666 | end, 667 | erlang:spawn_link(StartDir), 668 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg} end), 669 | 670 | F = fun() -> {ok, undefined} end, 671 | {ok, ChPid} = ?CHILD_MODULE:start_link(F), 672 | Id = foo, 673 | ChildSpec1 = #{id => Id, start => {?CHILD_MODULE, start_link, [F]}}, 674 | ?assertEqual(ok, director:become_supervisor(?DIRECTOR, ChPid, ChildSpec1)), 675 | ?assertEqual({ok, ChPid}, director:get_pid(?DIRECTOR, Id)), 676 | erlang:exit(ChPid, kill), 677 | director_test_utils:handle_return(?CALLBACK 678 | ,handle_exit 679 | ,fun([Id2, ChState2, killed, State2, #{restart_count := 1}]) when Id2 =:= Id andalso 680 | ChState2 =:= undefined andalso 681 | State2 =:= InitArg -> 682 | {restart, ChState2, State2, [{log, true}]} 683 | end), 684 | director_test_utils:handle_return(?CALLBACK 685 | ,handle_start 686 | ,fun([Id2, ChState2, State2, #{pid := Pid, restart_count := 1}]) when Id2 =:= Id andalso 687 | ChState2 =:= undefined andalso 688 | State2 =:= InitArg andalso 689 | erlang:is_pid(Pid) -> 690 | {ok, ChState2, State2, [{log, true}]} 691 | end), 692 | timer:sleep(50), 693 | ?assertMatch({ok, _}, director:get_pid(?DIRECTOR, Id)), 694 | 695 | ?assertEqual({error, noproc}, director:become_supervisor(?DIRECTOR, ChPid, ChildSpec1)), 696 | {ok, ChPid2} = director:get_pid(?DIRECTOR, Id), 697 | ?assertEqual({error, {already_started, ChPid2}}, director:become_supervisor(?DIRECTOR, ChPid2, ChildSpec1)), 698 | erlang:spawn_link(fun() -> ?assertEqual(ok, director:terminate_child(?DIRECTOR, Id)) end), 699 | director_test_utils:handle_return(?CALLBACK, handle_terminate, fun([foo, undefined, shutdown, _, _]) -> {ok, undefined, undefined, [{log, true}]} end), 700 | {ok, ChPid3} = ?CHILD_MODULE:start_link(F), 701 | ?assertEqual({error, {already_present, Id}}, director:become_supervisor(?DIRECTOR, ChPid3, ChildSpec1)), 702 | ?assertEqual({error, {duplicate_child_name, Id}}, director:become_supervisor(?DIRECTOR, ChPid3, ChildSpec1#{start => ?CHILD_MODULE})), 703 | ok. 704 | 705 | 706 | '10'(_Cfg) -> 707 | InitArg = erlang:self(), 708 | F = fun() -> {ok, undefined} end, 709 | Id = foo, 710 | ChildSpec1 = #{id => Id, start => {?CHILD_MODULE, start_link, [F]}}, 711 | StartDir = 712 | fun() -> 713 | InitArg ! director:start_link({local, ?DIRECTOR}, ?CALLBACK, InitArg, ?START_OPTIONS), 714 | receive after infinity -> ok end 715 | end, 716 | erlang:spawn_link(StartDir), 717 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [ChildSpec1]} end), 718 | director_test_utils:handle_return(?CALLBACK 719 | ,handle_start 720 | ,fun([Id2, ChState2, State2, #{pid := Pid, restart_count := 0}]) when Id2 =:= Id andalso 721 | ChState2 =:= undefined andalso 722 | State2 =:= InitArg andalso 723 | erlang:is_pid(Pid) -> 724 | {ok, ChState2, State2, [{log, true}]} 725 | end), 726 | {ok, ChPid} = director:get_pid(?DIRECTOR, Id), 727 | ?assertEqual(ok, director:delete_running_child(?DIRECTOR, ChPid)), 728 | timer:sleep(50), 729 | ?assertEqual({error, not_found}, director:get_pid(?DIRECTOR, Id)). 730 | 731 | 732 | '11'(_Cfg) -> 733 | InitArg = erlang:self(), 734 | Id = foo, 735 | StartDir = 736 | fun() -> 737 | InitArg ! director:start_link({local, ?DIRECTOR}, ?CALLBACK, InitArg, ?START_OPTIONS), 738 | receive after infinity -> ok end 739 | end, 740 | erlang:spawn_link(StartDir), 741 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg} end), 742 | 743 | {ok, []} = director:get_pids(?DIRECTOR), 744 | ?assertEqual({error, not_found}, director:terminate_child(?DIRECTOR, Id)), 745 | ?assertEqual({error, not_found}, director:delete_running_child(?DIRECTOR, erlang:self())), 746 | ?assertEqual({error, not_found}, director:restart_child(?DIRECTOR, Id)). 747 | 748 | 749 | '12'(_Cfg) -> 750 | InitArg = erlang:self(), 751 | F = 752 | fun() -> 753 | InitArg ! erlang:self(), 754 | receive 755 | M -> 756 | M 757 | end 758 | end, 759 | Id = foo, 760 | ChildSpec1 = #{id => Id, start => {?CHILD_MODULE, start_link, [F]}}, 761 | StartDir = 762 | fun() -> 763 | InitArg ! director:start_link({local, ?DIRECTOR}, ?CALLBACK, InitArg, ?START_OPTIONS), 764 | receive after infinity -> ok end 765 | end, 766 | erlang:spawn_link(StartDir), 767 | director_test_utils:handle_return(?CALLBACK, init, fun([Arg]) when Arg =:= InitArg -> {ok, InitArg, [ChildSpec1]} end), 768 | receive 769 | Pid -> 770 | Pid ! {ok, undefined} 771 | end, 772 | director_test_utils:handle_return(?CALLBACK 773 | ,handle_start 774 | ,fun([Id2, ChState2, State2, #{pid := Pid2, restart_count := 0}]) when Id2 =:= Id andalso 775 | ChState2 =:= undefined andalso 776 | State2 =:= InitArg andalso 777 | erlang:is_pid(Pid2) -> 778 | {ok, ChState2, State2, [{log, true}]} 779 | end), 780 | erlang:spawn_link(fun() -> director:terminate_child(?DIRECTOR, Id) end), 781 | director_test_utils:handle_return(?CALLBACK 782 | ,handle_terminate 783 | ,fun([Id2, ChState2, shutdown, State2, #{restart_count := 0}]) when Id2 =:= Id andalso 784 | ChState2 =:= undefined andalso 785 | State2 =:= InitArg -> 786 | {ok, ChState2, State2, [{log, true}]} 787 | end), 788 | erlang:spawn_link(fun() -> director:restart_child(?DIRECTOR, Id) end), 789 | receive 790 | Pid2 when erlang:is_pid(Pid2) -> 791 | io:format("Pid: ~p~n", [Pid2]), 792 | Pid2 ! {stop, oops} 793 | end, 794 | timer:sleep(10), 795 | ?assertEqual({error, undefined}, director:get_pid(?DIRECTOR, Id)), 796 | ok. 797 | 798 | 799 | %% ------------------------------------------------------------------------------------------------- 800 | %% Internal functions: 801 | 802 | count_children(Director, Specs, Actives, Workers, Sups) -> 803 | CountChildren = director:count_children(Director), 804 | 805 | ?assertEqual(Specs, ?config(specs, CountChildren)), 806 | ?assertEqual(Actives, ?config(active, CountChildren)), 807 | ?assertEqual(Workers, ?config(workers, CountChildren)), 808 | ?assertEqual(Sups, ?config(supervisors, CountChildren)). -------------------------------------------------------------------------------- /test/director_SUITE_data/src/director_callback.erl: -------------------------------------------------------------------------------- 1 | -module(director_callback). 2 | -export([init/1 3 | ,handle_start/4 4 | ,handle_terminate/5 5 | ,handle_exit/5 6 | ,terminate/2]). 7 | 8 | 9 | 10 | 11 | init(InitArg) -> 12 | director_test_utils:make_return(InitArg, ?MODULE, init, [InitArg]). 13 | 14 | handle_start(Id, ChState, State, MetaData) -> 15 | director_test_utils:make_return(State, ?MODULE, handle_start, [Id, ChState, State, MetaData]). 16 | 17 | 18 | handle_exit(Id, ChState, Rsn, State, MetaData) -> 19 | director_test_utils:make_return(State, ?MODULE, handle_exit, [Id, ChState, Rsn, State, MetaData]). 20 | 21 | handle_terminate(Id, ChState, Rsn, State, MetaData) -> 22 | director_test_utils:make_return(State, ?MODULE, handle_terminate, [Id, ChState, Rsn, State, MetaData]). 23 | 24 | terminate(Rsn, State) -> 25 | director_test_utils:make_return(State, ?MODULE, terminate, [Rsn, State]). -------------------------------------------------------------------------------- /test/director_SUITE_data/src/director_child_.erl: -------------------------------------------------------------------------------- 1 | -module(director_child_). 2 | -export([start_link/1 3 | ,start_link/2 4 | ,init/1 5 | ,handle_info/2 6 | ,terminate/2 7 | ,code_change/3]). 8 | 9 | 10 | 11 | 12 | 13 | 14 | start_link(Arg) -> 15 | gen_server:start_link(?MODULE, Arg, []). 16 | 17 | 18 | start_link(Name, Arg) -> 19 | gen_server:start_link(Name, ?MODULE, Arg, []). 20 | 21 | 22 | 23 | init(Arg) -> 24 | Arg(). 25 | 26 | 27 | handle_info(_, St) -> 28 | {noreply, St}. 29 | 30 | 31 | terminate(_Reason, _State) -> 32 | ok. 33 | 34 | 35 | 36 | 37 | code_change(_, State, _) -> 38 | {ok, State}. -------------------------------------------------------------------------------- /test/director_table_.erl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | %% @author Pouriya Jahanbakhsh 34 | %% @version 18.4.29 35 | %% ------------------------------------------------------------------------------------------------- 36 | -module(director_table_). 37 | -author("pouriya.jahanbakhsh@gmail.com"). 38 | %% ------------------------------------------------------------------------------------------------- 39 | %% Exports: 40 | 41 | -export(['1'/2 42 | ,'2'/2 43 | ,'3'/2 44 | ,'4'/2 45 | ,'5'/2 46 | ,'6'/2 47 | ,'7'/2 48 | ,'8'/2]). 49 | 50 | %% ------------------------------------------------------------------------------------------------- 51 | %% Records & Macros & Includes: 52 | 53 | -define(M, 'director_table'). 54 | 55 | -include_lib("common_test/include/ct.hrl"). 56 | -include_lib("eunit/include/eunit.hrl"). 57 | -include("internal/director_child.hrl"). 58 | 59 | %% ------------------------------------------------------------------------------------------------- 60 | 61 | '1'(Mod, InitArg) -> 62 | TabState = create(Mod, InitArg), 63 | count(Mod, TabState, 0), 64 | delete_table(Mod, TabState). 65 | 66 | 67 | '2'(Mod, InitArg) -> 68 | TabState = create(Mod, InitArg), 69 | Count = 100, 70 | Children = [#?CHILD{id = Int, pid = Int} || Int <- lists:seq(1, Count)], 71 | Fold = 72 | fun(Child, TabState2) -> 73 | insert(Mod, TabState2, Child) 74 | end, 75 | TabState2 = lists:foldl(Fold, TabState, Children), 76 | count(Mod, TabState2, Count), 77 | 78 | Fold2 = 79 | fun(#?CHILD{id = Id2}=Child, TabState3) -> 80 | lookup_id(Mod, TabState3, Id2, Child), 81 | delete(Mod, TabState3, Child) 82 | end, 83 | TabState3 = lists:foldl(Fold2, TabState2, Children), 84 | count(Mod, TabState3, 0). 85 | 86 | 87 | '3'(Mod, InitArg) -> 88 | TabState = create(Mod, InitArg), 89 | Count = 10, 90 | Children = [#?CHILD{id = Int, pid = pid(Int)} || Int <- lists:seq(1, Count)], 91 | Fold = 92 | fun(Child, TabState2) -> 93 | insert(Mod, TabState2, Child) 94 | end, 95 | TabState2 = lists:foldl(Fold, TabState, Children), 96 | count(Mod, TabState2, Count), 97 | 98 | Fold2 = 99 | fun(#?CHILD{pid = Pid2}=Child, TabState3) -> 100 | lookup_pid(Mod, TabState3, Pid2, Child), 101 | delete(Mod, TabState3, Child) 102 | end, 103 | TabState3 = lists:foldl(Fold2, TabState2, Children), 104 | count(Mod, TabState3, 0). 105 | 106 | 107 | 108 | '4'(Mod, InitArg) -> 109 | [Child1, Child2, Child3] = Children = [#?CHILD{id = 1}, #?CHILD{id = 2}, #?CHILD{id = 3}], 110 | TabState = create(Mod, InitArg), 111 | Fold = 112 | fun(Child, TabState2) -> 113 | insert(Mod, TabState2, Child) 114 | end, 115 | TabState2 = lists:foldl(Fold, TabState, Children), 116 | count(Mod, TabState2, 3), 117 | Rslt = ?M:tab2list(Mod, TabState2), 118 | ?assertMatch({ok, _}, Rslt), 119 | {ok, List} = Rslt, 120 | ?assert(erlang:is_list(List)), 121 | ?assert(erlang:length(List) =:= 3), 122 | ?assertEqual(Child1, lists:keyfind(1, 2, List)), 123 | ?assertEqual(Child2, lists:keyfind(2, 2, List)), 124 | ?assertEqual(Child3, lists:keyfind(3, 2, List)). 125 | 126 | 127 | '5'(Mod, InitArg) -> 128 | TabState = create(Mod, InitArg), 129 | Count = 10, 130 | Children = [#?CHILD{id = id, append = false, supervisor = erlang:self(), modules = []} 131 | |[#?CHILD{id = Int 132 | ,append = true 133 | ,modules = [] 134 | ,supervisor = erlang:self() 135 | ,pid = pid(Int)} 136 | || Int <- lists:seq(1, Count)]], 137 | Fold = 138 | fun(Child, TabState2) -> 139 | insert(Mod, TabState2, Child) 140 | end, 141 | TabState2 = lists:foldl(Fold, TabState, Children), 142 | 143 | Val = [?MODULE], 144 | TabState3 = combine(Mod, TabState2, #{modules => Val}), 145 | 146 | Foreach = 147 | fun(Id) -> 148 | lookup_id(Mod, TabState3, Id, #?CHILD{id = Id, modules = Val, append = true, restart_count = 0, supervisor = erlang:self()}) 149 | end, 150 | lists:foreach(Foreach, lists:seq(1, Count)), 151 | lookup_id(Mod, TabState3, id, #?CHILD{id = id, append = false, supervisor = erlang:self(), modules = []}), 152 | 153 | TabState4 = separate(Mod, TabState3, #{modules => Val}), 154 | 155 | Foreach2 = 156 | fun(Id) -> 157 | lookup_id(Mod, TabState4, Id, #?CHILD{id = Id, append = true, modules = [], restart_count = 0, supervisor = erlang:self()}) 158 | end, 159 | lists:foreach(Foreach2, lists:seq(1, Count)), 160 | lookup_id(Mod, TabState4, id, #?CHILD{id = id, append = false, supervisor = erlang:self(), modules = []}). 161 | 162 | 163 | '6'(Mod, InitArg) -> 164 | TabState = create(Mod, InitArg), 165 | Count = 10, 166 | Ids = lists:seq(1, Count), 167 | Children = [#?CHILD{id = Int, pid = pid(Int)} || Int <- Ids], 168 | Fold = 169 | fun(Child, TabState2) -> 170 | insert(Mod, TabState2, Child) 171 | end, 172 | TabState2 = lists:foldl(Fold, TabState, Children), 173 | {ok, Pids} = director_table:get_pids(Mod, TabState2), 174 | Fun = 175 | fun(X) -> 176 | ?assertEqual(true, lists:member({X, pid(X)}, Pids)) 177 | end, 178 | lists:foreach(Fun, Ids). 179 | 180 | 181 | '7'(Mod, InitArg) -> 182 | TabState = create(Mod, InitArg), 183 | Count = 10, 184 | Ids = lists:seq(1, Count), 185 | Children = [#?CHILD{id = Int, pid = pid(Int)} || Int <- Ids], 186 | Fold = 187 | fun(Child, TabState2) -> 188 | insert(Mod, TabState2, Child) 189 | end, 190 | TabState2 = lists:foldl(Fold, TabState, Children), 191 | Fun = 192 | fun(X) -> 193 | Pid = pid(X), 194 | ?assertEqual({ok, Pid}, director_table:get_pid(Mod, TabState2, X)) 195 | end, 196 | lists:foreach(Fun, Ids). 197 | 198 | '8'(Mod, InitArg) -> 199 | TabState = create(Mod, InitArg), 200 | Count = 10, 201 | Ids = lists:seq(1, Count), 202 | Children = [#?CHILD{id = Int 203 | ,type = if 204 | Int rem 2 == 0 -> 205 | worker; 206 | true -> 207 | supervisor 208 | end 209 | ,append = true 210 | ,modules = [] 211 | ,supervisor = erlang:self() 212 | ,pid = pid(Int) 213 | ,restart_count = Int} 214 | || Int <- Ids], 215 | Fold = 216 | fun(Child, TabState2) -> 217 | insert(Mod, TabState2, Child) 218 | end, 219 | TabState2 = lists:foldl(Fold, TabState, Children), 220 | CountChildrenRes = Mod:count_children(TabState2), 221 | ?assert(erlang:is_list(CountChildrenRes)), 222 | ?assertEqual(Count div 2, proplists:get_value(workers, CountChildrenRes)), 223 | ?assertEqual(Count div 2, proplists:get_value(supervisors, CountChildrenRes)), 224 | ?assertEqual(Count, proplists:get_value(active, CountChildrenRes)), 225 | ?assertEqual(Count, proplists:get_value(specs, CountChildrenRes)), 226 | 227 | WhichChildren = Mod:which_children(TabState2), 228 | ?assert(erlang:is_list(WhichChildren)), 229 | WhichChildrenFun = 230 | fun(X) -> 231 | {_, Pid, Type, Mods} = lists:keyfind(X, 1, WhichChildren), 232 | ?assertEqual(Pid, pid(X)), 233 | ?assert(Type == worker orelse Type == supervisor), 234 | ?assertEqual([], Mods) 235 | end, 236 | lists:foreach(WhichChildrenFun, Ids), 237 | 238 | GetChildSpecFun = 239 | fun(X) -> 240 | ?assertMatch({ok, #{id := X}}, Mod:get_childspec(TabState2, X)) 241 | end, 242 | lists:foreach(GetChildSpecFun, Ids), 243 | 244 | GetRestartCountFun = 245 | fun(X) -> 246 | ?assertMatch({ok, X}, Mod:get_restart_count(TabState2, X)) 247 | end, 248 | lists:foreach(GetRestartCountFun, Ids), 249 | 250 | {ok, Pids} = Mod:get_pids(TabState2), 251 | GetPidsFun = 252 | fun(X) -> 253 | ?assert(lists:member({X, pid(X)}, Pids)) 254 | end, 255 | lists:foreach(GetPidsFun, Ids), 256 | 257 | GetPidFun = 258 | fun(X) -> 259 | ?assertEqual({ok, pid(X)}, Mod:get_pid(TabState2, X)) 260 | end, 261 | lists:foreach(GetPidFun, Ids), 262 | 263 | 264 | ok. 265 | 266 | 267 | 268 | 269 | %% ------------------------------------------------------------------------------------------------- 270 | 271 | create(Mod, InitArg) -> 272 | Res = ?M:create(Mod, InitArg), 273 | ?assertMatch({ok, _}, Res), 274 | erlang:element(2, Res). 275 | 276 | 277 | count(Mod, TabState, Count) -> 278 | Rslt = ?M:count(Mod, TabState), 279 | ?assertEqual({ok, Count}, Rslt), 280 | erlang:element(2, Rslt). 281 | 282 | 283 | delete_table(Mod, TabState) -> 284 | ?assertEqual(ok, ?M:delete_table(Mod, TabState)). 285 | 286 | 287 | insert(Mod, TabState, Child) -> 288 | Res = ?M:insert(Mod, TabState, Child), 289 | ?assertMatch({ok, _}, Res), 290 | erlang:element(2, Res). 291 | 292 | 293 | lookup_id(Mod, TabState, Id, Rslt) -> 294 | Res = ?M:lookup_id(Mod, TabState, Id), 295 | ?assertEqual({ok, Rslt}, Res), 296 | Rslt. 297 | 298 | 299 | lookup_pid(Mod, TabState, Pid, Rslt) -> 300 | Res = ?M:lookup_pid(Mod, TabState, Pid), 301 | ?assertEqual({ok, Rslt}, Res), 302 | Rslt. 303 | 304 | 305 | delete(Mod, TabState, Child) -> 306 | Res = ?M:delete(Mod, TabState, Child), 307 | ?assertMatch({ok, _}, Res), 308 | erlang:element(2, Res). 309 | 310 | 311 | combine(Mod, TabState, DefChildSpec) -> 312 | Res = ?M:combine_children(Mod, TabState, DefChildSpec), 313 | ?assertMatch({ok, _}, Res), 314 | erlang:element(2, Res). 315 | 316 | 317 | separate(Mod, TabState, DefChildSpec) -> 318 | Res = ?M:separate_children(Mod, TabState, DefChildSpec), 319 | ?assertMatch({ok, _}, Res), 320 | erlang:element(2, Res). 321 | 322 | 323 | pid(Int) -> 324 | erlang:list_to_pid("<0." ++ erlang:integer_to_list(Int) ++ ".0>"). -------------------------------------------------------------------------------- /test/director_table_ets_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | %% @author Pouriya Jahanbakhsh 34 | %% @version 18.1.1 35 | %% ------------------------------------------------------------------------------------------------- 36 | -module(director_table_ets_SUITE). 37 | -author("pouriya.jahanbakhsh@gmail.com"). 38 | %% ------------------------------------------------------------------------------------------------- 39 | %% Exports: 40 | 41 | %% ct callbacks: 42 | -export([init_per_suite/1 43 | ,end_per_suite/1 44 | ,all/0 45 | ,init_per_testcase/2 46 | ,end_per_testcase/2]). 47 | 48 | -export(['1'/1 49 | ,'2'/1 50 | ,'3'/1 51 | ,'4'/1 52 | ,'5'/1 53 | ,'6'/1 54 | ,'7'/1 55 | ,'8'/1 56 | ,'9'/1 57 | ,'10'/1]). 58 | 59 | %% ------------------------------------------------------------------------------------------------- 60 | %% Records & Macros & Includes: 61 | 62 | -define(TAB_MOD, 'director_table_ets'). 63 | -define(TAB_NAME, director_table_name). 64 | -define(TAB_INIT_ARG, {value, ?TAB_NAME}). 65 | -define(TESTER_MODULE, 'director_table_'). 66 | 67 | -include_lib("common_test/include/ct.hrl"). 68 | -include_lib("eunit/include/eunit.hrl"). 69 | 70 | %% ------------------------------------------------------------------------------------------------- 71 | %% ct callbacks: 72 | 73 | all() -> 74 | [erlang:list_to_atom(erlang:integer_to_list(Int)) 75 | || Int <- lists:seq(1, erlang:length(?MODULE:module_info(exports))-8)]. 76 | 77 | 78 | init_per_suite(Config) -> 79 | application:start(sasl), 80 | Config. 81 | 82 | 83 | end_per_suite(Config) -> 84 | application:stop(sasl), 85 | Config. 86 | 87 | 88 | init_per_testcase(_TestCase, Config) -> 89 | Config. 90 | 91 | 92 | end_per_testcase(_TestCase, _Config) -> 93 | catch ets:delete(?TAB_NAME), 94 | ok. 95 | 96 | %% ------------------------------------------------------------------------------------------------- 97 | 98 | '1'(_) -> 99 | ?TESTER_MODULE:'1'(?TAB_MOD, ?TAB_INIT_ARG). 100 | 101 | 102 | '2'(_) -> 103 | ?TESTER_MODULE:'2'(?TAB_MOD, ?TAB_INIT_ARG). 104 | 105 | 106 | '3'(_) -> 107 | ?TESTER_MODULE:'3'(?TAB_MOD, ?TAB_INIT_ARG). 108 | 109 | 110 | '4'(_) -> 111 | ?TESTER_MODULE:'4'(?TAB_MOD, ?TAB_INIT_ARG). 112 | 113 | 114 | '5'(_) -> 115 | ?TESTER_MODULE:'5'(?TAB_MOD, ?TAB_INIT_ARG). 116 | 117 | 118 | '6'(_) -> 119 | ?TESTER_MODULE:'6'(?TAB_MOD, ?TAB_INIT_ARG). 120 | 121 | 122 | '7'(_) -> 123 | ?TESTER_MODULE:'7'(?TAB_MOD, ?TAB_INIT_ARG). 124 | 125 | 126 | '8'(_) -> 127 | ?TESTER_MODULE:'8'(?TAB_MOD, ?TAB_INIT_ARG). 128 | 129 | 130 | '9'(_) -> 131 | ?assert(erlang:is_list(?TAB_MOD:options())). 132 | 133 | 134 | '10'(_) -> 135 | ets:new(?TAB_NAME, ?TAB_MOD:options()), 136 | ?assertMatch({ok, _}, ?TAB_MOD:create({value, ?TAB_NAME})), 137 | ets:delete(?TAB_NAME), 138 | 139 | ets:new(?TAB_NAME, [public, named_table, {keypos, 2}]), 140 | ?assertMatch({ok, _}, ?TAB_MOD:create({value, ?TAB_NAME})), 141 | ets:delete(?TAB_NAME), 142 | 143 | ets:new(?TAB_NAME, [protected, named_table, {keypos, 2}]), 144 | ?assertMatch({ok, _}, ?TAB_MOD:create({value, ?TAB_NAME})), 145 | ets:delete(?TAB_NAME), 146 | 147 | timer:sleep(30), 148 | Creator = erlang:spawn_link(fun() -> ets:new(?TAB_NAME, [protected, named_table]), receive _ -> ets:delete(?TAB_NAME) end end), 149 | timer:sleep(30), 150 | ?assertMatch({hard_error, {table_info, [{protection, _}|_]}}, ?TAB_MOD:create({value, ?TAB_NAME})), 151 | Creator ! done, 152 | 153 | timer:sleep(30), 154 | Creator2 = erlang:spawn_link(fun() -> ets:new(?TAB_NAME, [public, named_table]), receive _ -> ets:delete(?TAB_NAME) end end), 155 | timer:sleep(30), 156 | ?assertMatch({hard_error, {table_info, [{keypos, _}|_]}}, ?TAB_MOD:create({value, ?TAB_NAME})), 157 | Creator2 ! done, 158 | 159 | timer:sleep(30), 160 | Creator3 = erlang:spawn_link(fun() -> ets:new(?TAB_NAME, [public, named_table, bag, {keypos,2}]), receive _ -> ets:delete(?TAB_NAME) end end), 161 | timer:sleep(30), 162 | ?assertMatch({hard_error, {table_info, [{type, _}|_]}}, ?TAB_MOD:create({value, ?TAB_NAME})), 163 | Creator3 ! done, 164 | timer:sleep(30), 165 | 166 | ets:new(?TAB_NAME, [protected, named_table]), 167 | ?assertMatch({hard_error, {table_info, [{keypos, _}|_]}}, ?TAB_MOD:create({value, ?TAB_NAME})), 168 | ets:delete(?TAB_NAME), 169 | 170 | ets:new(?TAB_NAME, [protected, named_table, {keypos, 2}, bag]), 171 | ?assertMatch({hard_error, {table_info, [{type, _}|_]}}, ?TAB_MOD:create({value, ?TAB_NAME})), 172 | ets:delete(?TAB_NAME), 173 | 174 | ?assertMatch({hard_error, {table_existence, _}}, ?TAB_MOD:delete_table(?TAB_NAME)), 175 | ets:new(?TAB_NAME, [public, named_table, {keypos, 2}]), 176 | timer:sleep(30), 177 | ?assertMatch(ok, ?TAB_MOD:delete_table(?TAB_NAME)), 178 | 179 | ?assertMatch({hard_error, {table_existence, _}}, ?TAB_MOD:lookup_id(?TAB_NAME, foo)), 180 | ?assertMatch({hard_error, {table_existence, _}}, ?TAB_MOD:lookup_pid(?TAB_NAME, foo)), 181 | 182 | Creator4 = erlang:spawn_link(fun() -> ets:new(?TAB_NAME, [private, named_table]), receive _ -> ets:delete(?TAB_NAME) end end), 183 | timer:sleep(30), 184 | ?assertMatch({hard_error, {table_info, [{protection, _}|_]}}, ?TAB_MOD:lookup_id(?TAB_NAME, foo)), 185 | Creator4 ! done, 186 | timer:sleep(30), 187 | 188 | 189 | 190 | %% ets:new(?TAB_NAME, [?TAB_MOD:options()]), 191 | %% ?assertMatch({ok, _}, ?TAB_MOD:create({value, ?TAB_NAME})), 192 | %% ets:delete(?TAB_NAME), 193 | %% 194 | %% ets:new(?TAB_NAME, [?TAB_MOD:options()]), 195 | %% ?assertMatch({ok, _}, ?TAB_MOD:create({value, ?TAB_NAME})), 196 | %% ets:delete(?TAB_NAME), 197 | ok. 198 | 199 | -------------------------------------------------------------------------------- /test/director_table_list_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | %% @author Pouriya Jahanbakhsh 34 | %% @version 18.1.1 35 | %% ------------------------------------------------------------------------------------------------- 36 | -module(director_table_list_SUITE). 37 | -author("pouriya.jahanbakhsh@gmail.com"). 38 | %% ------------------------------------------------------------------------------------------------- 39 | %% Exports: 40 | 41 | %% ct callbacks: 42 | -export([init_per_suite/1 43 | ,end_per_suite/1 44 | ,all/0 45 | ,init_per_testcase/2 46 | ,end_per_testcase/2]). 47 | 48 | -export(['1'/1 49 | ,'2'/1 50 | ,'3'/1 51 | ,'4'/1 52 | ,'5'/1 53 | ,'6'/1 54 | ,'7'/1]). 55 | 56 | %% ------------------------------------------------------------------------------------------------- 57 | %% Records & Macros & Includes: 58 | 59 | -define(TAB_MOD, 'director_table_list'). 60 | -define(TAB_INIT_ARG, 'undefined'). 61 | -define(TESTER_MODULE, 'director_table_'). 62 | 63 | %% ------------------------------------------------------------------------------------------------- 64 | %% ct callbacks: 65 | 66 | all() -> 67 | [erlang:list_to_atom(erlang:integer_to_list(Int)) 68 | || Int <- lists:seq(1, erlang:length(?MODULE:module_info(exports))-7)]. 69 | 70 | 71 | init_per_suite(Config) -> 72 | application:start(sasl), 73 | Config. 74 | 75 | 76 | end_per_suite(Config) -> 77 | application:stop(sasl), 78 | Config. 79 | 80 | 81 | init_per_testcase(_TestCase, Config) -> 82 | erlang:process_flag(trap_exit, true), 83 | Config. 84 | 85 | 86 | end_per_testcase(_TestCase, _Config) -> 87 | ok. 88 | 89 | %% ------------------------------------------------------------------------------------------------- 90 | 91 | '1'(_Config) -> 92 | ?TESTER_MODULE:'1'(?TAB_MOD, ?TAB_INIT_ARG). 93 | 94 | 95 | '2'(_Config) -> 96 | ?TESTER_MODULE:'2'(?TAB_MOD, ?TAB_INIT_ARG). 97 | 98 | 99 | '3'(_Config) -> 100 | ?TESTER_MODULE:'3'(?TAB_MOD, ?TAB_INIT_ARG). 101 | 102 | 103 | '4'(_Config) -> 104 | ?TESTER_MODULE:'4'(?TAB_MOD, ?TAB_INIT_ARG). 105 | 106 | 107 | '5'(_Config) -> 108 | ?TESTER_MODULE:'5'(?TAB_MOD, ?TAB_INIT_ARG). 109 | 110 | 111 | '6'(_Config) -> 112 | ?TESTER_MODULE:'6'(?TAB_MOD, ?TAB_INIT_ARG). 113 | 114 | 115 | '7'(_) -> 116 | ?TESTER_MODULE:'7'(?TAB_MOD, ?TAB_INIT_ARG). -------------------------------------------------------------------------------- /test/director_table_map_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | %% @author Pouriya Jahanbakhsh 34 | %% @version 18.1.1 35 | %% ------------------------------------------------------------------------------------------------- 36 | -module(director_table_map_SUITE). 37 | -author("pouriya.jahanbakhsh@gmail.com"). 38 | %% ------------------------------------------------------------------------------------------------- 39 | %% Exports: 40 | 41 | %% ct callbacks: 42 | -export([init_per_suite/1 43 | ,end_per_suite/1 44 | ,all/0 45 | ,init_per_testcase/2 46 | ,end_per_testcase/2]). 47 | 48 | -export(['1'/1 49 | ,'2'/1 50 | ,'3'/1 51 | ,'4'/1 52 | ,'5'/1 53 | ,'6'/1 54 | ,'7'/1]). 55 | 56 | %% ------------------------------------------------------------------------------------------------- 57 | %% Records & Macros & Includes: 58 | 59 | -define(TAB_MOD, 'director_table_map'). 60 | -define(TAB_INIT_ARG, 'undefined'). 61 | -define(TESTER_MODULE, 'director_table_'). 62 | 63 | %% ------------------------------------------------------------------------------------------------- 64 | %% ct callbacks: 65 | 66 | all() -> 67 | [erlang:list_to_atom(erlang:integer_to_list(Int)) 68 | || Int <- lists:seq(1, erlang:length(?MODULE:module_info(exports))-7)]. 69 | 70 | 71 | init_per_suite(Config) -> 72 | application:start(sasl), 73 | Config. 74 | 75 | 76 | end_per_suite(Config) -> 77 | application:stop(sasl), 78 | Config. 79 | 80 | 81 | init_per_testcase(_TestCase, Config) -> 82 | erlang:process_flag(trap_exit, true), 83 | Config. 84 | 85 | 86 | end_per_testcase(_TestCase, _Config) -> 87 | ok. 88 | 89 | %% ------------------------------------------------------------------------------------------------- 90 | 91 | '1'(_Config) -> 92 | ?TESTER_MODULE:'1'(?TAB_MOD, ?TAB_INIT_ARG). 93 | 94 | 95 | '2'(_Config) -> 96 | ?TESTER_MODULE:'2'(?TAB_MOD, ?TAB_INIT_ARG). 97 | 98 | 99 | '3'(_Config) -> 100 | ?TESTER_MODULE:'3'(?TAB_MOD, ?TAB_INIT_ARG). 101 | 102 | 103 | '4'(_Config) -> 104 | ?TESTER_MODULE:'4'(?TAB_MOD, ?TAB_INIT_ARG). 105 | 106 | 107 | '5'(_Config) -> 108 | ?TESTER_MODULE:'5'(?TAB_MOD, ?TAB_INIT_ARG). 109 | 110 | 111 | '6'(_Config) -> 112 | ?TESTER_MODULE:'6'(?TAB_MOD, ?TAB_INIT_ARG). 113 | 114 | 115 | '7'(_) -> 116 | ?TESTER_MODULE:'7'(?TAB_MOD, ?TAB_INIT_ARG). -------------------------------------------------------------------------------- /test/director_table_mnesia_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% ------------------------------------------------------------------------------------------------ 2 | %%% Director is available for use under the following license, commonly known as the 3-clause (or 3 | %%% "modified") BSD license: 4 | %%% 5 | %%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh 6 | %%% (pouriya.jahanbakhsh@gmail.com) 7 | %%% All rights reserved. 8 | %%% 9 | %%% Redistribution and use in source and binary forms, with or without modification, are permitted 10 | %%% provided that the following conditions are met: 11 | %%% 12 | %%% 1. Redistributions of source code must retain the above copyright notice, this list of 13 | %%% conditions and the following disclaimer. 14 | %%% 15 | %%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of 16 | %%% conditions and the following disclaimer in the documentation and/or other materials provided 17 | %%% with the distribution. 18 | %%% 19 | %%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to 20 | %%% endorse or promote products derived from this software without specific prior written 21 | %%% permission. 22 | %%% 23 | %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 24 | %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 25 | %%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | %%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 | %%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | %%% POSSIBILITY OF SUCH DAMAGE. 32 | %%% ------------------------------------------------------------------------------------------------ 33 | %% @author Pouriya Jahanbakhsh 34 | %% @version 18.1.1 35 | %% ------------------------------------------------------------------------------------------------- 36 | -module(director_table_mnesia_SUITE). 37 | -author("pouriya.jahanbakhsh@gmail.com"). 38 | %% ------------------------------------------------------------------------------------------------- 39 | %% Exports: 40 | 41 | %% ct callbacks: 42 | -export([init_per_suite/1 43 | ,end_per_suite/1 44 | ,all/0 45 | ,init_per_testcase/2 46 | ,end_per_testcase/2]). 47 | 48 | -export(['1'/1 49 | ,'2'/1 50 | ,'3'/1 51 | ,'4'/1 52 | ,'5'/1 53 | ,'6'/1 54 | ,'7'/1 55 | ,'8'/1 56 | ,'9'/1]). 57 | 58 | %% ------------------------------------------------------------------------------------------------- 59 | %% Records & Macros & Includes: 60 | 61 | -define(TAB_MOD, 'director_table_mnesia'). 62 | -define(TAB_INIT_ARG, {value, 'director_table'}). 63 | -define(TESTER_MODULE, 'director_table_'). 64 | 65 | -include_lib("common_test/include/ct.hrl"). 66 | -include_lib("eunit/include/eunit.hrl"). 67 | 68 | %% ------------------------------------------------------------------------------------------------- 69 | %% ct callbacks: 70 | 71 | all() -> 72 | [erlang:list_to_atom(erlang:integer_to_list(Int)) 73 | || Int <- lists:seq(1, erlang:length(?MODULE:module_info(exports))-8)]. 74 | 75 | 76 | init_per_suite(Config) -> 77 | application:start(sasl), 78 | Config. 79 | 80 | 81 | end_per_suite(Config) -> 82 | application:stop(sasl), 83 | Config. 84 | 85 | 86 | init_per_testcase(_TestCase, Config) -> 87 | erlang:process_flag(trap_exit, true), 88 | application:start(mnesia), 89 | Config. 90 | 91 | 92 | end_per_testcase(_TestCase, _Config) -> 93 | application:stop(mnesia), 94 | ok. 95 | %% ------------------------------------------------------------------------------------------------- 96 | 97 | '1'(_Config) -> 98 | ?TESTER_MODULE:'1'(?TAB_MOD, ?TAB_INIT_ARG). 99 | 100 | 101 | '2'(_Config) -> 102 | ?TESTER_MODULE:'2'(?TAB_MOD, ?TAB_INIT_ARG). 103 | 104 | 105 | '3'(_Config) -> 106 | ?TESTER_MODULE:'3'(?TAB_MOD, ?TAB_INIT_ARG). 107 | 108 | 109 | '4'(_Config) -> 110 | ?TESTER_MODULE:'4'(?TAB_MOD, ?TAB_INIT_ARG). 111 | 112 | 113 | '5'(_Config) -> 114 | ?TESTER_MODULE:'5'(?TAB_MOD, ?TAB_INIT_ARG). 115 | 116 | 117 | '6'(_Config) -> 118 | ?TESTER_MODULE:'6'(?TAB_MOD, ?TAB_INIT_ARG). 119 | 120 | 121 | '7'(_) -> 122 | ?TESTER_MODULE:'7'(?TAB_MOD, ?TAB_INIT_ARG). 123 | 124 | 125 | '8'(_) -> 126 | ?TESTER_MODULE:'8'(?TAB_MOD, ?TAB_INIT_ARG). 127 | 128 | 129 | '9'(_) -> 130 | ?assert(erlang:is_list(?TAB_MOD:options())). -------------------------------------------------------------------------------- /test/director_test_utils.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author pouriya 3 | %%% @copyright (C) 2018, 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 31. Jan 2018 6:21 PM 8 | %%%------------------------------------------------------------------- 9 | -module(director_test_utils). 10 | -author("pouriya"). 11 | 12 | %% API 13 | -export([make_return/4 14 | ,handle_return/3 15 | ,flush_handle_returns/0]). 16 | 17 | 18 | 19 | make_return(Pid, Mod, Func, Args) -> 20 | Ref = erlang:make_ref(), 21 | Pid ! {handle_return, erlang:self(), Ref, Mod, Func, Args}, 22 | receive 23 | {Ref, Val} -> 24 | if 25 | erlang:is_function(Val) -> 26 | Val(); 27 | true -> 28 | Val 29 | end 30 | end. 31 | 32 | 33 | handle_return(Mod, Func, Filter) -> 34 | receive 35 | {handle_return, Pid, Ref, Mod, Func, Args} -> 36 | Pid ! {Ref, Filter(Args)} 37 | after 1000 -> 38 | flush_handle_returns(), 39 | c:flush(), 40 | exit({handle_return, Mod, Func}) 41 | end. 42 | 43 | 44 | flush_handle_returns() -> 45 | receive 46 | {handle_return, Pid, _, Mod, Func, Args} -> 47 | ct:pal("Handle return from ~tp for ~tp:~tp/~tp with arguments ~tp~n", [Pid, Mod, Func, erlang:length(Args), Args]), 48 | flush_handle_returns() 49 | after 0 -> 50 | ok 51 | end. -------------------------------------------------------------------------------- /test/director_utils_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(director_utils_SUITE). 2 | 3 | %% ct callbacks: 4 | -export([init_per_suite/1 5 | ,end_per_suite/1 6 | ,all/0 7 | ,init_per_testcase/2 8 | ,end_per_testcase/2]). 9 | 10 | -export(['1'/1 11 | ,'2'/1 12 | ,'3'/1 13 | ,'4'/1]). 14 | 15 | -include_lib("common_test/include/ct.hrl"). 16 | -include_lib("eunit/include/eunit.hrl"). 17 | 18 | all() -> 19 | [erlang:list_to_atom(erlang:integer_to_list(Int)) 20 | || Int <- lists:seq(1, erlang:length(?MODULE:module_info(exports))-8)]. 21 | 22 | 23 | init_per_suite(Config) -> 24 | application:start(sasl), 25 | Config. 26 | 27 | 28 | end_per_suite(Config) -> 29 | application:stop(sasl), 30 | Config. 31 | 32 | 33 | init_per_testcase(_, Config) -> 34 | Config. 35 | 36 | 37 | end_per_testcase(_, _) -> 38 | ok. 39 | 40 | 41 | '1'(_) -> 42 | ?assertEqual([1,2,3,4,5], director_utils:concat([1,2,3], [4,5])), 43 | ?assertEqual([1,2,3,4,5], director_utils:concat([1,2|3], [4|5])), 44 | ?assertEqual([1,2,3,4,5], director_utils:concat([1,2|3], [4,5])), 45 | ?assertEqual([1,2,3,4,5], director_utils:concat([1,2,3], [4|5])), 46 | ?assertEqual([4,5], director_utils:concat([], [4,5])), 47 | ?assertEqual([1,2,3], director_utils:concat([1,2,3], [])), 48 | ?assertEqual([], director_utils:concat([], [])). 49 | 50 | 51 | '2'(_) -> 52 | ?assertEqual(default, director_utils:option(key, [], fun(_) -> filter end, default)), 53 | ?assertEqual(filter, director_utils:option(key, [{key, value}], fun(value) -> filter end, default)). 54 | 55 | 56 | '3'(_) -> 57 | ?assertEqual(default, director_utils:value(key, [], default)), 58 | ?assertEqual(value, director_utils:value(key, [{key, value}], default)). 59 | 60 | 61 | '4'(_) -> 62 | ?assertEqual({true, 1}, director_utils:has_duplicate([1,2,3,4,5,6,7,8,9,1])), 63 | ?assertEqual({true, 1}, director_utils:has_duplicate([1,2,3,4,5,1,6,7,8,9])), 64 | ?assertEqual(false, director_utils:has_duplicate([1,2,3,4,5,6,7,8,9])), 65 | ?assertEqual(false, director_utils:has_duplicate([])). 66 | 67 | 68 | --------------------------------------------------------------------------------