erlangerlang-supervisorgen-event

Adding gen_event handlers while initializing an Erlang supervisor process


I'm learning Erlang, and am managing to find answers and work out solutions to most issues relatively efficiently, but have spent an unacceptable amount of time working out this one:

What is the correct way to add handlers to a gen_event module that is being being started by a supervisor process? In my exploration so far, I have been able to set up processes, gen_servers, supervisors (that start gen_servers), and gen_event modules, but everything I've attempted to actually add a handler to gen_event is crashing my supervisor process — and sometimes even my shell!!

The supervisor module:

-module(sup).
-behaviour(supervisor).

%% API
-export([start_link/0, init/1]).
-export([stop/0]).

start_link() ->
  supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init(_) ->
  EventServerSpec = #{
    id => osc_event_server,
    start => {gen_event, start_link, [{local, osc_server}]},
    modules => dynamic
  },
  ChildSpecList = [EventServerSpec, child(frequency), child(bkpt_server)],
  SupFlags = #{strategy => rest_for_one,
    intensity => 2, period => 3600},
  {ok, {SupFlags, ChildSpecList}}.

child(Module) ->
  #{id => Module,
    start => {Module, start_link, []},
    restart => permanent,
    shutdown => 2000,
    type => worker,
    modules => [Module]}.

… and some bits and pieces from the gen_event module, osc_event_server (hopefully the relevant ones!)

...
init([]) ->
  {ok, Socket} = gen_udp:open(8980, [binary, {active, false}]),
  {ok, #{socket => Socket, target_port => get_target_port()}}.

...
handle_event({send_osc, Path, Data}, State) ->
  TargetPort = maps:get(target_port, State),
  Socket = maps:get(socket, State),
  sendMessage(Socket, TargetPort, Path, Data),
  {ok, State};
...

As poorly as I understand the gen_event behaviour, I'm not much surprised by the result of running the code that includes these snippets:

The two things that I'd like to incorporate:

  1. attach the specific event handlers (e.g., "send_osc") to my gen_event process… hopefully from within the supervisor code
  2. pass initialization arguments to the osc_event_server module; I want to be able at least to specify the port for the UDP server (rather than hard-wiring "8980") and provide the IP+port of a remote UDP server with which I want my process to communicate.

Thanks very much. Feel free to be critical of anything you see… e.g., my whole approach :) As much Erlang as I've come to understand at this point, it's nothing, I'm sure, compared to the amount of Erlang I misunderstand.


Solution

  • You should consider the gen_event process as a kind of service that exists on your node. Usually you'd give it a name, and use that name to talk to it. Until you do, it is a little standalone piece of code.

    That means that if you want to add handlers, given that you can't run arbitrary code within the supervisor to do it, the simplest way to do it is to have your supervisor spawn a sibling process after the gen_event process is ready.

    Mark the new sibling as transient or temporary, have it be a little gen_server or supervisor_bridge worker, and give it the name and handlers of the gen_event. The little worker should add the handlers. It can then shut down with reason normal, or if needs be, supervise the handlers in case they crash to re-add them.