GenEvent


the event handling model consists of

       -   a generic event manager process with
       -   an arbitrary number of event handlers which are added and
       deleted dynamically

an event manager will have a standard set of interface functions and include
functionality for tracing and error reporting. It will also fit into a
supervision tree

as an example, let’s have a GenEvent that accumulates messages until they
are collected by an explicit call:


defmodule LoggerHandler do

  use GenEvent

  def handle_event({:log, x}, messages) do
    {:ok, [x | messages]}
  end

  def handle_call(:messages, messages) do
    {:ok, Enum.reverse(messages), []}
  end

end


iex> {:ok, pid} = GenEvent.start_link([])
 {:ok, #PID<0.5500.0>}
iex> GenEvent.add_handler(pid, LoggerHandler, [])
 :ok
iex> GenEvent.notify(pid, {:log, 1})
 :ok
iex> GenEvent.notify(pid, {:log, 2})
 :ok
iex> GenEvent.call(pid, LoggerHandler, :messages)
 [1, 2]
iex> GenEvent.call(pid, LoggerHandler, :messages)
 []


we start a new event manager by calling GenEvent.start_link/1. notifications
can be sent to the event manager which will then invoke handle_event/2 for
EACH registered handler. we can add new handlers with add_handler/3 and
add_mon_handler/3. calls can also be made to specific handlers by using call/3





            Name Registration

a GenEvent is bound to the same name registration rules as a GenServer




            Modes

GenEvent stream supports three different notifications:

   GenEvent.notify/2
all events are processed asynchronously and there is no ack

   GenEvent.ack_notify/2
the manager acknowledges each event, but processing of the message happens
asynchronously

   GenEvent.sync_notify/2
the manager acknowledges an event just after it is processed by all event
handlers








               Callbacks


there are 6 callbacks required to be implemented in a GenEvent. by adding 'use
GenEvent' to your module, Elixir will automatically define all 6 callbacks
for you, leaving it up to you to implement the ones you want to customize


   init(args)

   handle_call(request, state)
   handle_event(event, state)

   handle_info(msg, state)
   code_change(old_vsn, state, extra)
   terminate(reason, state)


init(args :: term) :: {:ok, state}  |  {:ok, state, :hibernate}  |  {:error,
reason :: any}

   Invoked when the handler is added to the GenEvent process
   add_handler/3, (and add_mon_handler/3) will block until it returns

   args is the argument term (third argument) passed to add_handler/3

   returning {:ok, state} will cause add_handler/3 to return :ok

   returning {:ok, state, :hibernate} is similar to {:ok, state} except the
   GenEvent process is
   hibernated before continuing its loop

   returning {:error, reason} will cause add_handler/3 to return {:error,
   reason}




handle_call(request :: term, state :: term) ::
  {:ok, reply, new_state} |
  {:ok, reply, new_state, :hibernate} |
  {:remove_handler, reply} when reply: term, new_state: term

   Invoked to handle synchronous call/4 messages to a specific handler

   request is the request message sent by a call/4 and state is the current
   state of the handler

   Returning {:ok, reply, new_state} sends reply as a response to the call
   and sets the handler’s
   state to new_state

   Returning {:ok, reply, new_state, :hibernate} is similar to {:ok, reply,
   new_state} except the
   process is hibernated. See handle_event/2 for more information on
   hibernation

   Returning {:remove_handler, reply} sends reply as a reponse to the call,
   removes the handler
   from the GenEvent loop and calls terminate/2 with reason :remove_handler
   and state state




handle_event(event :: term, state :: term) ::
  {:ok, new_state} |
  {:ok, new_state, :hibernate} |
  :remove_handler when new_state: term

   Invoked to handle notify/2, ack_notify/2 or sync_notify/2 messages

   event is the event message and state is the current state of the handler

   Returning {:ok, new_state} sets the handler’s state to new_state and
   the GenEvent loop
   continues

   Returning {:ok, new_state, :hibernate} is similar to {:ok, new_state}
   except the process is
   hibernated once all handlers have handled the events. The GenEvent process
   will continue the
   loop once a message is its message queue. If a message is already in the
   message queue this
   will be immediately. Hibernating a GenEvent causes garbage collection
   and leaves a continuous
   heap that minimises the memory used by the process

   Hibernating should not be used aggressively as too much time could be
   spent garbage collecting
   Normally it should only be used when a message is not expected soon and
   minimising the memory
   of the process is shown to be beneficial

   Returning :remove_handler removes the handler from the GenEvent loop and
   calls terminate/2 with
   reason :remove_handler and state state



handle_info(msg :: term, state :: term) ::
  {:ok, new_state} |
  {:ok, new_state, :hibernate} |
  :remove_handler when new_state: term

   Invoked to handle all other messages. All handlers are run in the GenEvent
   process so messages
   intended for other handlers should be ignored with a catch all clause

   msg is the message and state is the current state of the handler

   Return values are the same as handle_event/2



terminate(reason, state :: term) :: term when reason: :stop | {:stop, term}
| :remove_handler | {:error,
 term} | term

   Invoked when the server is about to exit. It should do any cleanup required

   reason is removal reason and state is the current state of the handler. The
   return value is
   returned to GenEvent.remove_handler/3 or ignored if removing for another
   reason

   reason is one of:
     * :stop - manager is terminating
     * {:stop, term} - monitored process terminated (for monitored handlers)
     * :remove_handler - handler is being removed
     * {:error, term} - handler crashed or returned a bad value and an error
     is logged
     * term - any term passed to functions like GenEvent.remove_handler/3

   If part of a supervision tree, a GenEvent‘s Supervisor will send an
   exit signal when shutting
   it down. The exit signal is based on the shutdown strategy in the child’s
   specification. If it
   is :brutal_kill the GenEvent is killed and so terminate/2 is not called
   for its handlers
   However if it is a timeout the Supervisor will send the exit signal
   :shutdown and the GenEvent
   will have the duration of the timeout to call terminate/2 on all of its
   handlers - if the
   process is still alive after the timeout it is killed

   If the GenEvent receives an exit signal (that is not :normal) from any
   process when it is not
   trapping exits it will exit abruptly with the same reason and so not call
   the handlers’
   terminate/2. Note that a process does NOT trap exits by default and an
   exit signal is sent when
   a linked process exits or its node is disconnected. Therefore it is not
   guaranteed that
   terminate/2 is called when a GenEvent exits

   Care should be taken to cleanup because the GenEvent can continue is loop
   after removing the
   handler. This is different to most other OTP behaviours. For example if
   the handler controls a
   port (e.g. :gen_tcp.socket) or File.io_device, it will be need to be
   closed in terminate/2 as
   the process is not exiting so will not be automatically cleaned up



code_change(old_vsn, state :: term, extra :: term) :: {:ok, new_state ::
term} when old_vsn: term | {:do
wn, term}

   Invoked to change the state of the handler when a different version of
   the handler’s module
   module is loaded (hot code swapping) and the state’s term structure
   should be changed

   old_vsn is the previous version of the module (defined by the @vsn
   attribute) when upgrading
   When downgrading the previous version is wrapped in a 2-tuple with first
   element :down. state
   is the current state of the handker and extra is any extra data required
   to change the state

   Returning {:ok, new_state} changes the state to new_state and the code
   change is successful

   If code_change/3 raises, the code change fails and the handler will
   continue with its previous
   state. Therefore this callback does not usually contain side effects



              Types

handler :: atom | {atom, term}

manager :: pid | name | {atom, node}

name :: atom | {:global, term} | {:via, module, term}

on_start ::  {:ok, pid} |  {:error, {:already_started, pid}}

options :: [{:name, name}]




                 Functions





call(manager, handler, term, timeout) :: term | {:error, term}

   Makes a synchronous call to the event handler installed in manager

   The given request is sent and the caller waits until a reply arrives or
   a timeout occurs. The
   event manager will call handle_call/2 to handle the request

   The return value reply is defined in the return value of handle_call/2. If
   the specified event
   handler is not installed, the function returns {:error, :not_found}
   notify(manager, event)






notify(manager, term) :: :ok

   the event manager will call handle_event/2 for each installed event handler

   notify is asynchronous and will return immediately after the notification
   is sent
   notify will not fail even if the specified event manager does not exist,
   unless it is specified as an atom


ack_notify(manager, term) :: :ok


sync_notify(manager, term) :: :ok

   Sends a sync event notification to the event manager

   In other words, this function only returns :ok after the event manager
   invokes the
   handle_event/2 callback on each installed event handler

   See notify/2 for more info







add_handler(manager, handler, term) :: :ok | {:error, term}

   The event manager will call the init/1 callback with args to initiate
   the event handler and its
   internal state

   If init/1 returns a correct value indicating successful completion,
   the event manager adds the
   event handler and this function returns :ok. If the callback fails with
   reason or returns
   {:error, reason}, the event handler is ignored and this function returns
   {:error, reason}

   If the given handler was previously installed at the manager, this function
   returns {:error,
   :already_present}

   For installing multiple instances of the same handler, {Module, id}
   instead of Module must be
   used. The handler could be then referenced with {Module, id} instead of
   just Module
   add_mon_handler(manager, handler, args)



add_mon_handler(manager, handler, term) :: :ok | {:error, term}

    A monitored handler implies the calling process will now be monitored
    by the GenEvent manager

   If the calling process later terminates with reason, the event manager
   will delete the event
   handler by calling the terminate/2 callback with {:stop, reason} as
   argument. If the event
   handler later is deleted, the event manager sends a message
   {:gen_event_EXIT, handler, reason}
   to the calling process. Reason is one of the following:
     * :normal - if the event handler has been removed due to a call to
     remove_handler/3, or
       :remove_handler has been returned by a callback function
     * :shutdown - if the event handler has been removed because the event
     manager is terminating
     * {:swapped, new_handler, pid} - if the process pid has replaced the
     event handler by another
     * a term - if the event handler is removed due to an error. Which term
     depends on the error

   Keep in mind that the {:gen_event_EXIT, handler, reason} message is not
   guaranteed to be
   delivered in case the manager crashes. If you want to guarantee the
   message is delivered, you
   have two options:
     * monitor the event manager
     * link to the event manager and then set Process.flag(:trap_exit, true)
     in your handler
       callback

   Finally, this functionality only works with GenEvent started via this
   module (it is not
   backwards compatible with Erlang’s :gen_event)
   call(manager, handler, request, timeout \\ 5000)



which_handlers(manager) :: [handler]

   Returns a list of all event handlers installed in the manager



remove_handler(manager, handler, term) ::  term | {:error, term}

   The event manager will call terminate/2 to terminate the event handler
   and return the callback
   value. If the specified event handler is not installed, the function
   returns {:error,
   :not_found}
   start(options \\ [])






start(options) :: on_start

start_link(options) :: on_start

   it accepts the :name option
   if the event manager is successfully created and initialized, the function
   returns {:ok, pid}


stop(manager, reason :: term, timeout) :: :ok

   Before terminating, the event manager will call terminate(:stop, ...) for
   each installed event
   handler. It returns :ok if the manager terminates with the given reason,
   if it terminates with
   another reason, the call will exit

   If the reason is any other than :normal, :shutdown or {:shutdown, _},
   an error report will be logged







stream(manager, Keyword.t) :: GenEvent.Stream.t

   returns a stream that consumes events from the manager

   The stream is a GenEvent struct that implements the Enumerable
   protocol. Consumption of events
   only begins when enumeration starts

   streaming is specific to Elixir’s GenEvent and does not work with
   Erlang ones





swap_handler(manager, handler, term, handler, term) ::  :ok |  {:error, term}

   Replaces an old event handler with a new one in the event manager

   First, the old event handler is deleted by calling terminate/2 with the
   given args1 and
   collects the return value. Then the new event handler is added and
   initiated by calling
   init({args2, term}), where term is the return value of calling terminate/2
   in the old handler
   This makes it possible to transfer information from one handler to another

   The new handler will be added even if the specified old event handler is
   not installed or if
   the handler fails to terminate with a given reason in which case state =
   {:error, term}

   If init/1 in the second handler returns a correct value, this function
   returns :ok
   swap_mon_handler(manager, handler1, args1, handler2, args2)




swap_mon_handler(manager, handler, term, handler, term) ::   :ok |  {:error,
term}

   Read the docs for add_mon_handler/3 and swap_handler/5 for more information



           Streaming

GenEvent messages can be streamed with the help of stream/1/2. you will need
to start another process to consume the stream:


Task.start_link fn ->
  stream = GenEvent.stream(pid)
  _ = Enum.drop(stream, 3)
  for event <- stream do
    IO.inspect event
  end
end


now call GenEvent.notify/2 multiple times