Task spawned with async runs despite caller's exit

I have a two part question concerning a module like so:

defmodule AsyncAwait do
  defp long_running_task(id) do
    Process.sleep(5_000)
    IO.puts("Completed #{id}")
    {:ok, "Completed #{id}"}
  end

  def perform_tasks(task_ids \\ [0, 1, 2]) do
    task_ids
    |> Enum.map(fn id ->
      Task.async(fn ->
        long_running_task(id)
      end)
    end)
    |> Enum.map(fn task ->
      IO.inspect(self())
      Task.await(task, 1_000)
    end)
  end
end

When I launch the iex shell and run AsyncAwait.perform_tasks(), I recieve a timeout and an exit as expected, at the await:

** (exit) exited in: Task.await(%Task{owner: #PID<0.151.0>, pid: #PID<0.153.0>, ref: #Reference<0.3029894714.2600271873.129496>}, 1000)
    ** (EXIT) time out
    (elixir 1.13.4) lib/task.ex:809: Task.await/2
    (elixir 1.13.4) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2

I would expect that this kills the caller process, and hence also all the associated task processes that were spawned during the async call. This is what the docs say:

A timeout, in milliseconds or :infinity, can be given with a default value of 5000. If the timeout is exceeded, then the caller process will exit. If the task process is linked to the caller process which is the case when a task is started with async, then the task process will also exit. If the task process is trapping exits or not linked to the caller process, then it will continue to run - Task β€” Elixir v1.13.4

  1. Normally when the caller process exits, I expected it would kill the iex shell since they exist in the same process. I can verify that they exist in the same process since I call IO.inspect(self()) in the anonymous function that executes the Task.await, and when comparing it to the output of calling self() in iex, they are both indeed the same.

However the iex prompt numbers do not reset after the error, and its pid does not change either. Manually killing the current process using Process.exit(self(), :exit) resets the iex prompt number and the pid of the iex process, so I suspect that the caller process is not actually being killed. If it isn’t then what process actually exits due to the timeout?

  1. After 5 seconds I still see the output from the tasks in the iex shell:
Completed 0
Completed 1
Completed 2

This indicates that the async tasks that were spawned are still running.

How could this be happening? Assuming the caller processes did actually exit this means the associated task processes did not exit, which contradict the docs above.

Check out the docs for async: Task β€” Elixir v1.13.4

IEx traps exits (so it can report them).

1 Like