Home

Awesome

directror travis test status Coverage Status

Welcome

directror

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.

What is a supervisor? (for newbies)

According to the Erlang's manual:  

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.   In Erlang we tell supervisors to start other processes. Every child process has its own options called childspec.

Features

All features not listed here. For more info see Guide and examples. For contributing see CONTRIBUTING.md file.

How to use?

Since Director is an Erlang behaviour; So before explaining its workflow, I'll explain that "What a behaviour is?" for newbies.

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.

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.

init/1

For starting a linked Director process, you should call one of the API functions:

director:start_link(Module::module(), InitArg::any()).
director:start_link(Module::module(), InitArg::any(), Opts::director:start_options()).
director:start_link(RegisterName::director:register_name(), Module::module(), InitArg::any()).
director:start_link(RegisterName::director:register_name(), Module::module(), InitArg::any(), Opts::director:start_options()).

For starting an stand-alone Director process, you should call one of the API functions:

director:start(Module::module(), InitArg::any()).
director:start(Module::module(), InitArg::any(), Opts::director:start_options()).
director:start(RegisterName::director:register_name(), Module::module(), InitArg::any()).
director:start(RegisterName::director:register_name(), Module::module(), InitArg::any(), Opts::director:start_options()).

After calling, Director process calls Module:init(InitArg), possible return values of init/1 are:

-type init_return() :: 'ok' % will be {ok, undefined, [], director:default_childspec(), []}
                     | {'ok', director:state()}
                     | {'ok', director:state(), [director:childspec()]|[]}
                     | {'ok', director:state(), [director:childspec()]|[], director:default_childspec()}
                     | {'ok', director:state(), [director:childspec()]|[], director:start_options()}
                     | {'ok', director:state(), [director:childspec()]|[], director:default_childspec(), director:start_options()}
                     | 'ignore'
                     | {'stop', Reason::any()}.

Childspec

A childspec is an Erlang map containing some mandatory and optional keys that belongs to one child process. I will explain these keys below.

Start options

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:

init/1 examples

-module(director_test).
...
-export([init/1]). % Do not forget to export it
...
-record(state, {}). %% State record for Director itself
-record(chstate, {}). %% State record for Director children

