When a process terminates, it will terminate with an exit reason. This
exit reason is emitted in an exit signal to all linked processes.
The default behavior when a process receives an exit signal with an
exit reason other than normal, is to terminate and in turn emit exit
signals with the same exit reason to its linked processes. An exit
signal with reason normal is ignored.
So, just because a process ends, does not necessarily mean that its linked processes will terminate. Rather, a process has to crash to cause its linked processes to crash. Even then, a linked process can "trap exits" to prevent it from crashing.
In the following example, a process starts a new process and links to it, then the original process ends because it has nothing more to do, i.e. the function has no more statements to execute, yet the linked process keeps executing:
defmodule A do
def start do
spawn(A, :do_stuff, [])
end
def do_stuff do #This is the "do_stuff() process"...
spawn_link(A, :ticker, [10]) #...which spawns a second process, the "ticker()" process, and links to it.
IO.puts "exiting do_stuff()" #The "do_stuff()" process ends. Does the "ticker()" process also end now?
end
def ticker(0), do: IO.puts "exiting ticker()"
def ticker(num_ticks) do
:timer.sleep(1000) #sleep for 1 second
IO.write "."
ticker(num_ticks - 1)
end
end
In iex:
iex(1)> A.start
exiting do_stuff
:ok
..........exiting ticker()
iex(2)>
The output shows that the ticker() process continued executing for 10 seconds after the do_stuff() process terminated--see the ten dots?
Compare the output above to the output produced when you change do_stuff() to this:
def do_stuff do
spawn_link(A, :ticker, [10])
:timer.sleep(2000)
raise "I'm crashing"
IO.puts "exiting do_stuff() normally"
end
In iex:
iex(1)> A.start
:ok
..iex(2)>
23:25:25.772 [error] Process #PID<0.116.0> raised an exception
** (RuntimeError) I'm crashing
a.ex:20: A.do_stuff/0
This time there are only two dots.
========= Response to comment:
Here is an example that calls Task.async to run ticker():
defmodule A do
def start do
spawn(A, :do_stuff, [])
:ok
end
def do_stuff do
Task.async(A, :ticker, [10])
#spawn_link(A, :ticker, [10]) #links the process executing do_stuff() and
#:timer.sleep(2000)
#raise "I'm crashing"
IO.puts "exiting do_stuff() normally"
end
def ticker(0), do: IO.puts "exiting ticker()"
def ticker(num_ticks) do
:timer.sleep(1000) #sleep for 1 second
IO.write "."
ticker(num_ticks - 1)
end
end
In iex:
iex(1)> A.start
exiting do_stuff() normally
:ok
..........exiting ticker()
Notice the 10 dots, which means the "ticker() process" executed for 10 seconds after the "do_stuff() process" exited normally.
The Task docs say that you must call Task.await() if you call Task.async(), but there doesn't seem to be any penalty for not calling await(). I guess you get a message in the calling process's mailbox when Task.async() finishes executing, and if you launch a trillion Tasks, then you can overflow the mailbox and cause the process to crash.