parent v0.1.0 Parent.GenServer behaviour View Source
A GenServer extension which simplifies parenting of children.
This behaviour helps implementing a GenServer which also needs to directly start child processes and handle their termination.
Starting the process
The usage is similar to GenServer. You need to use the module and start the process:
def MyParentProcess do
use Parent.GenServer
def start_link(arg) do
Parent.GenServer.start_link(__MODULE__, arg, options \\ [])
end
end
The expression use Parent.GenServer will also inject use GenServer into
your code. Your parent process is a GenServer, and this behaviour doesn’t try
to hide it. Except when starting the process, you work with the parent exactly
as you work with any GenServer, using the same functions, and writing the same
callbacks:
def MyParentProcess do
use Parent.GenServer
def do_something(pid, arg), do: GenServer.call(pid, {:do_something, arg})
...
@impl GenServer
def init(arg), do: {:ok, initial_state(arg)}
@impl GenServer
def handle_call({:do_something, arg}, _from, state),
do: {:reply, response(state, arg), next_state(state, arg)}
end
Compared to plain GenServer, there are following differences:
- A Parent.GenServer traps exits by default.
- The generated
child_spec/1has the:shutdownconfigured to:infinity.
Starting children
To start a child, you can invoke start_child/1 in the parent process:
def handle_call(...) do
Parent.GenServer.start_child(child_spec)
...
end
The function takes a child spec map which is similar to Supervisor child specs. The map has the following keys:
:id(required) - a term uniquely identifying the child:start(required) - an MFA, or a zero arity lambda invoked to start the child:meta(optional) - a term associated with the started child, defaults tonil:shutdown(optional) - same as withSupervisor, defaults to 5000
The function described with :start needs to start a linked process and return
the result as {:ok, pid}. For example:
Parent.GenServer.start_child(%{
id: :hello_world,
start: {Task, :start_link, [fn -> IO.puts "Hello, World!" end]}
})
You can also pass a zero-arity lambda for :start:
Parent.GenServer.start_child(%{
id: :hello_world,
start: fn -> Task.start_link(fn -> IO.puts "Hello, World!" end) end
})
Finally, a child spec can also be a module, or a {module, arg} function.
This works similarly to supervisor specs, invoking module.child_spec/1
is which must provide the final child specification.
Handling child termination
When a child terminates, handle_child_terminated/5 will be invoked. The
default implementation is injected into the module, but you can of course
override it:
@impl Parent.GenServer
def handle_child_terminated(id, child_meta, pid, reason, state) do
...
{:noreply, state}
end
The return value of handle_child_terminated is the same as for handle_info.
Working with children
This module provide various functions for managing the children. For example,
you can enumerate running children with children/0, fetch child meta with
child_meta/1, or terminate a child with shutdown_child/1.
Termination
The behaviour takes down the children during termination, to ensure that no
child is running after the parent has terminated. This happens after the
terminate/1 callback returns. Therefore in terminate/1 the children are
still running, and you can interact with them.
Link to this section Summary
Functions
Returns true if the child is still running, false otherwise
Returns the id of a child with the given pid
Returns the meta associated with the given child id
Returns the pid of a child with the given id
Returns the list of running children
Returns the count of running children
Terminates all running children
Terminates the child
Starts the child described by the specification
Starts the parent process
Updates the meta of the given child
Callbacks
Invoked when a child has terminated
Link to this section Types
child_spec() :: %{
:id => id(),
:start => start(),
optional(:meta) => child_meta(),
optional(:shutdown) => shutdown()
}
start() :: (() -> on_start_child()) | {module(), atom(), [term()]}
Link to this section Functions
Returns true if the child is still running, false otherwise.
Note that this function might return true even if the child has terminated.
This can happen if the corresponding :EXIT message still hasn’t been
processed.
Returns the id of a child with the given pid.
Returns the meta associated with the given child id.
Returns the pid of a child with the given id.
Returns the list of running children.
Returns the count of running children.
shutdown_all(reason :: term()) :: :ok
Terminates all running children.
The order in which children are taken down is not guaranteed. The function returns after all of the children have been terminated.
Terminates the child.
This function waits for the child to terminate. In the case of explicit
termination, handle_child_terminated/5 will not be invoked.
start_child(child_spec() | module() | {module(), term()}) :: on_start_child()
Starts the child described by the specification.
start_link(module(), arg :: term(), GenServer.options()) :: GenServer.on_start()
Starts the parent process.
update_child_meta(id(), (child_meta() -> child_meta())) :: :ok | :error
Updates the meta of the given child.
Link to this section Callbacks
Invoked when a child has terminated.