supervisor child startup in elixir

Fact: Supervisors fully serialize the startup of their children. Each child’s init/1 function runs to completion before the supervisor starts the next child.

For example, given modules ModuleA and ModuleB that implement both the init/1 callback and this supervisor:

Supervisor.start_link([
  ModuleA,
  ModuleB
], strategy: :one_for_one)

The ModuleA child process will be started and its init/1 function will run to completion before the ModuleB child process starts. This is true regardless of the strategy used.

so what?

This fact can often simplify application startup and error handling scenarios. For example, consider a situation where several processes need a token to interact with an API. The application must acquire this token on startup. If the token expires or the server revokes the token the application must acquire a brand new token and distribute it to all the processes.

We can implement that behavior simply and easily using serialization and appropriate crash handling strategy of a supervisor.

defmodule MyApi.TokenManager do
  use GenServer

  def init(_) do
    token = fetch_token_from_api!()
    {:ok, token}
  end

  def handle_call(:get_token, _from, token) do
    {:reply, token, token}
  end
end

defmodule MyApi.ThingDoer do
  use GenServer

  def handle_call(:do_thing, _from, _state) do
    token = MyApi.TokenManager.get_token()

    # do stuff with token; crash if it doesn't work
  en
end

defmodule MyApi.OtherThingDoer do
  use GenServer

  def handle_call(:do_other_thing, _from, _state) do
    token = MyApi.TokenManager.get_token()

    # do stuff with token; crash if it doesn't work
  en
end

Supervisor.start_link([
  MyApi.TokenManager,
  MyApi.ThingDoer,
  MyApi.OtherThingDoer
], strategy: :one_for_all)

In this example MyApi.TokenManager.init/1 acquires the token before returning. That means the token is ready by the time MyApi.ThingDoer and MyApi.OtherThingDoer start. If at any point to the API server revokes the token, or it expires, the next thing doer to try to use it can just crash. That crash will cause the supervisor to shutdown the remaining children and restart them all beginning with MyApi.TokenManager which will acquire a new, working token.

With this approach MyApi.ThingDoer and MyApi.OtherThingDoer don’t need any specific error handling code around token management. The removal of situation-specific error handling logic makes them simpler and more reliable.

This makes me sad:


iex(10)> Δz = 1
** (SyntaxError) iex:10: 
  unexpected token: "Δ" (column 1, codepoint U+0394)