aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2018-08-23 16:39:19 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2018-08-23 16:39:37 -0400
commitc781a066ea4f0de0dc46e953ba55e10943199d6d (patch)
tree9eb10687734c0984c51f640d8bdd713a07cfa998 /src
parent9126b4ee1c59ef07c55227b55c45a8d4bc6590d9 (diff)
downloadpostgresql-c781a066ea4f0de0dc46e953ba55e10943199d6d.tar.gz
postgresql-c781a066ea4f0de0dc46e953ba55e10943199d6d.zip
In libpq, don't look up all the hostnames at once.
Historically, we looked up the target hostname in connectDBStart, so that PQconnectPoll did not need to do DNS name resolution. The patches that added multiple-target-host support to libpq preserved this division of labor; but it's really nonsensical now, because it means that if any one of the target hosts fails to resolve in DNS, the connection fails. That negates the no-single-point-of-failure goal of the feature. Additionally, DNS lookups aren't exactly cheap, but the code did them all even if the first connection attempt succeeds. Hence, rearrange so that PQconnectPoll does the lookups, and only looks up a hostname when it's time to try that host. This does mean that PQconnectPoll could block on a DNS lookup --- but if you wanted to avoid that, you should be using hostaddr, as the documentation has always specified. It seems fairly unlikely that any applications would really care whether the lookup occurs inside PQconnectStart or PQconnectPoll. In addition to calling out that fact explicitly, do some other minor wordsmithing in the docs around the multiple-target-host feature. Since this seems like a bug in the multiple-target-host feature, backpatch to v10 where that was introduced. In the back branches, avoid moving any existing fields of struct pg_conn, just in case any third-party code is looking into that struct. Tom Lane, reviewed by Fabien Coelho Discussion: https://postgr.es/m/4913.1533827102@sss.pgh.pa.us
Diffstat (limited to 'src')
-rw-r--r--src/interfaces/libpq/fe-connect.c249
-rw-r--r--src/interfaces/libpq/libpq-int.h6
2 files changed, 121 insertions, 134 deletions
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 9315a27561f..a8048ffad20 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -360,7 +360,7 @@ static PGconn *makeEmptyPGconn(void);
static bool fillPGconn(PGconn *conn, PQconninfoOption *connOptions);
static void freePGconn(PGconn *conn);
static void closePGconn(PGconn *conn);
-static void release_all_addrinfo(PGconn *conn);
+static void release_conn_addrinfo(PGconn *conn);
static void sendTerminateConn(PGconn *conn);
static PQconninfoOption *conninfo_init(PQExpBuffer errorMessage);
static PQconninfoOption *parse_connection_string(const char *conninfo,
@@ -1742,10 +1742,6 @@ setKeepalivesWin32(PGconn *conn)
static int
connectDBStart(PGconn *conn)
{
- char portstr[MAXPGPATH];
- int ret;
- int i;
-
if (!conn)
return 0;
@@ -1765,101 +1761,6 @@ connectDBStart(PGconn *conn)
resetPQExpBuffer(&conn->errorMessage);
/*
- * Look up socket addresses for each possible host using
- * pg_getaddrinfo_all.
- */
- for (i = 0; i < conn->nconnhost; ++i)
- {
- pg_conn_host *ch = &conn->connhost[i];
- struct addrinfo hint;
- int thisport;
-
- /* Initialize hint structure */
- MemSet(&hint, 0, sizeof(hint));
- hint.ai_socktype = SOCK_STREAM;
- hint.ai_family = AF_UNSPEC;
-
- /* Figure out the port number we're going to use. */
- if (ch->port == NULL || ch->port[0] == '\0')
- thisport = DEF_PGPORT;
- else
- {
- thisport = atoi(ch->port);
- if (thisport < 1 || thisport > 65535)
- {
- appendPQExpBuffer(&conn->errorMessage,
- libpq_gettext("invalid port number: \"%s\"\n"),
- ch->port);
- conn->options_valid = false;
- goto connect_errReturn;
- }
- }
- snprintf(portstr, sizeof(portstr), "%d", thisport);
-
- /* Use pg_getaddrinfo_all() to resolve the address */
- ret = 1;
- switch (ch->type)
- {
- case CHT_HOST_NAME:
- ret = pg_getaddrinfo_all(ch->host, portstr, &hint, &ch->addrlist);
- if (ret || !ch->addrlist)
- appendPQExpBuffer(&conn->errorMessage,
- libpq_gettext("could not translate host name \"%s\" to address: %s\n"),
- ch->host, gai_strerror(ret));
- break;
-
- case CHT_HOST_ADDRESS:
- hint.ai_flags = AI_NUMERICHOST;
- ret = pg_getaddrinfo_all(ch->hostaddr, portstr, &hint, &ch->addrlist);
- if (ret || !ch->addrlist)
- appendPQExpBuffer(&conn->errorMessage,
- libpq_gettext("could not parse network address \"%s\": %s\n"),
- ch->hostaddr, gai_strerror(ret));
- break;
-
- case CHT_UNIX_SOCKET:
-#ifdef HAVE_UNIX_SOCKETS
- hint.ai_family = AF_UNIX;
- UNIXSOCK_PATH(portstr, thisport, ch->host);
- if (strlen(portstr) >= UNIXSOCK_PATH_BUFLEN)
- {
- appendPQExpBuffer(&conn->errorMessage,
- libpq_gettext("Unix-domain socket path \"%s\" is too long (maximum %d bytes)\n"),
- portstr,
- (int) (UNIXSOCK_PATH_BUFLEN - 1));
- conn->options_valid = false;
- goto connect_errReturn;
- }
-
- /*
- * NULL hostname tells pg_getaddrinfo_all to parse the service
- * name as a Unix-domain socket path.
- */
- ret = pg_getaddrinfo_all(NULL, portstr, &hint, &ch->addrlist);
- if (ret || !ch->addrlist)
- appendPQExpBuffer(&conn->errorMessage,
- libpq_gettext("could not translate Unix-domain socket path \"%s\" to address: %s\n"),
- portstr, gai_strerror(ret));
- break;
-#else
- Assert(false);
- conn->options_valid = false;
- goto connect_errReturn;
-#endif
- }
- if (ret || !ch->addrlist)
- {
- if (ch->addrlist)
- {
- pg_freeaddrinfo_all(hint.ai_family, ch->addrlist);
- ch->addrlist = NULL;
- }
- conn->options_valid = false;
- goto connect_errReturn;
- }
- }
-
- /*
* Set up to try to connect to the first host. (Setting whichhost = -1 is
* a bit of a cheat, but PQconnectPoll will advance it to 0 before
* anything else looks at it.)
@@ -2157,6 +2058,12 @@ keep_going: /* We will come back to here until there is
/* Time to advance to next connhost[] entry? */
if (conn->try_next_host)
{
+ pg_conn_host *ch;
+ struct addrinfo hint;
+ int thisport;
+ int ret;
+ char portstr[MAXPGPATH];
+
if (conn->whichhost + 1 >= conn->nconnhost)
{
/*
@@ -2166,10 +2073,100 @@ keep_going: /* We will come back to here until there is
goto error_return;
}
conn->whichhost++;
- conn->addr_cur = conn->connhost[conn->whichhost].addrlist;
- /* If no addresses for this host, just try the next one */
- if (conn->addr_cur == NULL)
- goto keep_going;
+
+ /* Drop any address info for previous host */
+ release_conn_addrinfo(conn);
+
+ /*
+ * Look up info for the new host. On failure, log the problem in
+ * conn->errorMessage, then loop around to try the next host. (Note
+ * we don't clear try_next_host until we've succeeded.)
+ */
+ ch = &conn->connhost[conn->whichhost];
+
+ /* Initialize hint structure */
+ MemSet(&hint, 0, sizeof(hint));
+ hint.ai_socktype = SOCK_STREAM;
+ conn->addrlist_family = hint.ai_family = AF_UNSPEC;
+
+ /* Figure out the port number we're going to use. */
+ if (ch->port == NULL || ch->port[0] == '\0')
+ thisport = DEF_PGPORT;
+ else
+ {
+ thisport = atoi(ch->port);
+ if (thisport < 1 || thisport > 65535)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid port number: \"%s\"\n"),
+ ch->port);
+ goto keep_going;
+ }
+ }
+ snprintf(portstr, sizeof(portstr), "%d", thisport);
+
+ /* Use pg_getaddrinfo_all() to resolve the address */
+ switch (ch->type)
+ {
+ case CHT_HOST_NAME:
+ ret = pg_getaddrinfo_all(ch->host, portstr, &hint,
+ &conn->addrlist);
+ if (ret || !conn->addrlist)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not translate host name \"%s\" to address: %s\n"),
+ ch->host, gai_strerror(ret));
+ goto keep_going;
+ }
+ break;
+
+ case CHT_HOST_ADDRESS:
+ hint.ai_flags = AI_NUMERICHOST;
+ ret = pg_getaddrinfo_all(ch->hostaddr, portstr, &hint,
+ &conn->addrlist);
+ if (ret || !conn->addrlist)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not parse network address \"%s\": %s\n"),
+ ch->hostaddr, gai_strerror(ret));
+ goto keep_going;
+ }
+ break;
+
+ case CHT_UNIX_SOCKET:
+#ifdef HAVE_UNIX_SOCKETS
+ conn->addrlist_family = hint.ai_family = AF_UNIX;
+ UNIXSOCK_PATH(portstr, thisport, ch->host);
+ if (strlen(portstr) >= UNIXSOCK_PATH_BUFLEN)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("Unix-domain socket path \"%s\" is too long (maximum %d bytes)\n"),
+ portstr,
+ (int) (UNIXSOCK_PATH_BUFLEN - 1));
+ goto keep_going;
+ }
+
+ /*
+ * NULL hostname tells pg_getaddrinfo_all to parse the service
+ * name as a Unix-domain socket path.
+ */
+ ret = pg_getaddrinfo_all(NULL, portstr, &hint,
+ &conn->addrlist);
+ if (ret || !conn->addrlist)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not translate Unix-domain socket path \"%s\" to address: %s\n"),
+ portstr, gai_strerror(ret));
+ goto keep_going;
+ }
+#else
+ Assert(false);
+#endif
+ break;
+ }
+
+ /* OK, scan this addrlist for a working server address */
+ conn->addr_cur = conn->addrlist;
reset_connection_state_machine = true;
conn->try_next_host = false;
}
@@ -3134,8 +3131,8 @@ keep_going: /* We will come back to here until there is
return PGRES_POLLING_READING;
}
- /* We can release the address lists now. */
- release_all_addrinfo(conn);
+ /* We can release the address list now. */
+ release_conn_addrinfo(conn);
/* We are open for business! */
conn->status = CONNECTION_OK;
@@ -3197,8 +3194,8 @@ keep_going: /* We will come back to here until there is
return PGRES_POLLING_READING;
}
- /* We can release the address lists now. */
- release_all_addrinfo(conn);
+ /* We can release the address list now. */
+ release_conn_addrinfo(conn);
/* We are open for business! */
conn->status = CONNECTION_OK;
@@ -3227,6 +3224,9 @@ keep_going: /* We will come back to here until there is
goto keep_going;
}
+ /* We can release the address list now. */
+ release_conn_addrinfo(conn);
+
/* We are open for business! */
conn->status = CONNECTION_OK;
return PGRES_POLLING_OK;
@@ -3300,9 +3300,6 @@ keep_going: /* We will come back to here until there is
PQclear(res);
termPQExpBuffer(&savedMessage);
- /* We can release the address lists now. */
- release_all_addrinfo(conn);
-
/*
* Finish reading any remaining messages before being
* considered as ready.
@@ -3639,32 +3636,18 @@ freePGconn(PGconn *conn)
}
/*
- * release_all_addrinfo
- * - free addrinfo of all hostconn elements.
+ * release_conn_addrinfo
+ * - Free any addrinfo list in the PGconn.
*/
-
static void
-release_all_addrinfo(PGconn *conn)
+release_conn_addrinfo(PGconn *conn)
{
- if (conn->connhost != NULL)
+ if (conn->addrlist)
{
- int i;
-
- for (i = 0; i < conn->nconnhost; ++i)
- {
- int family = AF_UNSPEC;
-
-#ifdef HAVE_UNIX_SOCKETS
- if (conn->connhost[i].type == CHT_UNIX_SOCKET)
- family = AF_UNIX;
-#endif
-
- pg_freeaddrinfo_all(family,
- conn->connhost[i].addrlist);
- conn->connhost[i].addrlist = NULL;
- }
+ pg_freeaddrinfo_all(conn->addrlist_family, conn->addrlist);
+ conn->addrlist = NULL;
+ conn->addr_cur = NULL; /* for safety */
}
- conn->addr_cur = NULL;
}
/*
@@ -3723,7 +3706,7 @@ closePGconn(PGconn *conn)
conn->xactStatus = PQTRANS_IDLE;
pqClearAsyncResult(conn); /* deallocate result */
resetPQExpBuffer(&conn->errorMessage);
- release_all_addrinfo(conn);
+ release_conn_addrinfo(conn);
/* Reset all state obtained from server, too */
pqDropServerData(conn);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index bc60373cb0f..4174b6ff17c 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -312,7 +312,7 @@ typedef struct pg_conn_host
char *password; /* password for this host, read from the
* password file; NULL if not sought or not
* found in password file. */
- struct addrinfo *addrlist; /* list of possible backend addresses */
+ struct addrinfo *was_addrlist; /* dummy for ABI compatibility */
} pg_conn_host;
/*
@@ -496,6 +496,10 @@ struct pg_conn
/* Buffer for receiving various parts of messages */
PQExpBufferData workBuffer; /* expansible string */
+
+ /* Placed at the end, in this branch, to minimize ABI breakage */
+ struct addrinfo *addrlist; /* list of addresses for current connhost */
+ int addrlist_family; /* needed to know how to free addrlist */
};
/* PGcancel stores all data necessary to cancel a connection. A copy of this