Li

Links em Elixir

1 exercício

Sobre Links

Os processos em Elixir são isolados e, por defeito, não partilham nada. Quando um processo filho não ligado rebenta, o processo pai não é afetado.

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 comportamento pode ser alterado ao ligar processos uns aos outros. Se dois processos estiverem ligados, uma falha num processo será propagada para o outro processo. As ligações são bidirecionais.

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

Os processos podem ser criados já ligados ao processo que os invoca com spawn_link/1, que é uma operação atómica, ou podem ser ligados mais tarde com Process.link/1.

Podes verificar que processos estão atualmente ligados a um processo (e ainda em execução) com 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>]

Ligar processos pode ser útil para trabalho paralelizado, quando cada parte do trabalho não deve continuar caso outra parte não consiga terminar.

Capturar saídas

Ligar processos também pode ser usado para supervisionar processos. Se um processo capturar as saídas, não rebenta quando um processo ao qual está ligado rebenta. Em vez disso, recebe uma mensagem sobre o rebentamento. Isto permite-lhe lidar com o rebentamento de forma graciosa, por exemplo, reiniciando o processo que rebentou.

Um processo pode ser configurado para capturar saídas chamando Process.flag(:trap_exit, true). Repara que Process.flag/2 devolve o valor antigo da flag, não o novo.

O valor atual da flag pode ser verificado com Process.info/1.

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

# => false

A mensagem que será enviada ao processo, caso um processo ligado rebente, corresponde ao padrão {:EXIT, from, reason}, em que from é um PID. Se reason for algo diferente do átomo :normal, isso significa que o processo rebentou ou foi eliminado à força.

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

Repara que capturar as saídas torna o processo imune a rebentamentos em todos os processos que lhe estão ligados, e as mensagens de saída podem acumular-se na caixa de correio do processo se ficarem por ler. Por este motivo, deves acompanhar todos os processos ligados a um processo que captura as saídas. Normalmente, os processos que capturam as saídas apenas são responsáveis por supervisionar outros processos e não fazem mais nada.

Na prática, não vais implementar um processo supervisor de raiz. O Elixir fornece uma abstração para este tipo de funcionalidade sob a forma do comportamento Supervisor.

Outras abstrações em torno dos processos, como Agent, GenServer ou Task, fornecem duas formas de os iniciar: start ou start_link.

Editar via GitHub A ligação abre numa nova janela ou separador

Aprende Links