Skip to content

Commit 0f92ae3

Browse files
authored
Return proper error when getting tcp closed after fatal errors (#765)
When PostgreSQL sends a FATAL or PANIC ErrorResponse, it closes the connection immediately without sending ReadyForQuery. Postgrex unconditionally waited for ReadyForQuery, which would hit tcp_closed and return a generic disconnect error, discarding the original FATAL error.
1 parent 3385a98 commit 0f92ae3

2 files changed

Lines changed: 37 additions & 2 deletions

File tree

lib/postgrex/protocol.ex

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3069,13 +3069,18 @@ defmodule Postgrex.Protocol do
30693069
end
30703070

30713071
defp error_ready(s, status, %Postgrex.Error{} = err, buffer) do
3072+
%{connection_id: connection_id} = s
3073+
30723074
case recv_ready(s, status, buffer) do
30733075
{:ok, s} ->
3074-
%{connection_id: connection_id} = s
30753076
{:error, %{err | connection_id: connection_id}, s}
30763077

30773078
{:disconnect, _, _} = disconnect ->
3078-
disconnect
3079+
if err.postgres.severity in ["FATAL", "PANIC"] do
3080+
{:disconnect, %{err | connection_id: connection_id}, %{s | buffer: buffer}}
3081+
else
3082+
disconnect
3083+
end
30793084
end
30803085
end
30813086

test/query_test.exs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1940,6 +1940,36 @@ defmodule QueryTest do
19401940
end) =~ "** (Postgrex.Error) FATAL 57P01 (admin_shutdown)"
19411941
end
19421942

1943+
test "terminate backend during query returns FATAL error", context do
1944+
assert {:ok, pid} = P.start_link([idle_interval: 10] ++ context[:options])
1945+
1946+
%Postgrex.Result{connection_id: connection_id} = Postgrex.query!(pid, "SELECT 42", [])
1947+
1948+
task =
1949+
Task.async(fn ->
1950+
receive do
1951+
:go -> :ok
1952+
end
1953+
1954+
Postgrex.query(pid, "SELECT pg_sleep(10)", [])
1955+
end)
1956+
1957+
:erlang.trace(task.pid, true, [:call])
1958+
:erlang.trace_pattern({Postgrex.Protocol, :recv_bind, :_}, [], [:local])
1959+
1960+
send(task.pid, :go)
1961+
1962+
assert_receive {:trace, _, :call, {Postgrex.Protocol, :recv_bind, _}}, 200
1963+
1964+
assert [[true]] = query("SELECT pg_terminate_backend($1)", [connection_id])
1965+
1966+
assert {:error, %Postgrex.Error{postgres: %{code: :admin_shutdown, severity: "FATAL"}}} =
1967+
Task.await(task, 5000)
1968+
after
1969+
:erlang.trace_pattern({Postgrex.Protocol, :recv_bind, :_}, false, [])
1970+
:erlang.trace(:all, false, [:call])
1971+
end
1972+
19431973
test "terminate backend with socket", context do
19441974
Process.flag(:trap_exit, true)
19451975
socket = System.get_env("PG_SOCKET_DIR") || "/tmp"

0 commit comments

Comments
 (0)