Li

Links in Elixir

1 Übung

Über Links

Elixir-Prozesse sind isoliert und teilen standardmäßig nichts miteinander. Wenn ein nicht verknüpfter Kindprozess abstürzt, hat das keine Auswirkungen auf seinen Elternprozess.

parent_pid =
  spawn(fn ->
    spawn(fn -> raise "oops" end)

    receive do
      message -> IO.inspect(message, label: "received message")
    end
  end)

# => 20:03:08.405 [error] Process #PID<0.153.0> raised an exception
#    ** (RuntimeError) oops
#    (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6

Process.alive?(parent_pid)
# => true

Dieses Verhalten lässt sich ändern, indem man Prozesse miteinander verknüpft. Sind zwei Prozesse verknüpft, wird ein Fehler in einem Prozess an den anderen Prozess weitergegeben. Verknüpfungen sind bidirektional.

parent_pid =
  spawn(fn ->
    spawn_link(fn -> raise "oops" end)

    receive do
      message ->
        IO.inspect(message, label: "received message")
    end
  end)

# => 20:05:34.125 [error] Process #PID<0.171.0> raised an exception
#    ** (RuntimeError) oops
#    (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6

Process.alive?(parent_pid)
# => false

Du kannst Prozesse mit spawn_link/1 gleich verknüpft mit dem aufrufenden Prozess starten. Das ist eine atomare Operation. Alternativ verknüpfst du sie später mit Process.link/1.

Mit Process.info/1 kannst du prüfen, welche Prozesse aktuell mit einem Prozess verknüpft sind (und noch laufen).

pid = spawn_link(fn -> :timer.sleep(50_000) end)
# => #PID<0.126.0>

self()
|> Process.info()
|> Keyword.get(:links)

# => [#PID<0.126.0>]

Das Verknüpfen von Prozessen ist bei parallelisierter Arbeit nützlich, wenn ein Arbeitspaket nicht weitergeführt werden soll, falls ein anderes nicht fertig wird.

Exits abfangen

Verknüpfungen lassen sich auch zur Überwachung von Prozessen einsetzen. Wenn ein Prozess Exits abfängt, stürzt er nicht ab, wenn ein mit ihm verknüpfter Prozess abstürzt. Stattdessen erhält er eine Nachricht über den Absturz. So kann er den Absturz sauber behandeln, zum Beispiel indem er den abgestürzten Prozess neu startet.

Du kannst einen Prozess so konfigurieren, dass er Exits abfängt, indem du Process.flag(:trap_exit, true) aufrufst. Beachte, dass Process.flag/2 den alten Wert des Flags zurückgibt, nicht den neuen.

Den aktuellen Wert des Flags kannst du mit Process.info/1 prüfen.

pid
|> Process.info()
|> Keyword.get(:trap_exit)

# => false

Die Nachricht, die der Prozess erhält, wenn ein verknüpfter Prozess abstürzt, entspricht dem Muster {:EXIT, from, reason}, wobei from eine PID ist. Wenn reason etwas anderes als das Atom :normal ist, bedeutet das, dass der Prozess abgestürzt oder gewaltsam beendet wurde.

parent_pid =
  spawn(fn ->
    Process.flag(:trap_exit, true)

    spawn_link(fn -> raise "oops" end)

    receive do
      message ->
        IO.inspect(message, label: "received message")

        # do something else,
        # to demonstrate that the process stays alive
        :timer.sleep(50_000)
    end
  end)

# => 20:05:34.125 [error] Process #PID<0.295.0> raised an exception
#    ** (RuntimeError) oops
#    (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6

# received message: {:EXIT, #PID<0.295.0>,
#   {%RuntimeError{message: "oops"},
#     [{:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 678]}]}}

Process.alive?(parent_pid)
# => true

Beachte, dass das Abfangen von Exits den Prozess gegen Abstürze in allen seinen verknüpften Prozessen immun macht. Außerdem können sich Exit-Nachrichten in der Mailbox des Prozesses ansammeln, wenn sie nicht gelesen werden. Deshalb solltest du alle Prozesse im Blick behalten, die mit einem Prozess verknüpft sind, der Exits abfängt. Üblicherweise sind Prozesse, die Exits abfangen, nur für die Überwachung anderer Prozesse zuständig und machen sonst nichts.

In der Praxis würdest du einen Supervisor-Prozess nicht selbst von Grund auf implementieren. Elixir bietet für solche Funktionalität eine Abstraktion in Form des Supervisor-Behaviour.

Andere Abstraktionen rund um Prozesse wie Agent, GenServer oder Task bieten zwei Arten, sie zu starten: entweder start oder start_link.

Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab

Lerne Links