Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PG::Connection#check_connection as a way to verify the socket is OK #521

Merged
merged 1 commit into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ext/pg_connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,10 @@ pgconn_conninfo( VALUE self )
*
* ... and other constants of kind PG::Constants::CONNECTION_*
*
* This method returns the status of the last command from memory.
* It doesn't do any socket access hence is not suitable to test the connectivity.
* See check_connection for a way to verify the socket state.
*
* Example:
* PG.constants.grep(/CONNECTION_/).find{|c| PG.const_get(c) == conn.status} # => :CONNECTION_OK
*/
Expand Down
21 changes: 20 additions & 1 deletion lib/pg/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,20 @@ def ssl_attributes
end
end

# Read all pending socket input to internal memory and raise an exception in case of errors.
#
# This verifies that the connection socket is in a usable state and not aborted in any way.
# No communication is done with the server.
# Only pending data is read from the socket - the method doesn't wait for any outstanding server answers.
#
# Raises a kind of PG::Error if there was an error reading the data or if the socket is in a failure state.
def check_connection
while socket_io.wait_readable(0)
consume_input
end
nil
end

# call-seq:
# conn.get_result() -> PG::Result
# conn.get_result() {|pg_result| block }
Expand Down Expand Up @@ -797,7 +811,10 @@ def new(*args)
# PG::Connection.ping(connection_string) -> Integer
# PG::Connection.ping(host, port, options, tty, dbname, login, password) -> Integer
#
# Check server status.
# PQpingParams reports the status of the server.
#
# It accepts connection parameters identical to those of PQ::Connection.new .
# It is not necessary to supply correct user name, password, or database name values to obtain the server status; however, if incorrect values are provided, the server will log a failed connection attempt.
#
# See PG::Connection.new for a description of the parameters.
#
Expand All @@ -810,6 +827,8 @@ def new(*args)
# could not establish connection
# [+PQPING_NO_ATTEMPT+]
# connection not attempted (bad params)
#
# See also check_connection for a way to check the connection without doing any server communication.
def ping(*args)
if Fiber.respond_to?(:scheduler) && Fiber.scheduler
# Run PQping in a second thread to avoid blocking of the scheduler.
Expand Down
34 changes: 34 additions & 0 deletions spec/pg/connection_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1678,6 +1678,40 @@
expect{ conn.consume_input }.to raise_error(PG::ConnectionBad, /can't get socket descriptor|connection not open/){|err| expect(err).to have_attributes(connection: conn) }
end

describe :check_connection do
it "does nothing if connection is OK" do
expect( @conn.check_connection ).to be_nil
end

def wait_check_connection(conn)
retries = 100
loop do
conn.check_connection
sleep 0.1
break if (retries-=1) < 0
end
end

it "raises error on broken connection" do
conn = PG.connect(@conninfo)
conn.send_query "SELECT pg_terminate_backend(pg_backend_pid())"
expect( conn.get_result.result_status ).to be( PG::PGRES_FATAL_ERROR )

expect do
wait_check_connection(conn)
end.to raise_error(PG::ConnectionBad, /SSL connection has been closed unexpectedly|server closed the connection unexpectedly/)
end

it "processes messages before connection error" do
conn = PG.connect(@conninfo)
conn.send_query "do $$ BEGIN RAISE NOTICE 'foo'; PERFORM pg_terminate_backend(pg_backend_pid()); END; $$ LANGUAGE plpgsql;"

expect do
wait_check_connection(conn)
end.to raise_error(PG::ConnectionBad, /SSL connection has been closed unexpectedly|server closed the connection unexpectedly/)
end
end

it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
"any number of arguments" do

Expand Down