diff --git a/lib/ecto/repo/preloader.ex b/lib/ecto/repo/preloader.ex index 23d3902966..e1f6698418 100644 --- a/lib/ecto/repo/preloader.ex +++ b/lib/ecto/repo/preloader.ex @@ -129,12 +129,49 @@ defmodule Ecto.Repo.Preloader do defp maybe_pmap(preloaders, _repo_name, {adapter_meta, opts}) do if match?([_, _ | _] , preloaders) and not adapter_meta.adapter.checked_out?(adapter_meta) and Keyword.get(opts, :in_parallel, true) do + pmap(preloaders, {adapter_meta, opts}) + else + Enum.map(preloaders, &(&1.({adapter_meta, opts}))) + end + end + + # TODO: once we require Elixir >= v1.15.0, we can remove the + # alternative function implementations. + # + # Elixir v1.15.0 included the `Logger.get_process_level/2` and + # `Logger.put_process_level` functions, which are used here + # to guarantee that the preloader processes inherit the parent's + # logger level. + # + if Version.match?(System.version(), ">= 1.15.0") do + defp pmap(preloaders, {adapter_meta, opts}) do # We pass caller: self() so the ownership pool knows where # to fetch the connection from and set the proper timeouts. # Note while the ownership pool uses '$callers' from pdict, # it does not do so in automatic mode, hence this line is # still necessary. opts = Keyword.put_new(opts, :caller, self()) + + # Additionally, we propagate the caller's logger level to + # the spawned tasks + parent_logger_level = Logger.get_process_level(self()) + + on_preloader_spawn = Keyword.get(opts, :on_preloader_spawn, fn -> :ok end) + + preloaders + |> Task.async_stream(fn preloader -> + Logger.put_process_level(self(), parent_logger_level) + on_preloader_spawn.() + preloader.({adapter_meta, opts}) + end, timeout: :infinity) + |> Enum.map(fn + {:ok, assoc} -> assoc + {:exit, reason} -> exit(reason) + end) + end + else + defp pmap(preloaders, {adapter_meta, opts}) do + opts = Keyword.put_new(opts, :caller, self()) on_preloader_spawn = Keyword.get(opts, :on_preloader_spawn, fn -> :ok end) preloaders @@ -146,8 +183,6 @@ defmodule Ecto.Repo.Preloader do {:ok, assoc} -> assoc {:exit, reason} -> exit(reason) end) - else - Enum.map(preloaders, &(&1.({adapter_meta, opts}))) end end