Do you want to pick up from where you left of?
Take me there

OTP Supervisors

Supervisors sind besondere Prozesse mit einem Zweck: andere Prozesse zu überwachen. Diese Supervisors erlauben uns fehlertolerante Anwendungen zu erstellen, indem sie Kindprozesse automatisch neu starten, falls diese versagen.

Konfiguration

Die Magie von Supervisors liegt in der Supervisor.start_link/2-Funktion. Zusätzlich zum Starten unserer Supervisors und Kinder erlaubt sie uns die Strategie zu bestimmen, mit der unser Supervisor Kindprozesse verwaltet.

Wir benutzen SimpleQueue aus der Lektion OTP Concurrency und legen los:

Erstelle ein neues Projekt mit dem Befehl mix new simple_queue --sup um ein Projekt mit einem Supervisor-Baum zu erzeugen. Der Code für das Modul SimpleQueue sollte in lib/simple_queue.ex liegen und der Supervisor Code, den wir hinzufügen werden, soll in lib/simple_queue/application.ex liegen.

Kinder werden werden in einer Liste definiert, entweder eine Liste von Modulnamen:

import Supervisor.Spec

children = [
  worker(SimpleQueue, [], name: SimpleQueue)
]

{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)

oder eine Liste von Tupeln, falls du Konfigurations-Optionen mitgeben willst:

defmodule SimpleQueue.Application do
  use Application

  def start(_type, _args) do
    children = [
      {SimpleQueue, [1, 2, 3]}
    ]

    opts = [strategy: :one_for_one, name: SimpleQueue.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Wenn wir iex -S mix ausführen, sehen wir, dass unsere SimpleQueue automatisch gestartet wird:

iex> SimpleQueue.queue
[1, 2, 3]

Falls unser SimpleQueue Prozess abstürzt oder sonstwie terminiert, würde unser Supervisor ihn automatisch neu starten, als ob nichts gewesen wäre.

Strategien

Es gibt zurzeit drei verschiedene Strategien zum Neustart, welche Supervisors benutzen können:

Kind-Spezfikation

Nachdem ein Supervisor gestartet ist, muss er wissen, wie er seine Kinder starten/stoppen/neustarten soll. Jedes Kind-Modul sollte eine child_spec/1 Funktion haben, die dieses Verhalten definiert. Die Macros use GenServer, use Supervisor, und use Agent definieren diese Methode automatisch für uns (SimpleQueue hat use GenServer, also brauchen wir dieses Modul nicht anzupassen), aber wenn du sie selbst definieren musst, sollte child_spec/1 eine Map von Optionen zurückgeben:

def child_spec(opts) do
  %{
    id: SimpleQueue,
    start: {__MODULE__, :start_link, [opts]},
    shutdown: 5_000,
    restart: :permanent,
    type: :worker
  }
end

Standardwert für den Prozesstyp :supervisor. Nicht empfohlen für den Typ :worker.

Standard für alle Prozesse

DynamicSupervisor

Supervisors starten normalerweise mit einer Liste von Kindern, die sie starten, wenn die App startet. Manchmal jedoch sind die beausichtigten Kinder beim App-Start nicht bekannt (zum Beispiel könnten wir eine Web-App haben, die einen neuen Prozess startet, wenn sich ein Nutzer mit unserer Webseite verbindet.) Für diese Fälle brauchen wir eine Supervisor, der Kinder bei Bedarf starten kann. In diesem Fall benutzen wir den DynamicSupervisor.

Da wir keine Kinder spezifizieren, müssen wir nur die Laufzeit-Optionen für den Supervisor bestimmen. Der DynamicSupervisor unterstüzt nur die Supervison-Strategie :one_for_one:

options = [
  name: SimpleQueue.Supervisor,
  strategy: :one_for_one
]

DynamicSupervisor.start_link(options)

Um eine neue SimpleQueue dynamisch zu starten, nutzen wir dann die Funktion start_child/2, welche einen Supervisor und eine Kind-Spezifikation als Argumente nimmt (SimpleQueue nutzt use GenServer, die Kind-Spezifikation ist daher schon definiert):

{:ok, pid} = DynamicSupervisor.start_child(SimpleQueue.Supervisor, SimpleQueue)

Task Supervisor

Tasks haben ihren eigenen spezialisierten Supervisor, Task.Supervisor. Dieser Supervisor wurde für dynamisch erstellte Tasks entworfen und benutzt DynamicSupervisor unter der Haube.

Setup

Task.Supervisor zu benutzen ist nicht anders wie andere Supervisors:

children = [
  {Task.Supervisor, name: ExampleApp.TaskSupervisor, restart: :transient}
]

{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)

Der Hauptunterschied zwischen Supervisor und Task.Supervisor ist, dass die standardmäßige Strategie zum Neustart :temporary ist (Tasks würden nie neu gestartet werden).

Supervised Tasks

Wenn der Supervisor gestartet ist, können wir die Funktion start_child/2 benutzen, um einen supervised Task zu erstellen:

{:ok, pid} = Task.Supervisor.start_child(ExampleApp.TaskSupervisor, fn -> background_work end)

Falls unser Task vorzeitig abstürzt, wird er für uns neu gestartet. Das kann besonders sinnvoll sein, wenn wir mit eingehenden Verbindungen arbeiten oder Hintergrundarbeit verrichten.

Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!