O Poder do Módulo Task do Elixir -Task.Supervisor
Felipe Araujo
Posted on September 17, 2021
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:
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.
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
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 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
Hora de usar o elixir's interactive shell.
iex -S mix
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>
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
Hora de criar o Task.Supervisor
como um child process
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.
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.
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.
Posted on September 17, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.