Re: [POC] Implement async DNS with getaddrinfo_a

From: Andrey Borodin <x4mmm(at)yandex-team(dot)ru>
To: Jacob Champion <jacob(dot)champion(at)enterprisedb(dot)com>
Cc: PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>, Evgeny Kuzin <evgeny(dot)kuzin(at)outlook(dot)com>
Subject: Re: [POC] Implement async DNS with getaddrinfo_a
Date: 2026-08-22 11:42:47
Message-ID: 3931A17F-3AAC-46D8-B1F1-78CD428CBE3A@yandex-team.ru
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi Jacob,

On Tue, Jul 7, 2026, Jacob Champion wrote:
> This is just a proof-of-concept, with multiple TODOs. It hacks up the
> existing altsock support in libpq to handle connection establishment,
> using glibc's getaddrinfo_a as a sample (the BSDs have their own
> distinct APIs, I think).

Thanks for working this out. Sorry it took me so long to follow up. I
spent more time than I expected thinking about where the abstraction
boundary should be, and I will try to respond much faster in the next
round.

I tried to fit my SRV/SVCB work around your patches, and I think the new
PQconnectPoll() state is the right foundation. You already describe
getaddrinfo_a as a sample and point out that its background-thread
architecture may not be something we want to keep. I agree. My question
is which of the POC's architectural bones we should preserve if we take
it forward.

The POC already hides gaicb and sigevent in src/common/ip.c. What still
seems specific to the getaddrinfo_a implementation is the contract
visible to fe-connect.c: it creates a self-pipe, exposes one end through
altsock, keeps an opaque async_dns_ctx, and knows when to finish and
release the operation. That works for this implementation, but Windows
has GetAddrInfoExW(), the BSDs have different resolver APIs, and c-ares
has multiple sockets plus a timer rather than one completion descriptor.

Could we make the address-resolution extraction establish an internal
resolver interface before putting an asynchronous implementation behind
it? I think there are two useful boundaries here:

1. PQconnectPoll() owns a generic asynchronous operation. It can start
or advance the operation, ask what the caller should wait for, and
cancel and free it. It should not know whether the operation owns a
gaicb, an OVERLAPPED object, or a DNS channel.

2. The resolver produces connection endpoints. An endpoint has a host,
port, and optionally already-resolved addresses. Plain A/AAAA
resolution produces one endpoint, while SRV or SVCB discovery may
produce several. The existing multi-host and target_session_attrs
machinery can then try them.

I am not suggesting that SRV/SVCB support, c-ares, or a public resolver
API has to be part of this patch. They are useful tests of the internal
boundary. If the result is only struct addrinfo, service discovery will
have to bypass the interface or replace it later. Conversely, treating
ordinary resolution as the one-endpoint case does not seem to add much
complexity.

I would avoid fixing the exact wait contract around the first backend.
One completion descriptor is sufficient for this self-pipe, but not for
a resolver with several sockets and a timeout. We could keep that part
private until a second implementation tells us what it actually needs.
The important first step is that fe-connect.c does not own
backend-specific state or cleanup rules.

Your commit message already calls out the need to merge or refactor the
DNS and authentication cleanup. A generic asynchronous-operation object
seems like one way to resolve that TODO while keeping resolver-specific
lifetime rules out of pqDropConnection(). It may also make the DNS test
coverage issue you mentioned easier to address, by giving tests a place
to control resolution without depending entirely on the host resolver.

Your concern about the background-thread architecture may matter even
more when libpq is loaded into a PostgreSQL backend through dblink or
postgres_fdw. Threads created internally by libraries are not
unprecedented, and there is active work on making PostgreSQL capable of
using threads, so the existence of a thread is not by itself an
objection.

However, in this case libpq supplies the callback and manages the
objects it accesses. Before adopting SIGEV_THREAD, I think we need to
understand the callback's signal mask, whether PostgreSQL signals can be
delivered to it, and precisely which operations it may perform while
racing with connection cancellation and destruction.

If someone takes the POC forward, my suggested order would be:

1. Keep CONNECTION_AWAITING_HOST and the extraction of host resolution
from the main connection loop.
2. Put the existing synchronous getaddrinfo implementation behind the
internal resolver interface, preserving current behavior.
3. Use getaddrinfo_a, if its thread model is safe in a backend, and a
second implementation to settle the asynchronous wait interface.
4. Only then decide whether any part of the resolver interface should be
public.

I have a c-ares SVCB prototype that returned multiple endpoints through
a PQsetResolver() hook. That experiment convinced me that endpoint
results are useful, but also that publishing the hook before we have a
sound asynchronous wait model would be premature. I can rebase the
prototype onto an internal interface and help test where the boundary
needs to be, without asking this patch to implement service discovery.

WDYT?

Thank you!

Best regards, Andrey Borodin.

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Alexander Lakhin 2026-08-22 13:00:01 028_pitr_timelines.pl might fail when cp interrupted and backup includes required segment
Previous Message Etsuro Fujita 2026-08-22 10:36:48 Re: use of SPI by postgresImportForeignStatistics