sasa1977/parent
{ "createdAt": "2018-04-08T19:03:34Z", "defaultBranch": "master", "description": "Custom parenting of processes in Elixir", "fullName": "sasa1977/parent", "homepage": null, "language": "Elixir", "name": "parent", "pushedAt": "2024-10-04T08:22:20Z", "stargazersCount": 277, "topics": [], "updatedAt": "2025-10-02T20:40:57Z", "url": "https://github.com/sasa1977/parent"}Parent
Section titled “Parent”Support for custom parenting of processes. See docs for reference.
Parent is a toolkit for building processes which parent other children and manage their life cycles. The library provides features similar to Supervisor, such as the support for automatic restarts and failure escalation (maximum restart intensity), with some additional benefits that can help flattening the supervision tree, reduce the amount of custom process monitors, and simplify the process structure. The most important differences from Supervisor are:
- No supervision strategies (one_for_one, rest_for_one, etc). Instead, Parent uses bindings and shutdown groups to achieve the similar behaviour.
- No distinction between static and dynamic supervisors. Instead, a per-child option called
:ephemeral?is used to achieve dynamic behaviour. - Basic registry-like capabilities for simple children discovery baked-in directly into parent.
- Exposed lower level plumbing modules, such as
Parent.GenServerandParent, which can be used to build custom parent processes (i.e. supervisors with custom logic).
Examples
Section titled “Examples”Basic supervisor
Section titled “Basic supervisor”Parent.Supervisor.start_link( # child spec is a superset of supervisor child specification child_specs,
# parent options, note that there's no `:strategy` max_restarts: 3, max_seconds: 5,
# std. Supervisor/GenServer options name: __MODULE__)Binding lifecycles
Section titled “Binding lifecycles”Parent.Supervisor.start_link( [ Parent.child_spec(Child1), Parent.child_spec(Child2, binds_to: [Child1]), Parent.child_spec(Child3, binds_to: [Child1]), Parent.child_spec(Child4, shutdown_group: :children4_to_6), Parent.child_spec(Child5, shutdown_group: :children4_to_6), Parent.child_spec(Child6, shutdown_group: :children4_to_6), Parent.child_spec(Child7, binds_to: [Child1]), ])- if
Child1is restarted,Child2,Child3, andChild7will be restarted too - if
Child2,Child3, orChild7is restarted, nothing else is restarted - if any of
Child4,Child5, orChild6is restarted, all other processes from the shutdown group are restarted too
Discovering siblings during startup
Section titled “Discovering siblings during startup”Parent.Supervisor.start_link( [ Parent.child_spec(Child1), Parent.child_spec(Child2, binds_to: [Child1]), # ... ])
defmodule Child2 do def start_link do # can be safely invoked inside the parent process child1 = Parent.child_pid(:child1)
# ... endendPausing and resuming a part of the system
Section titled “Pausing and resuming a part of the system”# stops child1 and all children depending on it, removing it from the parentstopped_children = Parent.Client.shutdown_child(some_parent, :child1)
# ...
# returns all stopped children back to the parentParent.Client.return_children(some_parent, stopped_children)Dynamic supervisor with anonymous children
Section titled “Dynamic supervisor with anonymous children”Parent.Supervisor.start_link([], name: MySup)
# set `ephemeral?: true` for dynamic children if child is temporary/transient{:ok, pid1} = Parent.Client.start_child(MySup, Parent.child_spec(Child, id: nil, ephemeral?: true)){:ok, pid2} = Parent.Client.start_child(MySup, Parent.child_spec(Child, id: nil, ephemeral?: true))# ...
Parent.Client.shutdown_child(MySup, pid1)Parent.Client.restart_child(MySup, pid2)Dynamic supervisor with child discovery
Section titled “Dynamic supervisor with child discovery”Parent.Supervisor.start_link([], name: MySup)
# meta is an optional value associated with the childParent.Client.start_child(MySup, Parent.child_spec(Child, id: id1, ephemeral?: true, meta: some_meta))Parent.Client.start_child(MySup, Parent.child_spec(Child, id: id2, ephemeral?: true, meta: another_meta))# ...
# synchronous calls into the parent processpid = Parent.Client.child_pid(MySup, id1)meta = Parent.Client.child_meta(MySub, id1)all_children = Parent.Client.children(MySup)Optional ETS-powered registry:
Parent.Supervisor.start_link([], registry?: true)
# start some children
# ETS lookup, no call into parent involvedParent.Client.child_pid(my_sup, id1)Parent.Client.children(my_sup)Per-child max restart frequency
Section titled “Per-child max restart frequency”Parent.Supervisor.start_link( [ Parent.child_spec(Child1, max_restarts: 10, max_seconds: 10), Parent.child_spec(Child2, max_restarts: 3, max_seconds: 5) ],
# Per-parent max restart frequency can be disabled, or a parent-wide limit can be used. In the # former case make sure that this limit is higher than the limit of any child. max_restarts: :infinity)Module-based supervisor
Section titled “Module-based supervisor”defmodule MySup do use Parent.GenServer
def start_link(init_arg), do: Parent.GenServer.start_link(__MODULE__, init_arg, name: __MODULE__)
@impl GenServer def init(_init_arg) do Parent.start_all_children!(children) {:ok, initial_state} endendRestarting with a delay
Section titled “Restarting with a delay”defmodule MySup do use Parent.GenServer
def start_link(init_arg), do: Parent.GenServer.start_link(__MODULE__, init_arg, name: __MODULE__)
@impl GenServer def init(_init_arg) do # Make sure that children are temporary and ephemeral b/c otherwise `handle_stopped_children/2` # won't be invoked. Parent.start_all_children!(children) {:ok, initial_state} end
@impl Parent.GenServer def handle_stopped_children(stopped_children, state) do # invoked when a child stops and is not restarted Process.send_after(self, {:restart, stopped_children}, delay) {:noreply, state} end
def handle_info({:restart, stopped_children}, state) do # Returns the child to the parent preserving its place according to startup order and bumping # its restart count. This is basically a manual restart. Parent.return_children(stopped_children) {:noreply, state} endendStarting additional children after a child stops
Section titled “Starting additional children after a child stops”defmodule MySup do use Parent.GenServer
def start_link(init_arg), do: Parent.GenServer.start_link(__MODULE__, init_arg, name: __MODULE__)
@impl GenServer def init(_init_arg) do Parent.start_child(first_child_spec) {:ok, initial_state} end
@impl Parent.GenServer def handle_stopped_children(%{child1: info}, state) do Parent.start_child(other_children) {:noreply, state} end
def handle_stopped_children(_other, state), do: {:noreply, state}endBuilding a custom parent process or behaviour from scratch
Section titled “Building a custom parent process or behaviour from scratch”defp init_process do Parent.initialize(parent_opts) start_some_children() loop()end
defp loop() do receive do msg -> case Parent.handle_message(msg) do # parent handled the message :ignore -> loop()
# parent handled the message and returned some useful information {:stopped_children, stopped_children} -> handle_stopped_children(stopped_children)
# not a parent message nil -> custom_handle_message(msg) end endendStatus
Section titled “Status”This library has seen production usage in a couple of different projects. However, features such as automatic restarts and ETS registry are pretty fresh (aded in late 2020) and so they haven’t seen any serious production testing yet.
Based on a very quick & shallow test, Parent is about 3x slower and consumes about 2x more memory than DynamicSupervisor.
The API is prone to significant changes.
Compared to supervisor crash reports, the error logging is very basic and probably not sufficient.
License
Section titled “License”[MIT]!(./LICENSE)