Skip to content

Supervising dynamic children ​

Source: Elixir guide, Supervising dynamic children.

Buckets are named. Now they need to be supervised, and created at any time.

Child specs ​

A supervisor learns how to start a child from its child specification. A module or {module, options} in the children list is shorthand for calling that module's child_spec/1:

elixir
Registry.child_spec(name: KV, keys: :unique)
#=> %{id: KV,
#     start: {Registry, :start_link, [[name: KV, keys: :unique]]},
#     type: :supervisor}

:id and :start (a module, function, args triplet) are required. type and others are optional.

A tuple is shorthand. The map is what the supervisor uses.

use Agent (and use GenServer, use Supervisor) define a default child_spec/1, so KV.Bucket is supervisable already:

elixir
KV.Bucket.child_spec(name: :shopping)
#=> %{id: KV.Bucket, start: {KV.Bucket, :start_link, [[name: :shopping]]}}

Supervisor.start_link([{KV.Bucket, name: :shopping}], strategy: :one_for_one)
pid = Process.whereis(:shopping)
Process.exit(pid, :kill)
Process.whereis(:shopping)    #=> a new pid
Kill the bucket and the supervisor starts a new one under the same name.

Dynamic supervisors ​

Listing buckets in start/2 starts a fixed set. Users must be able to create buckets at any time. Supervisor can add children later, but it wasn't built for millions of them. DynamicSupervisor is: children are started after the supervisor.

elixir
{:ok, sup} = DynamicSupervisor.start_link(strategy: :one_for_one)
DynamicSupervisor.start_child(sup, {KV.Bucket, name: :another_list})

Give it a name too, and combine it with the registry. Then put both in the tree and add two functions to KV:

elixir
def start(_type, _args) do
  children = [
    {Registry, name: KV, keys: :unique},
    {DynamicSupervisor, name: KV.BucketSupervisor, strategy: :one_for_one}
  ]

  Supervisor.start_link(children, strategy: :one_for_one)
end

def create_bucket(name) do
  DynamicSupervisor.start_child(KV.BucketSupervisor, {KV.Bucket, name: via(name)})
end

def lookup_bucket(name), do: GenServer.whereis(via(name))

defp via(name), do: {:via, Registry, {KV, name}}
The dynamic supervisor is a fixed child. Its own children come and go.

A second create_bucket/1 with the same name returns {:error, {:already_started, pid}}. Tests use a unique name to avoid clashes.

start_supervised ​

Don't call start_link/1 in tests. ExUnit starts a supervision tree per test and start_supervised/2 puts processes in it, so they're shut down when the test ends:

elixir
{:ok, bucket} = start_supervised(KV.Bucket)
{:ok, _} = start_supervised({KV.Bucket, name: config.test})

Observer ​

:observer.start() (after Mix.ensure_application!(:observer) inside a project) opens a GUI. Its Applications tab draws each running application's supervision tree, and new buckets appear as you create them. Right-click a process to send a kill signal, a way to check that your supervisor reacts. That is why processes belong in supervision trees even if temporary: they stay reachable and introspectable.

Site code: MIT. Pages and diagrams are derived from the Elixir documentation (Apache-2.0). Not affiliated with the Elixir Team.