diff --git a/lib/xandra/cluster.ex b/lib/xandra/cluster.ex index c66f00db..ac025ee9 100644 --- a/lib/xandra/cluster.ex +++ b/lib/xandra/cluster.ex @@ -433,7 +433,14 @@ defmodule Xandra.Cluster do with_conn_and_retrying( cluster, options, - &Xandra.execute(&1, batch, options_without_retry_strategy) + fn conn -> + try do + Xandra.execute(conn, batch, options_without_retry_strategy) + catch + :exit, {:noproc, _} -> + {:error, ConnectionError.new("execute", {:cluster, :pool_closed})} + end + end ) end @@ -455,7 +462,14 @@ defmodule Xandra.Cluster do with_conn_and_retrying( cluster, options, - &Xandra.execute(&1, query, params, options_without_retry_strategy) + fn conn -> + try do + Xandra.execute(conn, query, params, options_without_retry_strategy) + catch + :exit, {:noproc, _} -> + {:error, ConnectionError.new("execute", {:cluster, :pool_closed})} + end + end ) end diff --git a/lib/xandra/connection_error.ex b/lib/xandra/connection_error.ex index 3dafbb8d..89ea959d 100644 --- a/lib/xandra/connection_error.ex +++ b/lib/xandra/connection_error.ex @@ -92,6 +92,13 @@ defmodule Xandra.ConnectionError do "not connected to any of the nodes" end + defp format_reason({:cluster, :pool_closed}) do + """ + the Xandra pool is closed, probably because the network connection dropped right after + Xandra.Cluster.Pool.checkout/0 + """ + end + defp format_reason(reason) do case :inet.format_error(reason) do ~c"unknown POSIX error" -> inspect(reason) diff --git a/test/integration/errors_test.exs b/test/integration/errors_test.exs index f0b1241c..e2bc224e 100644 --- a/test/integration/errors_test.exs +++ b/test/integration/errors_test.exs @@ -2,6 +2,9 @@ defmodule ErrorsTest do use XandraTest.IntegrationCase, async: true alias Xandra.Error + alias Xandra.Cluster + alias Xandra.Cluster.Host + alias Xandra.ConnectionError test "each possible error", %{conn: conn} do assert {:error, reason} = Xandra.execute(conn, "") @@ -45,4 +48,54 @@ defmodule ErrorsTest do assert_raise Error, fn -> Xandra.prepare!(conn, "") end assert_raise Error, fn -> Xandra.execute!(conn, "USE unknown") end end + + describe "on Xandra.Cluster level" do + defmodule Xandra.Cluster.PoolMock do + @behaviour :gen_statem + + def child_spec(opts) do + %{ + id: __MODULE__, + start: {__MODULE__, :start_link, [opts]}, + type: :worker, + restart: :permanent + } + end + + def start_link([]) do + :gen_statem.start_link(__MODULE__, :no_args, []) + end + + def checkout(pid) do + :gen_statem.call(pid, :checkout) + end + + @impl true + def init(:no_args) do + {dead_pid, ref} = spawn_monitor(fn -> :ok end) + + receive do + {:DOWN, ^ref, _, _, _} -> :ok + end + + {:ok, :waiting, [{dead_pid, %Host{}}]} + end + + @impl true + def callback_mode do + :state_functions + end + + def waiting({:call, from}, :checkout, data) do + {:keep_state_and_data, {:reply, from, {:ok, data}}} + end + end + + test "noproc errors are caught" do + {:ok, cluster} = start_supervised(Xandra.Cluster.PoolMock) + + assert {:error, %ConnectionError{action: "execute", reason: {:cluster, :pool_closed}}} = + Cluster.execute(cluster, "select * from system.peers") + end + end end