Skip to content

Commit 2ccb200

Browse files
authored
Merge pull request #485 from larskanis/wip
Revert connecting on a host-by-host basis and make sure only authentication errors stop host iteration
2 parents 499011b + 51f8217 commit 2ccb200

7 files changed

+266
-205
lines changed

Rakefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ LIBDIR = BASEDIR + 'lib'
1616
EXTDIR = BASEDIR + 'ext'
1717
PKGDIR = BASEDIR + 'pkg'
1818
TMPDIR = BASEDIR + 'tmp'
19-
TESTDIR = BASEDIR + "tmp_test_specs"
19+
TESTDIR = BASEDIR + "tmp_test_*"
2020

2121
DLEXT = RbConfig::CONFIG['DLEXT']
2222
EXT = LIBDIR + "pg_ext.#{DLEXT}"
2323

2424
GEMSPEC = 'pg.gemspec'
2525

26-
CLOBBER.include( TESTDIR.to_s )
26+
CLEAN.include( TESTDIR.to_s )
2727
CLEAN.include( PKGDIR.to_s, TMPDIR.to_s )
2828
CLEAN.include "lib/*/libpq.dll"
2929
CLEAN.include "lib/pg_ext.*"

lib/pg/connection.rb

Lines changed: 23 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -555,14 +555,17 @@ def cancel
555555
if (timeo = conninfo_hash[:connect_timeout].to_i) && timeo > 0
556556
# Lowest timeout is 2 seconds - like in libpq
557557
timeo = [timeo, 2].max
558-
stop_time = timeo + Process.clock_gettime(Process::CLOCK_MONOTONIC)
558+
host_count = conninfo_hash[:host].to_s.count(",") + 1
559+
stop_time = timeo * host_count + Process.clock_gettime(Process::CLOCK_MONOTONIC)
559560
end
560561

561562
poll_status = PG::PGRES_POLLING_WRITING
562563
until poll_status == PG::PGRES_POLLING_OK ||
563564
poll_status == PG::PGRES_POLLING_FAILED
564565

565-
timeout = stop_time&.-(Process.clock_gettime(Process::CLOCK_MONOTONIC))
566+
# Set single timeout to parameter "connect_timeout" but
567+
# don't exceed total connection time of number-of-hosts * connect_timeout.
568+
timeout = [timeo, stop_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)].min if stop_time
566569
event = if !timeout || timeout >= 0
567570
# If the socket needs to read, wait 'til it becomes readable to poll again
568571
case poll_status
@@ -600,7 +603,6 @@ def cancel
600603

601604
# Check to see if it's finished or failed yet
602605
poll_status = send( poll_meth )
603-
@last_status = status unless [PG::CONNECTION_BAD, PG::CONNECTION_OK].include?(status)
604606
end
605607

606608
unless status == PG::CONNECTION_OK
@@ -694,81 +696,47 @@ def new(*args)
694696
errors = []
695697
if iopts[:hostaddr]
696698
# hostaddr is provided -> no need to resolve hostnames
697-
ihostaddrs = iopts[:hostaddr].split(",", -1)
698699

699-
ihosts = iopts[:host].split(",", -1) if iopts[:host]
700-
raise PG::ConnectionBad, "could not match #{ihosts.size} host names to #{ihostaddrs.size} hostaddr values" if ihosts && ihosts.size != ihostaddrs.size
701-
702-
iports = iopts[:port].split(",", -1)
703-
iports = iports * ihostaddrs.size if iports.size == 1
704-
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihostaddrs.size} hosts" if iports.size != ihostaddrs.size
705-
706-
# Try to connect to each hostaddr with separate timeout
707-
ihostaddrs.each_with_index do |ihostaddr, idx|
708-
oopts = iopts.merge(hostaddr: ihostaddr, port: iports[idx])
709-
oopts[:host] = ihosts[idx] if ihosts
710-
c = connect_internal(oopts, errors)
711-
return c if c
712-
end
713-
elsif iopts[:host] && !iopts[:host].empty?
714-
# Resolve DNS in Ruby to avoid blocking state while connecting, when it ...
700+
elsif iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
701+
# Resolve DNS in Ruby to avoid blocking state while connecting.
702+
# Multiple comma-separated values are generated, if the hostname resolves to both IPv4 and IPv6 addresses.
703+
# This requires PostgreSQL-10+, so no DNS resolving is done on earlier versions.
715704
ihosts = iopts[:host].split(",", -1)
716-
717705
iports = iopts[:port].split(",", -1)
718706
iports = iports * ihosts.size if iports.size == 1
719707
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
720708

721-
ihosts.each_with_index do |mhost, idx|
709+
dests = ihosts.each_with_index.flat_map do |mhost, idx|
722710
unless host_is_named_pipe?(mhost)
723-
addrs = if Fiber.respond_to?(:scheduler) &&
711+
if Fiber.respond_to?(:scheduler) &&
724712
Fiber.scheduler &&
725713
RUBY_VERSION < '3.1.'
726714

727715
# Use a second thread to avoid blocking of the scheduler.
728716
# `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
729-
Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
717+
hostaddrs = Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
730718
else
731-
Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
732-
end
733-
734-
# Try to connect to each host with separate timeout
735-
addrs.each do |addr|
736-
oopts = iopts.merge(hostaddr: addr, host: mhost, port: iports[idx])
737-
c = connect_internal(oopts, errors)
738-
return c if c
719+
hostaddrs = Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
739720
end
740721
else
741722
# No hostname to resolve (UnixSocket)
742-
oopts = iopts.merge(host: mhost, port: iports[idx])
743-
c = connect_internal(oopts, errors)
744-
return c if c
723+
hostaddrs = [nil]
745724
end
725+
hostaddrs.map { |hostaddr| [hostaddr, mhost, iports[idx]] }
746726
end
727+
iopts.merge!(
728+
hostaddr: dests.map{|d| d[0] }.join(","),
729+
host: dests.map{|d| d[1] }.join(","),
730+
port: dests.map{|d| d[2] }.join(","))
747731
else
748732
# No host given
749-
return connect_internal(iopts)
750733
end
751-
raise PG::ConnectionBad, errors.join("\n")
752-
end
753-
754-
private def connect_internal(opts, errors=nil)
755-
begin
756-
conn = self.connect_start(opts) or
757-
raise(PG::Error, "Unable to create a new connection")
734+
conn = self.connect_start(iopts) or
735+
raise(PG::Error, "Unable to create a new connection")
758736

759-
raise PG::ConnectionBad, conn.error_message if conn.status == PG::CONNECTION_BAD
737+
raise PG::ConnectionBad, conn.error_message if conn.status == PG::CONNECTION_BAD
760738

761-
conn.send(:async_connect_or_reset, :connect_poll)
762-
rescue PG::ConnectionBad => err
763-
if errors && !(conn && [PG::CONNECTION_AWAITING_RESPONSE].include?(conn.instance_variable_get(:@last_status)))
764-
# Seems to be no authentication error -> try next host
765-
errors << err
766-
return nil
767-
else
768-
# Probably an authentication error
769-
raise
770-
end
771-
end
739+
conn.send(:async_connect_or_reset, :connect_poll)
772740
conn
773741
end
774742

0 commit comments

Comments
 (0)