Elixir runtime processes

Written by: David Roman

icon of a water droplet

By Sergey Gernyak, Back-end Engineer.


Mostly when you create your new Elixir application you’ll run the following command:

mix new my_app --sup

It generates a blank project with a root supervisor. For example:

defmodule SomeApp do
  use Application
  import Supervisor.Spec
  def start(_type, _args) do
    import Supervisor.Spec, warn: false
    children = [
      # worker(DynamicProcesses.Worker, [arg1, arg2, arg3]),
    ]
    opts = [strategy: :one_for_one, name: SomeApp.Supervisor]      
    Supervisor.start_link(children, opts)
  end
end

In the children array you can specify a list of workers (or supervisors) that will be supervised by the root supervisor.

All those settings are static. But you’re also able do that in runtime. In order to do this you’ll need several functions from the Elixir’s core: Supervisor.start_child/2Supervisor.Spec.supervisor/3 and Supervisor.Spec.worker/3. Let’s go through some examples. All of these examples are taken from this project on github.

Start supervisor in runtime

The algorithm is very simple:

  1. Create the specification
  2. Consider what the process should be for the root of a new one
  3. Start a new child

To create the specification Supervisor.Spec.supervisor/3 function is used. Let’s take a look at the example:

defmodule DynamicProcesses do
  use Application

  def start(_type, _args) do
    import Supervisor.Spec, warn: false
    
    children = [
    ]

    opts = [strategy: :one_for_one, name: DynamicProcesses.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
defmodule DynamicProcesses.Examples do
  alias DynamicProcesses.SomeSupervisor

  import Supervisor.Spec

  def add_single_supervisor(id \\ "1") do
    {:ok, supervisor_spec} = build_supervisor_spec(SomeSupervisor, [], id)
    Supervisor.start_child(DynamicProcesses.Supervisor, supervisor_spec)
  end

  defp build_supervisor_spec(module, args, id) do
    supervisor_spec = supervisor(module, args, [id: "supervisor" <> id])
    {:ok, supervisor_spec}
  end
end
defmodule DynamicProcesses.SomeSupervisor do
  use Supervisor

  def start_link do
    Supervisor.start_link(__MODULE__, [])
  end

  def init([]) do
    children = []

    supervise(children, strategy: :one_for_all)
  end
end

After running DynamicProcesses.Example.add_single_supevisor you will get a new supervisor which is supervised by the application root one.

Run observer by running :observer.start in the iex session. After starting dynamic_processes application the processes tree looks like this:

And after adding a new supervisor it looks like this:

Double click on the <0.151.0> (it could be different in your case) and check that it is the right process (I mean that the right module is its base ):

Start workers in runtime

The algorithm for starting supervisors is the same however, for building specifications a different function is used. Lets consider an example:

defmodule DynamicProcesses.Examples do
  alias DynamicProcesses.{SomeSupervisor, SomeWorker}

  import Supervisor.Spec

  def add_single_supervisor(id \\ "1") do
    {:ok, supervisor_spec} = build_supervisor_spec(SomeSupervisor, [], id)
    Supervisor.start_child(DynamicProcesses.Supervisor, supervisor_spec)
  end

  def add_supervisor_with_workers do
    {:ok, supervisor_pid} = add_single_supervisor
    {:ok, worker_spec1} = build_worker_spec(SomeWorker, [], "1")
    {:ok, worker_spec2} = build_worker_spec(SomeWorker, [], "2")
    Supervisor.start_child(supervisor_pid, worker_spec1)
    Supervisor.start_child(supervisor_pid, worker_spec2)
  end

  defp build_supervisor_spec(module, args, id) do
    supervisor_spec = supervisor(module, args, [id: "supervisor" <> id])
    {:ok, supervisor_spec}
  end

  defp build_worker_spec(module, args, id) do
    worker_spec = worker(module, args, [id: "worker" <> id])
    {:ok, worker_spec}
  end
end
defmodule DynamicProcesses.SomeWorker do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, %{})
  end
end

Take a look at DynamicProcesses.Examples.add_supervisor_with_workers/0 function. Here we do the following:

  1. Create a supervisor
  2. Create 2 workers specifications
  3. Start 2 workers and attach them to the supervisor created in step 1
  4. After that the processes tree looks like this:

Here <0.114.0> is our new supervisor, <0.115.0> and <0.116.0> are its child workers.

Why do we need to pass some id parameter?

As I understand it, by default you can only create one single process for a module. Perhaps it’s because Elixir uses the name of a module as a process identifier. So if you try to create several processes without specifying an id you will get a result like this:

After you have specified ids you will get a successful result:

And that’s all folks! Thanks for reading!

(Visited 91 times, 1 visits today)
Last modified: April 17, 2020
Author info
David Roman
David is our content & social media specialist. He helps with the planning, creation, and distribution of all the social content at WeAreBrain. He also loves green tea, listening to good music and using his analogue camera.
Close