O Poder do Módulo Task do Elixir -Task.Supervisor
Tags: elixir, braziliandevs, task, async
Published: 2021-09-17 20:09:38

Caso prefira em inglês você pode encontrar [aqui](https://dev.to/felipearaujos/the-power-of-elixir-task-module-into-task-supervisor-5365).

A  [publicação anterior](https://dev.to/felipearaujos/pt-br-o-poder-do-modulo-task-do-elixir-o-inicio-3eh6) 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**](https://hexdocs.pm/elixir/Supervisor.html)
> 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**](https://elixir-lang.org/getting-started/processes.html#:~:text=In%20Elixir%2C%20all%20code%20runs,and%20communicate%20via%20message%20passing.&text=Processes%20in%20Elixir%20are%20extremely,in%20many%20other%20programming%20languages) 
> 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](https://hexdocs.pm/mix/Mix.html).

```elixir
mix new newsample --sup
```

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
```

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](https://dev.to/felipearaujos/pt-br-o-poder-do-modulo-task-do-elixir-o-inicio-3eh6) 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":

```elixir
  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
```
Hora de usar o [elixir's interactive shell](https://hexdocs.pm/iex/IEx.html).
```
iex -S mix
```

E então executar:

```elixir
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>
```

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`.

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

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

Hora de criar o `Task.Supervisor` como um  *child process*

```elixir
  children = [
    {Task.Supervisor, name: Newsample.TaskSupervisor}
  ]
```
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*.

```elixir
  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
```

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

```elixir
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>
```

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.