parent v0.10.0 Parent.GenServer behaviour View Source
A GenServer extension which simplifies parenting of child processes.
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.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/1
has the:shutdown
configured to:infinity
. - The generated
child_spec/1
specifies the:type
configured to:supervisor
Starting child processes
To start a child process, you can invoke Parent.start_child/1
in the parent process:
def handle_call(...) do
Parent.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:timeout
(optional) - timeout after which the child is killed by the parent, see the timeout section below, defaults to:infinity
The function described with :start
needs to start a linked process and return
the result as {:ok, pid}
. For example:
Parent.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.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 process 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
.
Timeout
If a positive integer is provided via the :timeout
option, the parent will
terminate the child if it doesn't stop within the given time. In this case,
handle_child_terminated/5
will be invoked with the exit reason :timeout
.
Working with child processes
The Parent
module provides various functions for managing child processes.
For example, you can enumerate running children with Parent.children/0
,
fetch child meta with Parent.child_meta/1
, or terminate a child process with
Parent.shutdown_child/1
.
Termination
The behaviour takes down the child processes during termination, to ensure that no child process is running after the parent has terminated. The children are terminated synchronously, one by one, in the reverse start order.
The termination of the children is done after the terminate/1
callback returns.
Therefore in terminate/1
the child processes are still running, and you can
interact with them.
Supervisor compliance
A process powered by Parent.GenServer
can handle supervisor specific
messages, which means that for all intents and purposes, such process is
treated as a supervisor. As a result, children of parent will be included in
the hot code reload process.
Link to this section Summary
Functions
See Parent.child?/1
.
See Parent.child_id/1
.
See Parent.child_meta/1
.
See Parent.child_pid/1
.
See Parent.children/0
.
See Parent.start_child/1
.
Starts the parent process.
Callbacks
Invoked when a child has terminated.
Link to this section Types
Link to this section Functions
See Parent.child?/1
.
See Parent.child_id/1
.
See Parent.child_meta/1
.
See Parent.child_pid/1
.
See Parent.children/0
.
See Parent.start_child/1
.
start_link(module, arg, options \\ [])
View Sourcestart_link(module(), arg :: term(), GenServer.options()) :: GenServer.on_start()
Starts the parent process.
Link to this section Callbacks
handle_child_terminated(arg1, arg2, pid, reason, state)
View Sourcehandle_child_terminated( Parent.child_id(), Parent.child_meta(), pid(), reason :: term(), state() ) :: {:noreply, new_state} | {:noreply, new_state, timeout() | :hibernate} | {:stop, reason :: term(), new_state} when new_state: state()
Invoked when a child has terminated.