Timeout in Task.await can't be trap exit

Hi, I found the timeout in Task.await not throw any error in my code, for example:

spawn_link(fn ->
  Task.async(fn -> 
    :timer.sleep(200)
  end)
  |> Task.await(100)
end)

The process just down quietly in production. (In IEX there would be an error log). So I tried to use trap exit to handle the timeout:

spawn_link(fn ->
  Task.async(fn -> 
    Process.flag(:trap_exit, true)
    :timer.sleep(200)
    IO.puts "task alive" 
  end)
  |> Task.await(100)

  IO.puts "caller alive"
end)

But the code doesn’t print anything, looks like the “trap_exit” not working. Here is the document of Task.await:

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.

How could I detect the timeout of Task.await and print a proper error log? Thanks.

trap exit should be in the process that receives the exit message. one of these 2 depending on what you want to do

Process.flag(:trap_exit, true)

spawn_link(fn ->
  Task.async(fn -> 
    :timer.sleep(200)
    IO.puts "task alive" 
  end)
  |> Task.await(100)
end)

receive do
  {:EXIT, _, :normal} -> IO.inspect "normal exit"
  {:EXIT, _, reason} IO.inspect reason
end

or

spawn_link(fn ->
  Process.flag(:trap_exit, true)

  Task.async(fn -> 
    :timer.sleep(200)
    IO.puts "task alive" 
  end)
  |> Task.await(100)

  receive do
    {:EXIT, _, :normal} -> IO.inspect "normal exit"
    {:EXIT, _, reason} IO.inspect reason
  end
end)
1 Like

Thank you!

The first code works well, but the second doesn’t print any log.

Ah I apologize I thought both would work. I’m not sure why that one doesn’t print anything without digging deeper. But probably one of the smart people around here already knows and will speak up :).

Actually this might work for the second one:

spawn_link(fn ->
  Process.flag(:trap_exit, true)

  Task.async(fn -> 
    :timer.sleep(200)
    IO.puts "task alive" 
  end)
  |> Task.await(100)

  receive do
    {:DOWN, ref, :process, pid, reason} -> IO.inspect reason
  end
end)

It looks like Task.async sets up a monitor so you might receive a down message first. I think you still need the exit trapping so the spawn process doesn’t die before receiving the down message.

Nervermind. I checked the implement of Task.await, it exit current process when timeout exceeded.

And the trap_exit can’t stop current process from exiting, like:

spawn(fn ->
  IO.puts "hello"

  Process.flag(:trap_exit, true)
  exit(:whatever) # stopped here

  IO.puts "unreachable"
end)
1 Like

Nice find!