How to wait for the full init of Ecto.Repo before making requests

In my Phoenix app, I need periodically sync some data stored in DB with an external service. So I add ExtSystem.Sync module to execute this sync. Here is some code snippets:

application.ex:

defmodule Core.Application do
  # ...
  use Application

  def start(_type, _args) do
    children = [
      Core.Repo,
      CoreWeb.Telemetry,
      {Phoenix.PubSub, name: Core.PubSub},
      CoreWeb.Endpoint,
      ExtSystem.Sync
    ]
    opts = [strategy: :one_for_one, name: Core.Supervisor]
    Supervisor.start_link(children, opts)
  end
  # ...
end

sync.ex:

defmodule ExtSystem.Sync do
  use GenServer

  # Client

  def start_link([]) do
    GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
  end

  # Server (callbacks)

  @impl true
  def init(state) do
    send(self(), :sync) # error
#    Process.send_after(self(), :sync, 1000) # ok

    {:ok, state}
  end

  @impl true
  def handle_info(:sync, state) do
    # ...
    Process.send_after(self(), :sync, period)
    {:noreply, state}
  end
  # ...
end

If I send :sync message from init to ExtSystem.Sync without any delay I got the next error:

14:04:36.724 [error] GenServer ExtSystem.Sync terminating
** (ArgumentError) errors were found at the given arguments:

  * 2nd argument: not a key that exists in the table

    (stdlib 3.15) :ets.lookup_element(Ecto.Repo.Registry, #PID<0.419.0>, 3)
    (ecto 3.5.6) lib/ecto/repo/registry.ex:24: Ecto.Repo.Registry.lookup/1
    (ecto 3.5.6) lib/ecto/repo/queryable.ex:210: Ecto.Repo.Queryable.execute/4
    (ecto 3.5.6) lib/ecto/repo/queryable.ex:17: Ecto.Repo.Queryable.all/3
    (webtrit_core 0.2.0) lib/ext_system/sync.ex:34: ExtSystem.Sync.handle_info/2
    (stdlib 3.15) gen_server.erl:695: :gen_server.try_dispatch/4
    (stdlib 3.15) gen_server.erl:771: :gen_server.handle_msg/6
    (stdlib 3.15) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: :sync
State: %{}

When the supervisor starts ExtSystem.Sync the second time everything works as expected. Also if I send :sync message from init to ExtSystem.Sync with delay it also works as expected.

What is the correct way to wait for the full init of Ecto.Repo before trying to execute requests?

This is not a direct answer to your question but it may lead to a more predictable startup.

Rather than send(self(), :sync) in your init/1 callback, instead return {:ok, state, {:continue, :sync}}. This article has a good summary of the background and reasons why this obviates a class of race conditions.

In summary, try:

  @impl true
  def init(state) do
    ...
    {:ok, state, {:continue, :sync}}
  end
1 Like

Thank you for your suggestion and article link, but unfortunately, this doesn’t help in my situation.

:continue with handle_continue help we need to sync internal inial messages.

However, in the current case, the first call of one process must be sync with full initialization of the external Application - :ecto. So I hope that maybe I could use Application.ensure_started/2 or Application.ensure_all_started/2 to sync, but unfortunately, this also doesn’t help.

To my knowledge, the Core.Repo module should be fully initialized at the point that the init function returns. What ecto version are you on?

2 Likes

This is correct. There is something else at play here if the repository is not available in Ecto’s internal registry.

2 Likes

To be sure that issue is not in versions I just updated to last ones:

  • phoenix_ecto: “4.2.1”
  • ecto: “3.6.2”
  • ecto_sql: “3.6.2”

Hm ok. I will try to create tiny test project from scratch and reproduce this issue in it. And if the issue remains post the link to this project here.

@benwilson512 and @josevalim thank you for your authoritative comment about - everything must work. It actually is :blush:.

After creating the project from scratch and reveal that it works without any error, I recheck my app start logic and found the issue - in some condition, Sync started before Repo :roll_eyes:.

Sorry for the hassle, and thanks for the impulse to find my own issue.

2 Likes