init(MyInitArg) ->
	Child_1 = #{id => child_1
	           ,start => {child_1_module, start_link, [MyInitArg]}
	           ,state => #chstate{}
	           ,terminate_timeout => 1000},
	Child_2 = #{id => child_2
	           ,start => {child_2_module, start_link, [MyInitArg, arg_2, arg_3]}
	           ,state => #chstate{}},
	{ok, #state{}, [Child_1, Child2], [{db, [{table, ets}, {init_arg, my_table}]}]}.
	%% In above if you want some children with similar childspecs, you can use default childspec:
%	Children = [#{id => Id, append => true} || _ <- lists:seq(1, 100)],
%	DefChildSpec = #{start => {foo, start, [arg_1, arg_2]}},
%	{ok, #state{}, Children, DefChildSpec}.
	%% You will have 100 childspecs with ids 1-100 and start {foo, start, [arg_1, arg_2]}
	
	%% If you want simple_one_for_one strategy of OTP/Supervisor:
%	Children = [#{id => Id, append => true, start => {foo, start, [arg_3]}} || _ <- lists:seq(1, 100)],
%	DefChildSpec = #{start => {foo, start, [arg_1, arg_2]}},
%	{ok, #state{}, Children, DefChildSpec}.
	%% You will have 100 childspecs with ids 1-100 and start {foo, start, [arg_1, arg_2, arg_3]}
...

handle_start/4

When Director starts a child process, It calls:

YourCallbackModule:handle_start(ChildId, ChildState, DirectorState, Metadata)

In above example when Director starts Child_1, It calls:

director_test:handle_start(child_1, #chstate{}, #state{}, #{restart_count := 0, pid := PidOfChild_1})

This callback-function should yield:

-type handle_start_return() :: {'ok', NewChildState::director:child_state(), NewDirectorState::director:state(), director:callback_return_options()}
                             | {'stop', director:child_state(), Reason::any(), director:callback_return_options()}.

Example of handle_start/4 which just tells Director to don't call error_logger about starting any child:

handle_start(_, ChState, State, _) ->
	{ok, ChState, State, [{log, false}]}.

handle_exit/5

When a child process crashes, Its Director will receive child's exit signal and calls:

YourCallbackModule:handle_exit(ChildId, ChildState, ReasonOfChildTermination, DirectorState, MetaData)

In above example when Child_1 exits with reason oops, Director calls:

director_test:handle_exit(child_1, #chstate{}, oops, #state{}, #{restart_count := 1})

This callback-function should yield:

-type handle_exit_return() :: {director:action(), director:child_state(), director:state(), director:callback_return_options()}.
-type  action() :: 'restart'
                 | {'restart', pos_integer()}
                 | 'delete'
                 | 'wait'
                 | 'stop'
                 | {'stop', Reason::any()}.

Example of handle_exit/5 which tells Director to restart child after 1000 milli-seconds:

handle_exit(_, ChState, _, State, _) ->
	{{restart, 1000}, ChState, State, []}.

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.
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:

handle_exit(foo, ChState, _Reason, State, #{restart_count := RC}) when RC < 6 ->
	{restart, ChState, State, []};
handle_exit(foo, ChState, _Reason, State, #{restart_count := 6}) ->
	{{restart, 1000}, ChState, State, []};
handle_exit(foo, ChState, _Reason, State, _) ->
	{{stop, {max_restart, foo}}, ChState, State, []}.

handle_terminate/5

When you call director:terminate_child/2-3 or director:delete_and_terminate_child/2-3, Director terminates child process and calls:

YourCallbackModule:handle_terminate(ChildId, ChildState, ReasonOfChildTermination::shutdown|kill|term(), DirectorState, MetaData)

Also it calls handle_terminate/5 when it is in terminate state and is terminating its alive children.
For above example when you call director:terminate_child(DirectorProc, child_2), It calls:

director_test:handle_exit(child_2, #chstate{}, shutdown, #state{}, #{restart_count := 0})

This callback-function should yield:

-type handle_terminate_return() :: {'ok', director:child_state(), director:state(), director:callback_return_options()}.

For example following code tells Director don't call error_logger for termination of child id bar and call it for other children:

handle_terminate(bar, ChildState, _, DirectorState, _) ->
	{ok, ChildState, DirectorState, [{log, false}]};
handle_terminate(_, ChildState, _, DirectorState, _) ->
	{ok, ChildState, DirectorState, []}. % Default log value is true

terminate/2

When director is terminating itself, after terminating its alive children, It calls:

YourCallbackModule:terminate(ReasonOfTermination, DirectorState)

For above example if Director is terminating with reason normal, it calls:

director_test:terminate(normal, #state{})

This callback-function should yield:

-type terminate_return() :: {'ok', callback_return_options()}
                          | {'new_error', NewReason::any(), callback_return_options()}
                          | any().

For example following code tells Director to change crash reason oops to normal and do not call error_logger about terminating yourself:

terminate(oops, State) ->
	{new_error, normal, [{log, false}]}.

Anything other than {ok, Opts}and{new_error, _, Opts}causes **Director** to callerror_logger` and exit with its crash reason.

Build

Compile

~/director $ make
===> Verifying dependencies...
===> Compiling director

Use as dependency

Rebar3

Put this in deps in rebar.config:

{director, "18.4.29"}
Rebar

Put this in deps in rebar.config:

{director, ".*", {git, "https://github.com/Pouriya-Jahanbakhsh/director.git", {tag, "18.4.29"}}}
Mix

Put this in deps in mix.exs:

{:director, "~> 18.4.29"}
erlang.mk
dep_director = hex 18.4.29

API documentation


 /projects/director $ make doc
===> Verifying dependencies...
===> Fetching edown ({pkg,"edown","0.8.1"})
===> Compiling edown
===> Compiling director
===> Running edoc for director

 /projects/director $ ls doc/ | grep .md
director.md
director_table_ets.md
director_table_mnesia.md
README.md

Todo

License

BSD 3-Clause

Author

pouriya.jahanbakhsh@gmail.com

Hex version

18.4.29