Request a free audit

Specialising in Digital positioning and marketing, we tackle challenging questions that yield tangible value. By the end, you’ll receive actionable tips ready for immediate implementation. Take advantage of our complimentary, no-obligation complete audits.

Social media audit Problem definition workshop ISO/SOC readiness

 

Elixir runtime processes

Date
July 5, 2017
Hot topics 🔥
Tech Insights
Contributor
David Roman
Elixir runtime processes

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.

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

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.

And that’s all folks! Thanks for reading!

David Roman

David is one our marketing gurus. He loves working with content but has a good eye for marketing analytics as well. Creativity is what drives him, photography being one of his passions.

Working Machines

An executive’s guide to AI and Intelligent Automation. Working Machines takes a look at how the renewed vigour for the development of Artificial Intelligence and Intelligent Automation technology has begun to change how businesses operate.