[erlang-questions] supervisor with ets stored children: gen_tracker

Max Lapshin max.lapshin@REDACTED
Mon May 20 21:27:29 CEST 2013


Hi, everyone.

gen_tracker is a library that implements subset of supervisor
functionality with storing children and their metadata in public named
ets table.

https://github.com/erlyvideo/gen_tracker

MIT license.

I've been using this code for a while in erlyvideo and I've extracted
it as a library.



What problems are solved:

1) supervisor doesn't have supervisor:find_child_by_name API, only
which_children that returns full list of children
2) supervisor library doesn't have supervisor:find_or_start API that
atomically either return existing child, either starts new. The only
way is to handle two different {already_started,Pid} errors
3) gproc library is ortogonal to supervisor tree, so it is impossible
to introspect all gproc children via appmon and do other cool
supervisor things like autostopping all children
4) there is no guaranteed way to call some function with supervisor or
gproc after process is dead, passing some metadata to this function.
Module:terminate can be not called if reason is kill. This function is
required for accounting need to calculate how many bytes have this
user session transferred or something else.




How gen_tracker solves this problem?

Each gen_tracker instance is a singleton that registers under some
atom in system, it creates ets table with the same name and second
table NAME_attrs for children metadata.

gen_tracker instance behaves like a supervisor.


Usage

You can start it as a regular child in your supervision tree:

    init([]) ->
      Supervisors = [{streams, {gen_tracker, start_link, [streams]},
permanent, infinity, supervisor, []}],
      {ok, { {one_for_one, 5, 10}, Supervisors} }.


Mention that gen_tracker process is a supervisor.

Now let's start a child of this gen_tracke instance.

Define a supervisor child:

    Stream = {<<"tv1">>, {stream, start_link, [<<"tv1">>, Options]},
temporary, infinity, supervisor, []},

and start it or get existing:

    {ok, Pid} = gen_tracker:find_or_open(streams, Stream).


Mention that find_or_open first checks in ets table and if it doesn't
exists, it goes to process.

If you don't want to make a gen_server:call, than you can just:

    {ok, Pid} = gen_tracker:find(streams, <<"tv1">>).

to find _existing_ process (or get undefined if there is no child with such ID)

Now, metadata.

Erlyvideo streams stores hundreds of megabytes of data in memory. It
is a bad idea to store this data in process dictionary, ets is a
better place:

    gen_tracker:setattr(streams, Name, [{hds,true},{bytes_in,0},{bytes_out,0}]),
    gen_tracker:increment(streams, Name, bytes_in, 1000),
    {ok, BytesIn} = gen_tracker:getattr(streams, Name, bytes_in).


Wow, several _nonblocking_ calls made to modify child metadata. No
single message is sent here.

Now it is time to kill annoying child:

    supervisor:which_children(streams),
    supervisor:delete_child(streams, <<"tv1">>).

gen_tracker implements subset of supervisor API.


So, I hope that it would be useful for someone else.



More information about the erlang-questions mailing list