Skip to content

Commit

Permalink
fix(electric): Handle connector errors during startup (#1290)
Browse files Browse the repository at this point in the history
Previously, we would ignore any non-raising connector startup failures,
failing to even log them.

With this change we make sure all connectors are running and log a fatal
error when that's not the case.

Side note: there's an ongoing work in
#1226 on adding an e2e test
to verify the correct output of such errors.
  • Loading branch information
alco committed Jun 4, 2024
1 parent cbd652d commit e77296d
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/grumpy-goats-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@core/electric": patch
---

The sync service now exits when a database connector fails to initialise. Previously, some initialisation errors would result in Electric running without a single database connection and not logging any erors about that.
48 changes: 34 additions & 14 deletions components/electric/lib/electric/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,26 @@ defmodule Electric.Application do

opts = [strategy: :one_for_one, name: Electric.Supervisor]

with {:ok, supervisor} <- Supervisor.start_link(children, opts) do
Application.get_env(:electric, Electric.Replication.Connectors, [])
|> Enum.each(fn {name, config} ->
Connectors.start_connector(
PostgresConnector,
config
|> Keyword.put(:origin, to_string(name))
|> Keyword.put(:write_to_pg_mode, write_to_pg_mode)
)
end)

with {:ok, supervisor} <- Supervisor.start_link(children, opts),
connectors = Application.get_env(:electric, Electric.Replication.Connectors, []),
:ok <- start_connectors(connectors, write_to_pg_mode) do
{:ok, supervisor}
else
error -> log_supervisor_startup_error(error)
end
end

defp start_connectors([], _write_to_pg_mode), do: :ok

defp start_connectors([{name, config} | connectors], write_to_pg_mode) do
connector_config =
config
|> Keyword.put(:origin, to_string(name))
|> Keyword.put(:write_to_pg_mode, write_to_pg_mode)

with {:ok, _pid} <- Connectors.start_connector(PostgresConnector, connector_config) do
start_connectors(connectors, write_to_pg_mode)
end
|> log_supervisor_startup_error()
end

defp maybe_add_child(children, nil), do: children
Expand Down Expand Up @@ -94,15 +100,15 @@ defmodule Electric.Application do
end
end

defp log_supervisor_startup_error({:ok, _sup_pid} = ok), do: ok

defp log_supervisor_startup_error(
{:error, {:shutdown, {:failed_to_start_child, child_id, reason}}} = error
) do
_ = log_child_error(child_id, reason)
error
end

defp log_supervisor_startup_error(error), do: error

@spec log_child_error({atom, atom}, term) :: no_return
defp log_child_error(
{ThousandIsland, :replication_tcp_server_listener},
Expand All @@ -126,6 +132,20 @@ defmodule Electric.Application do
)
end

defp log_child_error(
Electric.Replication.PostgresConnectorMng,
{{:badmatch, {:error, :nxdomain}}, _stracktrace}
) do
Electric.Errors.print_fatal_error(
:init,
"Failed to resolve the database domain name to an IP address.",
"""
Double-check the value of DATABASE_URL and make sure to set
DATABASE_USE_IPV6=true if your database is only reachable using IPv6.
"""
)
end

defp log_child_error(child_id, reason) do
Electric.Errors.failed_to_start_child_error(:init, child_id, reason)
|> Electric.Errors.print_fatal_error()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ defmodule Electric.Replication.PostgresConnector do
name = name(origin)
Electric.reg(name)

children = [%{id: :mng, start: {PostgresConnectorMng, :start_link, [connector_config]}}]
children = [{PostgresConnectorMng, connector_config}]
Supervisor.init(children, strategy: :one_for_all)
end

Expand Down

0 comments on commit e77296d

Please sign in to comment.