View Source Tarearbol.DynamicManager behaviour (tarearbol v1.12.0)

The scaffold implementation to dynamically manage many similar tasks running as processes.

It creates a main supervisor, managing the GenServer holding the state and DynamicSupervisor handling chidren. It has a strategy :rest_for_one, assuming that if the process holding the state crashes, the children will be restarted.

Typically one calls use Tarearbol.DynamicManager and implements at least children_specs/0 callback and receives back supervised tree with a state and many processes controlled by DynamicSupervisor.

To see how it works you might try

defmodule DynamicManager do
  use Tarearbol.DynamicManager

  def children_specs do
    for i <- 1..10, do: {"foo_#{i}", []}, into: %{}
  end
end

{:ok, pid} = DynamicManager.start_link()

The above would spawn 10 children with IDs "foo_1".."foo_10".

Workers Management

DynamicManager allows dynamic workers management. It exports three functions

@spec get(id :: id()) :: Enum.t()
@spec put(id :: id(), opts :: Enum.t()) :: pid()
@spec del(id :: id()) :: :ok
@spec restart(id :: id()) :: :ok

The semantics of put/2 arguments is the same as a single child_spec, del/1 and get/1 receive the unique ID of the child and shutdown it or return it’s payload respectively.

Workers Callbacks

Workers are allowed to implement several callbacks to be used to pass messages to them.

  • peform/2 is called periodically by the library internals; the interval is set upon worker initialization via children_specs/1 (static) or put/2 (dynamic); the interval set to 0 suppresses periodic invocations
  • call/3 to handle synchronous message send to worker
  • cast/2 to handle asynchronous message send to worker
  • terminate/2 to handle worker process termination

All the above should return a value of Tarearbol.DynamicManager.response/0 type.

Also, the implementing module might use a custom initialization function to e. g. dynamically build payload. Is should be passed to use DynamicManager as a parameter init: handler and might be a tuple {module(), function(), arity()} or a captured function &MyMod.my_init/1. Arities 0, 1 and 2 are allowed, as described by Tarearbol.DynamicManager.init_handler/0 type.

The worker process will call this function from GenServer.handle_continue/2 callback.

Summary

Types

Identifier of the child process

Post-instantion init handler type, that might be passed to use DynamicManager vis init:

Payload associated with the worker

Expected response from the DymanicManager implementation

Callbacks

The method to implement to support explicit GenServer.call/3 on the wrapping worker.

The method to implement to support explicit GenServer.cast/2 on the wrapping worker.

This function is called to retrieve the map of children with name as key and a workers as the value.

Declares an instance-wide callback to report state; if the startup process takes a while, it’d be run in handle_continue/2 and this function will be called after it finishes so that the application might start using it.

Declares a callback to report slow process (when the scheduler cannot process in a reasonable time).

The main function, doing all the internal job, supervised.

The method that will be called before the worker is terminated.

Types

@type id() :: any()

Identifier of the child process

Link to this type

init_handler()

View Source (since 0.9.0)
@type init_handler() ::
  nil
  | (-> payload())
  | (payload() -> payload())
  | (id(), payload() -> payload())

Post-instantion init handler type, that might be passed to use DynamicManager vis init:

Link to this type

payload()

View Source (since 0.9.0)
@type payload() :: any()

Payload associated with the worker

Link to this type

response()

View Source (since 0.9.0)
@type response() ::
  :halt
  | {:replace, payload()}
  | {:replace, id(), payload()}
  | {{:timeout, integer()}, payload()}
  | {:ok, any()}
  | any()

Expected response from the DymanicManager implementation

Callbacks

Link to this callback

call(message, from, {})

View Source (since 1.2.0)
@callback call(
  message :: any(),
  from :: GenServer.from(),
  {id :: id(), payload :: payload()}
) :: response()

The method to implement to support explicit GenServer.call/3 on the wrapping worker.

Link to this callback

cast(message, {})

View Source (since 1.2.1)
@callback cast(
  message :: any(),
  {id :: id(), payload :: payload()}
) :: response()

The method to implement to support explicit GenServer.cast/2 on the wrapping worker.

Link to this callback

children_specs()

View Source (since 0.9.0)
@callback children_specs() :: %{required(id()) => Enum.t()}

This function is called to retrieve the map of children with name as key and a workers as the value.

The value must be an enumerable with keys among:

  • :payload passed as second argument to perform/2, default nil
  • :timeout time between iterations of perform/2, default 1 second
  • :lull threshold to notify latency in performing, default 1.1 (the threshold is :lull times the :timeout)

This function should not care about anything save for producing side effects.

It will be backed by DynamicSupervisor. The value it returns will be put into the state under children key.

Link to this callback

handle_state_change(state)

View Source (since 0.9.0)
@callback handle_state_change(state :: :down | :up | :starting | :unknown) ::
  :ok | :restart

Declares an instance-wide callback to report state; if the startup process takes a while, it’d be run in handle_continue/2 and this function will be called after it finishes so that the application might start using it.

If the application is not interested in receiving state updates, e. g. when all it needs from runners is a side effect, there is a default implementation that does nothing.

Link to this callback

handle_timeout(state)

View Source (since 0.9.5)
@callback handle_timeout(state :: map()) :: any()

Declares a callback to report slow process (when the scheduler cannot process in a reasonable time).

Link to this callback

perform(id, payload)

View Source (since 0.9.0)
@callback perform(id :: id(), payload :: payload()) :: response()

The main function, doing all the internal job, supervised.

It will be called with the child id as first argument and the payload option to child spec as second argument (defaulting to nil, can also be ignored if not needed).

Return values

perform/2 might return

  • :halt if it wants to be killed
  • {:ok, result} to store the last result and reschedule with default timeout
  • {:replace, payload} to replace the payload (state) of the current worker with the new one
  • {:replace, id, payload} to replace the current worker with the new one
  • {{:timeout, timeout}, result} to store the last result and reschedule in given timeout interval
  • or deprecated anything else will be treated as a result
Link to this callback

terminate(reason, {})

View Source (since 1.2.0)
@callback terminate(
  reason :: term(),
  {id :: id(), payload :: payload()}
) :: any()

The method that will be called before the worker is terminated.