En

Enlaces en Elixir

1 ejercicio

Acerca de Enlaces

Los procesos de Elixir están aislados y, de forma predeterminada, no comparten nada. Cuando un proceso hijo que no está enlazado falla, el proceso padre no se ve afectado.

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

Este comportamiento se puede cambiar enlazando procesos entre sí. Si dos procesos están enlazados, una falla en uno de ellos se propagará al otro proceso. Los enlaces son bidireccionales.

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

Los procesos se pueden crear ya enlazados al proceso que los invoca usando spawn_link/1, que es una operación atómica, o se pueden enlazar más adelante con Process.link/1.

Puedes consultar qué procesos están enlazados actualmente a un proceso (y siguen en ejecución) con Process.info/1.

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

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

# => [#PID<0.126.0>]

Enlazar procesos puede ser útil cuando haces trabajo en paralelo y cada parte del trabajo no debería continuar si otra parte no logra terminar.

Capturar salidas

El enlace también se puede usar para supervisar procesos. Si un proceso captura las salidas, no fallará cuando falle un proceso al que está enlazado. En su lugar, recibirá un mensaje sobre la falla. Esto le permite manejar la falla de forma ordenada, por ejemplo, reiniciando el proceso que falló.

Un proceso se puede configurar para capturar salidas llamando a Process.flag(:trap_exit, true). Ten en cuenta que Process.flag/2 devuelve el valor anterior de la bandera, no el nuevo.

El valor actual de la bandera se puede consultar con Process.info/1.

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

# => false

El mensaje que se enviará al proceso cuando falle un proceso enlazado coincidirá con el patrón {:EXIT, from, reason}, donde from es un PID. Si reason es algo distinto del átomo :normal, eso significa que el proceso falló o fue eliminado por la fuerza.

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

Ten en cuenta que capturar salidas hace que el proceso sea inmune a las fallas de todos sus procesos enlazados, y los mensajes de salida podrían acumularse en el buzón del proceso si no se leen. Por eso, deberías llevar un registro de todos los procesos enlazados a un proceso que captura salidas. Por lo general, los procesos que capturan salidas solo se encargan de supervisar otros procesos y no hacen nada más.

En la práctica, no implementarías tú mismo un proceso supervisor desde cero. Elixir ofrece una abstracción para este tipo de funcionalidad: el comportamiento Supervisor.

Otras abstracciones en torno a los procesos, como Agent, GenServer o Task, ofrecen dos formas de iniciarlos: start o start_link.

Editar en GitHub El enlace se abre en una ventana o pestaña nueva

Aprende Enlaces