I'm performing a query using preload with multiples associations in a process which has trap_exit: true. After the query ends the process receives a {:EXIT, #PID<0.102.0>, :normal} message which is really odd because this process doesn't have any linked processes.
I was able to reproduce this behavior with this example:
import Ecto.Query
internal_order_number = "da8bfb94-41b8-4921-a537-749a1b3d0936"
f = fn ->
Process.flag(:trap_exit, true)
Order
|> where([internal_number: ^internal_order_number]
|> preload([:vertical, :promo_codes, :credit_usages])
|> Repo.one
receive do
msg -> IO.puts("!!!!!!!!!! #{inspect msg}")
end
end
spawn(f)
which logs:
!!!!!!!!!! {:EXIT, #PID<0.102.0>, :normal}
Tracing this message i found that this message is produced when the Task.async ends here
The Task.async should run within a supervisor in order to prevent the link between the task and the client process to not receive any :EXIT message from ecto
Good call. We should clean up those messages.
As a workaround (which of course means a possible performance penalty) you can issue the same query with the option {:in_parallel, false}.
Example: the same query above doesn't propagate the EXIT signal if we add this flag.
import Ecto.Query
internal_order_number = "da8bfb94-41b8-4921-a537-749a1b3d0936"
f = fn ->
Process.flag(:trap_exit, true)
Order
|> where([internal_number: ^internal_order_number]
|> preload([:vertical, :promo_codes, :credit_usages])
|> Repo.one(in_parallel: false)
receive do
msg -> IO.puts("!!!!!!!!!! #{inspect msg}")
end
end
spawn(f)
@josevalim should we just change Task.async to Task.Supervisor.async_nolink?
No, the called should still crash if one of the preloads crashes. We should just clean up the messages (and potentially exit if we receive a non-normal exit).
I would even argue, this is something that should be handled by Task.await in general - I see no reason to leak those messages from Task.await.
As discussed with @fishcakez on IRC, a possible solution to this is using Task.async_stream instead of a manual pmap implementation - it takes care of exit messages since it fully controls the execution.
Hi @nashby! 馃憢
Since we depend on v1.4, :+1: for using Task.async_stream!
Most helpful comment
As a workaround (which of course means a possible performance penalty) you can issue the same query with the option
{:in_parallel, false}.Example: the same query above doesn't propagate the
EXITsignal if we add this flag.