Skip to content

Instantly share code, notes, and snippets.

@ukchukx
Forked from moklett/task1.exs
Created June 24, 2020 15:39
Show Gist options
  • Save ukchukx/d0bafea6469e779fcdd37060dc85b757 to your computer and use it in GitHub Desktop.
Save ukchukx/d0bafea6469e779fcdd37060dc85b757 to your computer and use it in GitHub Desktop.
Elixir Task - Crash Handling
# This demonstrates that, when using async/await, a crash in the task will crash the caller
defmodule Tasker do
def good(message) do
IO.puts message
end
def bad(message) do
IO.puts message
raise "I'm BAD!"
end
end
IO.puts "Starting Async GOOD"
a = Task.async(Tasker, :good, ["-> Async GOOD"])
IO.puts "Starting Async BAD"
b = Task.async(Tasker, :bad, ["-> Async BAD"])
:timer.sleep 500
IO.puts "Awaiting both"
Task.await(a)
Task.await(b)
# % elixir task1.exs
# Starting Async GOOD
# Starting Async BAD
# -> Async GOOD
# -> Async BAD
# ** (EXIT from #PID<0.47.0>) an exception was raised:
# ** (RuntimeError) I'm BAD!
# task1.exs:8: Tasker.bad/1
# (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
# (elixir) lib/task/supervised.ex:40: Task.Supervised.reply/5
# (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
#
# 09:06:08.269 [error] Task #PID<0.54.0> started from #PID<0.47.0> terminating
# ** (RuntimeError) I'm BAD!
# task1.exs:8: Tasker.bad/1
# (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
# (elixir) lib/task/supervised.ex:40: Task.Supervised.reply/5
# (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
# Function: &Tasker.bad/1
# Args: ["-> Async BAD"]
# This demonstrates that, when using async/yield, a crash in the task will crash the caller.
# i.e. `async` always links the processes! Note that "I'm still alive" is not printed
defmodule Tasker do
def good(message) do
IO.puts message
end
def bad(message) do
IO.puts message
raise "I'm BAD!"
end
end
IO.puts "Starting Async GOOD"
a = Task.async(Tasker, :good, ["-> Async GOOD"])
IO.puts "Starting Async BAD"
b = Task.async(Tasker, :bad, ["-> Async BAD"])
:timer.sleep 500
IO.puts "Awaiting both"
Task.yield(a)
Task.yield(b)
IO.puts "I'm still alive"
# % elixir task2.exs
# Starting Async GOOD
# Starting Async BAD
# -> Async GOOD
# -> Async BAD
# ** (EXIT from #PID<0.47.0>) an exception was raised:
# ** (RuntimeError) I'm BAD!
# task2.exs:9: Tasker.bad/1
# (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
# (elixir) lib/task/supervised.ex:40: Task.Supervised.reply/5
# (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
#
# 09:08:23.912 [error] Task #PID<0.54.0> started from #PID<0.47.0> terminating
# ** (RuntimeError) I'm BAD!
# task2.exs:9: Tasker.bad/1
# (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
# (elixir) lib/task/supervised.ex:40: Task.Supervised.reply/5
# (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
# Function: &Tasker.bad/1
# Args: ["-> Async BAD"]
#
# This demonstrates that, when using start_link, the caller will not wait around
# on the task to finish - the caller will finish regardless of what happens in the
# task.
defmodule Tasker do
def good(message) do
:timer.sleep 500
IO.puts message
end
def bad(message) do
:timer.sleep 500
IO.puts message
raise "I'm BAD!"
IO.puts "After CRASH"
end
end
IO.puts "Starting start_link GOOD"
{:ok, pid1} = Task.start_link(Tasker, :good, ["-> Start Link GOOD"])
IO.puts "Starting start_link BAD"
{:ok, pid2} = Task.start_link(Tasker, :bad, ["-> Start Link BAD"])
IO.puts "PIDS:"
IO.inspect pid1
IO.inspect pid2
# There is no way to wait!
# % elixir task3.exs
# Starting start_link GOOD
# Starting start_link BAD
# PIDS:
# #PID<0.53.0>
# #PID<0.54.0>
# This demonstrates that, when using start_link, the task can crash the caller
# if the caller is still running.
defmodule Tasker do
def good(message) do
IO.puts message
end
def bad(message) do
IO.puts message
raise "I'm BAD!"
IO.puts "After CRASH"
end
end
IO.puts "Starting start_link GOOD"
{:ok, pid1} = Task.start_link(Tasker, :good, ["-> Start Link GOOD"])
IO.puts "Starting start_link BAD"
{:ok, pid2} = Task.start_link(Tasker, :bad, ["-> Start Link BAD"])
:timer.sleep(1000)
IO.puts "PIDS:"
IO.inspect pid1
IO.inspect pid2
# % elixir task4.exs
# Starting start_link GOOD
# Starting start_link BAD
# -> Start Link GOOD
# -> Start Link BAD
# ** (EXIT from #PID<0.47.0>) an exception was raised:
# ** (RuntimeError) I'm BAD!
# task4.exs:10: Tasker.bad/1
# (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
# (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
#
# 09:19:30.200 [error] Task #PID<0.54.0> started from #PID<0.47.0> terminating
# ** (RuntimeError) I'm BAD!
# task4.exs:10: Tasker.bad/1
# (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
# (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
# Function: &Tasker.bad/1
# Args: ["-> Start Link BAD"]
# This demonstrates that, when using start, the task does not crash the caller
# (but the exception status is printed to the screen)
defmodule Tasker do
def good(message) do
IO.puts message
end
def bad(message) do
IO.puts message
raise "I'm BAD!"
IO.puts "After CRASH"
end
end
IO.puts "Starting start GOOD"
{:ok, pid1} = Task.start(Tasker, :good, ["-> Start GOOD"])
IO.puts "Starting start BAD"
{:ok, pid2} = Task.start(Tasker, :bad, ["-> Start BAD"])
:timer.sleep(1000)
IO.puts "PIDS:"
IO.inspect pid1
IO.inspect pid2
# % elixir task5.exs
# Starting start GOOD
# Starting start BAD
# -> Start GOOD
# -> Start BAD
#
# 09:21:51.944 [error] Task #PID<0.54.0> started from #PID<0.47.0> terminating
# ** (RuntimeError) I'm BAD!
# task5.exs:9: Tasker.bad/1
# (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
# (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
# Function: &Tasker.bad/1
# Args: ["-> Start BAD"]
# PIDS:
# #PID<0.53.0>
# #PID<0.54.0>
# This demonstrates that, when using start, the task does not crash the caller,
# but the tasks do not live past the life of the caller. I've switched from
# `IO.puts` in the tasks to `File.write` to show that the side-effect never
# happens.
defmodule Tasker do
def good(message) do
:timer.sleep(1000)
File.write("./task_good.txt", "#{message} - #{inspect(make_ref)}")
end
def bad(message) do
:timer.sleep(1000)
File.write("./task_bad.txt", "#{message} - #{inspect(make_ref)}")
raise "I'm BAD!"
end
end
IO.puts "Starting start GOOD"
{:ok, pid1} = Task.start(Tasker, :good, ["-> Start GOOD"])
IO.puts "Starting start BAD"
{:ok, pid2} = Task.start(Tasker, :bad, ["-> Start BAD"])
IO.puts "PIDS:"
IO.inspect pid1
IO.inspect pid2
# % elixir task6.exs
# Starting start GOOD
# Starting start BAD
# PIDS:
# #PID<0.53.0>
# #PID<0.54.0>
# % ls task_*.txt
# ls: task_*.txt: No such file or directory
# This demonstrates that, when using async_nolink, the task only crashes
# the caller when we call await. Note that "I'm still alive" is not
# printed
defmodule Tasker do
def good(message) do
:timer.sleep(1000)
IO.puts message
end
def bad(message) do
:timer.sleep(1000)
IO.puts message
raise "I'm BAD!"
end
end
{:ok, pid} = Task.Supervisor.start_link()
IO.puts "Starting async_nolink GOOD"
a = Task.Supervisor.async_nolink(pid, Tasker, :good, ["-> Start GOOD"])
IO.puts "Starting async_nolink BAD"
b = Task.Supervisor.async_nolink(pid, Tasker, :bad, ["-> Start BAD"])
IO.puts "Awaiting both"
Task.await(a)
Task.await(b)
IO.puts "I'm still alive"
# % elixir task7.exs
# Starting async_nolink GOOD
# Starting async_nolink BAD
# Awaiting both
# -> Start GOOD
# -> Start BAD
#
# 09:55:05.130 [error] Task #PID<0.55.0> started from #PID<0.47.0> terminating
# ** (RuntimeError) I'm BAD!
# task7.exs:13: Tasker.bad/1
# (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
# (elixir) lib/task/supervised.ex:40: Task.Supervised.reply/5
# (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
# Function: &Tasker.bad/1
# Args: ["-> Start BAD"]
# ** (exit) exited in: Task.await(%Task{owner: #PID<0.47.0>, pid: #PID<0.55.0>, ref: #Reference<0.0.1.14>}, 5000)
# ** (EXIT) an exception was raised:
# ** (RuntimeError) I'm BAD!
# task7.exs:13: Tasker.bad/1
# (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
# (elixir) lib/task/supervised.ex:40: Task.Supervised.reply/5
# (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
# (elixir) lib/task.ex:332: Task.await/2
# task7.exs:28: (file)
# (elixir) lib/code.ex:363: Code.require_file/2
#
#
# 09:55:05.157 [error] GenServer #PID<0.53.0> terminating
# ** (stop) exited in: Task.await(%Task{owner: #PID<0.47.0>, pid: #PID<0.55.0>, ref: #Reference<0.0.1.14>}, 5000)
# ** (EXIT) an exception was raised:
# ** (RuntimeError) I'm BAD!
# task7.exs:13: Tasker.bad/1
# (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
# (elixir) lib/task/supervised.ex:40: Task.Supervised.reply/5
# (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
# Last message: {:EXIT, #PID<0.47.0>, {{%RuntimeError{message: "I'm BAD!"}, [{Tasker, :bad, 1, [file: 'task7.exs', line: 13]}, {Task.Supervised, :do_apply, 2, [file: 'lib/task/supervised.ex', line: 89]}, {Task.Supervised, :reply, 5, [file: 'lib/task/supervised.ex', line: 40]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 240]}]}, {Task, :await, [%Task{owner: #PID<0.47.0>, pid: #PID<0.55.0>, ref: #Reference<0.0.1.14>}, 5000]}}}
# State: {:state, {#PID<0.53.0>, Supervisor.Default}, :simple_one_for_one, [{:child, :undefined, Task.Supervised, {Task.Supervised, :start_link, []}, :temporary, 5000, :worker, [Task.Supervised]}], {:set, 0, 16, 16, 8, 80, 48, {[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []}, {{[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []}}}, 3, 5, [], Supervisor.Default, {:ok, {{:simple_one_for_one, 3, 5}, [{Task.Supervised, {Task.Supervised, :start_link, []}, :temporary, 5000, :worker, [Task.Supervised]}]}}}
# This demonstrates that, when using async_nolink, the task does not crash
# if we use `yield` to capture results
# Note that "I'm still alive" IS printed, and we get results from the tasks
defmodule Tasker do
def good(message) do
:timer.sleep(1000)
IO.puts message
end
def bad(message) do
:timer.sleep(1000)
IO.puts message
raise "I'm BAD!"
end
end
{:ok, pid} = Task.Supervisor.start_link()
IO.puts "Starting async_nolink GOOD"
a = Task.Supervisor.async_nolink(pid, Tasker, :good, ["-> Start GOOD"])
IO.puts "Starting async_nolink BAD"
b = Task.Supervisor.async_nolink(pid, Tasker, :bad, ["-> Start BAD"])
IO.puts "Awaiting both"
res_a = Task.yield(a)
res_b = Task.yield(b)
IO.puts "I'm still alive"
IO.puts "Result A: #{inspect(res_a)}"
IO.puts "Result B: #{inspect(res_b)}"
# % elixir task8.exs
# Starting async_nolink GOOD
# Starting async_nolink BAD
# Awaiting both
# -> Start GOOD
# -> Start BAD
# I'm still alive
# Result A: {:ok, :ok}
#
# 09:57:48.659 [error] Task #PID<0.55.0> started from #PID<0.47.0> terminating
# ** (RuntimeError) I'm BAD!
# task8.exs:13: Tasker.bad/1
# (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
# (elixir) lib/task/supervised.ex:40: Task.Supervised.reply/5
# (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
# Function: &Tasker.bad/1
# Args: ["-> Start BAD"]
# Result B: {:exit, {%RuntimeError{message: "I'm BAD!"}, [{Tasker, :bad, 1, [file: 'task8.exs', line: 13]}, {Task.Supervised, :do_apply, 2, [file: 'lib/task/supervised.ex', line: 89]}, {Task.Supervised, :reply, 5, [file: 'lib/task/supervised.ex', line: 40]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 240]}]}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment