Skip to content
Open
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
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,16 @@ Internally Pg always uses the nonblocking connection mode of libpq.
It then behaves like running in blocking mode but ensures, that all blocking IO is handled in Ruby through a possibly registered `Fiber.scheduler`.
When `PG::Connection#setnonblocking(true)` is called then the nonblocking state stays enabled, but the additional handling of blocking states is disabled, so that the calling program has to handle blocking states on its own.

An exception to this rule are the methods for large objects like `PG::Connection#lo_create` and authentication methods using external libraries (like GSSAPI authentication).
They are not compatible with `Fiber.scheduler`, so that blocking states are not passed to the registered IO scheduler.
That means the operation will work properly, but IO waiting states can not be used to switch to another Fiber doing IO.
There are some exceptions to the `Fiber.scheduler` compatibility, so that blocking states are not passed to the registered IO scheduler.
That means the operation will work properly, but IO waiting states can not be used to switch to another Fiber doing IO:

* Methods for large objects like `PG::Connection#lo_create`
* Authentication methods using external libraries (like GSSAPI or LDAP authentication)
* LDAP Lookup of Connection Parameters: https://www.postgresql.org/docs/current/libpq-ldap.html
* A connection string/hash with the `service` parameter set but not `host` and `port`.
`Thread.scheduler` compatible alternatives are:
* Set the service via `PGSERVICE`environment variable
* Set the `host` and `port` parameters explicit in the connection string/hash.


## Ractor support
Expand Down
40 changes: 30 additions & 10 deletions lib/pg/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,9 @@ def reset
# Use connection options from PG::Connection.new to reconnect with the same options but with renewed DNS resolution.
# Use conninfo_hash as a fallback when connect_start was used to create the connection object.
iopts = @iopts_for_reset || conninfo_hash.compact
if iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
if iopts[:host] && !iopts[:host].empty? &&
iopts[:port] && !iopts[:port].empty? &&
PG.library_version >= 100000
iopts = self.class.send(:resolve_hosts, iopts)
end
conninfo = self.class.parse_connect_args( iopts );
Expand Down Expand Up @@ -916,29 +918,47 @@ def new(*args)
port: dests.map{|d| d[2] }.join(","))
end

RESOLUTION_KEYS = [:host, :hostaddr, :port].freeze
private_constant :RESOLUTION_KEYS

private def connect_to_hosts(*args)
option_string = parse_connect_args(*args)
iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts)
iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}) { |h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }

has_explicit_host = (!iopts[:host].to_s.empty? || !iopts[:hostaddr].to_s.empty?) && !iopts[:port].to_s.empty?
has_explicit_service = !iopts[:service].to_s.empty?

if PG::BUNDLED_LIBPQ_WITH_UNIXSOCKET && iopts[:host].to_s.empty? && iopts[:hostaddr].to_s.empty?
# Many distors patch the hardcoded default UnixSocket path in libpq to /var/run/postgresql instead of /tmp .
iopts_with_defaults = PG::Connection.conndefaults.each_with_object({}) do |h, o|
k = h[:keyword].to_sym
# Only use the host/hostname/port keys from defaults and leave user/dbname/sslmode/... for libpq, so that they are processed in the right order.
o[k] = h[:val] if h[:val] && RESOLUTION_KEYS.include?(k)
end.merge(iopts)

if PG::BUNDLED_LIBPQ_WITH_UNIXSOCKET && iopts_with_defaults[:host].to_s.empty? && iopts_with_defaults[:hostaddr].to_s.empty?
# Many distros patch the hardcoded default UnixSocket path in libpq to /var/run/postgresql instead of /tmp .
# We simply try them all.
iopts[:host] = "/var/run/postgresql" + # Ubuntu, Debian, Fedora, Opensuse
",/run/postgresql" + # Alpine, Archlinux, Gentoo
",/tmp" # Stock PostgreSQL
end

iopts_for_reset = iopts
if iopts[:hostaddr]
if iopts_with_defaults[:hostaddr]
# hostaddr is provided -> no need to resolve hostnames

elsif iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
iopts = resolve_hosts(iopts)
elsif has_explicit_service && !has_explicit_host
# The pg_service.conf might provide host/user/port/etc.
# Ruby-pg cannot interpret pg_service without reading INI file and the additional LDAP stuff.
# So, pass params through and let libpq resolve the service, possibly blocking the Thread.scheduler.
# This ensures the processing order of libpq which is:
# connection string => service file => environment variable => compiled default
elsif iopts_with_defaults[:host] && !iopts_with_defaults[:host].empty? && PG.library_version >= 100000
# Do host resolution to avoid blocking Thread.scheduler while DNS queries.
iopts_for_reset = iopts_with_defaults
iopts = resolve_hosts(iopts_with_defaults)
else
# No host given
end
conn = self.connect_start(iopts) or
conn = connect_start(iopts) or
raise(PG::Error, "Unable to create a new connection")

raise PG::ConnectionBad, conn.error_message if conn.status == PG::CONNECTION_BAD
Expand Down
2 changes: 2 additions & 0 deletions spec/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def self::included( mod )
@conninfo = $pg_server.conninfo
@unix_socket = $pg_server.unix_socket
@conn = $pg_server.connect
@user = ENV['USER'] || ENV['USERNAME']

# Find a local port that is not in use
@port_down = @port + 10
Expand Down Expand Up @@ -194,6 +195,7 @@ class PostgresServer
attr_reader :port
attr_reader :conninfo
attr_reader :unix_socket
attr_reader :test_dir

### Set up a PostgreSQL database instance for testing.
def initialize(name, port: 23456, postgresql_conf: '')
Expand Down
Loading
Loading