O Poder do Módulo Task do Elixir -Task.Supervisor

felipearaujos

Felipe Araujo

Posted on September 17, 2021

O Poder do Módulo Task do Elixir -Task.Supervisor

Caso prefira em inglês você pode encontrar aqui.

A publicação anterior foi uma intro sobre o módulo task e como ele funciona de uma forma geral.

Agora vamos tornar as coisas um pouco mais complexas em nossa jornada com Task.Supervisor e ver mais alguns conceitos do elixir.

Antes de avançar, vamos dar uma olhada da definição desses conceitos:

Supervisor

Um supervisor é um processo que supervisiona outros processos, que nós chamamos de child processes. Supervisors são usados para construir a estrutura de processos hierárquicos chamados supervision tree. Supervision trees provem tolerância a falha e encapsulam como nossas aplicação iniciam ou desligam.

Processes

No Elixir, todo código é executado dentro de processos. Os processos são isolados uns dos outros, são executados simultaneamente entre si e se comunicam por meio da passagem de mensagens.

Os processos do Elixir não devem ser confundidos com os processos do sistema operacional. Os processos no Elixir são extremamente leves em termos de memória e CPU (mesmo em comparação com os threads usados ​​em muitas outras linguagens de programação). Por causa disso, não é incomum ter dezenas ou mesmo centenas de milhares de processos em execução simultaneamente.

Vamos usar Task.Supervisor para criar processos responsáveis ​​por gerenciar o child process relacionado.

Criando o Playground

O primeiro passo é criar uma nova aplicação elixir com árvore de supervisão, mais informações sobre mix pode ser encontrada na documentação oficial.

mix new newsample --sup
Enter fullscreen mode Exit fullscreen mode

Vamos ver o que foi criado:

  lib
  lib/newsample
  lib/newsample.ex
  lib/newsample/application.ex
test
  test/newsample_test.exs
  test/test_helper.exs
README.md
mix.exs
Enter fullscreen mode Exit fullscreen mode

Nesse momento vamos trabalhar somente com os arquivos lib/newsample.ex e lib/newsample/application.ex.

O próximo passo será criar a mesma função do post anterior dentro do arquivo lib/newsample.ex com algumas pequenas mudanças para facilitar os testes.

Para forçar um comportamento explosivo usar um pattern matching dentro da função say_hello com o valor "alpha":

  def say_hello("alpha" = to), do: raise("Error to say hello to #{to}")

  def say_hello(to) do
    IO.puts("Hello #{to}")
    :ok
  end

  def process() do
    items = ["alpha", "beta", "gama"]

    Enum.map(items, fn item ->
      Task.async(fn ->
        say_hello(item)
      end)
    end)
    |> Enum.map(&Task.await/1)
  end
Enter fullscreen mode Exit fullscreen mode

Hora de usar o elixir's interactive shell.

iex -S mix
Enter fullscreen mode Exit fullscreen mode

E então executar:

iex(1)> self
#PID<0.145.0>
iex(2)> Newsample.process
Hello beta
Hello gama

22:01:54.276 [error] Task #PID<0.148.0> started from #PID<0.145.0> terminating
** (RuntimeError) Error to say hello to alpha
    (newsample 0.1.0) lib/newsample.ex:15: anonymous fn/2 in Newsample.process/1
    (elixir 1.11.3) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
    (elixir 1.11.3) lib/task/supervised.ex:35: Task.Supervised.reply/5
    (stdlib 3.14) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Function: #Function<1.13017213/0 in Newsample.process/1>
    Args: []
** (EXIT from #PID<0.145.0>) shell process exited with reason: an exception was raised:
    ** (RuntimeError) Error to say hello to alpha
        (newsample 0.1.0) lib/newsample.ex:15: anonymous fn/2 in Newsample.process/1
        (elixir 1.11.3) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
        (elixir 1.11.3) lib/task/supervised.ex:35: Task.Supervised.reply/5
        (stdlib 3.14) proc_lib.erl:226: :proc_lib.init_p_do_apply/3

Interactive Elixir (1.11.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> self
#PID<0.151.0>
Enter fullscreen mode Exit fullscreen mode

Como podemos ver a exception foi propagada até a sessão do iex podemos checar comparando o PID antes #PID <0.145.0> e após #PID <0.151.0> executar o código.

Então, como podemos evitar esse comportamento explosivo?

Finalmente chegamos no momento de usar supervised task, hora de abrir o arquivo lib/newsample/application.ex.

  def start(_type, _args) do
    children = []

    opts = [strategy: :one_for_one, name: Newsample.Supervisor]
    Supervisor.start_link(children, opts)
  end
Enter fullscreen mode Exit fullscreen mode

Hora de criar o Task.Supervisor como um child process

  children = [
    {Task.Supervisor, name: Newsample.TaskSupervisor}
  ]
Enter fullscreen mode Exit fullscreen mode

E agora com essa supervised task a function process e inicie como async_no_link. Isso significa que não há vínculo entre esse child process e o parent.

  def process() do
    items = ["alpha", "beta", "gama"]

    Enum.map(items, fn item ->
      Task.Supervisor.async_nolink(Newsample.TaskSupervisor, fn ->
        say_hello(item)
      end)
    end)
    |> Enum.map(&Task.await/1)
  end
Enter fullscreen mode Exit fullscreen mode

Se executarmos novamente tudo vai funcionar sem essa propagação de erro.

iex(1)> self
#PID<0.158.0>
iex(2)> Newsample.process
Hello beta
Hello gama

21:04:48.200 [error] Task #PID<0.161.0> started from #PID<0.158.0> terminating
** (RuntimeError) Error to say hello to alpha
    (newsample 0.1.0) lib/newsample.ex:6: Newsample.say_hello/1
    (elixir 1.11.3) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
    (elixir 1.11.3) lib/task/supervised.ex:35: Task.Supervised.reply/5
    (stdlib 3.14) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Function: #Function<1.39266525/0 in Newsample.process/0>
    Args: []
** (exit) exited in: Task.await(%Task{owner: #PID<0.158.0>, pid: #PID<0.161.0>, ref: #Reference<0.1303494724.713818114.156158>}, 5000)
    ** (EXIT) an exception was raised:
        ** (RuntimeError) Error to say hello to alpha
            (newsample 0.1.0) lib/newsample.ex:6: Newsample.say_hello/1
            (elixir 1.11.3) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
            (elixir 1.11.3) lib/task/supervised.ex:35: Task.Supervised.reply/5
            (stdlib 3.14) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
    (elixir 1.11.3) lib/task.ex:639: Task.await/2
    (elixir 1.11.3) lib/enum.ex:1411: Enum."-map/2-lists^map/1-0-"/2
iex(2)> self
#PID<0.158.0>
Enter fullscreen mode Exit fullscreen mode

Como podemos ver, o PID é o mesmo antes e depois da execução.

Processes e Supervision trees são os componentes importantes do elixir para construir sistemas resiliente e tolerante a falhas.

💖 💪 🙅 🚩
felipearaujos
Felipe Araujo

Posted on September 17, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related