Skip to content

Commit b3eb658

Browse files
committed
Don't wait for ReadyForQuery after FATAL errors
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 b3eb658

2 files changed

Lines changed: 26 additions & 0 deletions

File tree

lib/postgrex/protocol.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3068,6 +3068,12 @@ defmodule Postgrex.Protocol do
30683068
end
30693069
end
30703070

3071+
defp error_ready(s, _status, %Postgrex.Error{postgres: %{severity: severity}} = err, buffer)
3072+
when severity in ["FATAL", "PANIC"] do
3073+
%{connection_id: connection_id} = s
3074+
{:disconnect, %{err | connection_id: connection_id}, %{s | buffer: buffer}}
3075+
end
3076+
30713077
defp error_ready(s, status, %Postgrex.Error{} = err, buffer) do
30723078
case recv_ready(s, status, buffer) do
30733079
{:ok, s} ->

test/query_test.exs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1940,6 +1940,26 @@ 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+
# Start a long-running query in a separate process so we can terminate the
1949+
# backend while it's executing.
1950+
task =
1951+
Task.async(fn ->
1952+
Postgrex.query(pid, "SELECT pg_sleep(10)", [])
1953+
end)
1954+
1955+
Process.sleep(100)
1956+
1957+
assert [[true]] = query("SELECT pg_terminate_backend($1)", [connection_id])
1958+
1959+
assert {:error, %Postgrex.Error{postgres: %{code: :admin_shutdown, severity: "FATAL"}}} =
1960+
Task.await(task, 5000)
1961+
end
1962+
19431963
test "terminate backend with socket", context do
19441964
Process.flag(:trap_exit, true)
19451965
socket = System.get_env("PG_SOCKET_DIR") || "/tmp"

0 commit comments

Comments
 (0)