Ecto: Preload with in_parallel=true links the client process to an internal ecto Task.async process

Created on 27 Sep 2017  路  6Comments  路  Source: elixir-ecto/ecto

Environment

  • Elixir version: 1.5.1
  • Database and version: PostgreSQL 9.4
  • Ecto version: 2.1
  • Database adapter and version: postgrex 0.13.3
  • Operating system: Ubuntu

Current behavior

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

Expected behavior

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

Bug Starter

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 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)

All 6 comments

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!

Was this page helpful?
0 / 5 - 0 ratings