Instead of sending application_name as a SET command after the connection

is made, include it in the startup-packet options.  This makes it work more
like every other libpq connection option, in particular it now has the same
response to RESET ALL as the rest.  This also saves one network round trip
for new applications using application_name.  The cost is that if the server
is pre-8.5, it'll reject the startup packet altogether, forcing us to retry
the entire connection cycle.  But on balance we shouldn't be optimizing that
case in preference to the behavior with a new server, especially when doing
so creates visible behavioral oddities.  Per discussion.
This commit is contained in:
Tom Lane 2009-12-02 04:38:35 +00:00
parent 925b32bba1
commit 3dfcf8cc15
3 changed files with 92 additions and 218 deletions

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.380 2009/11/29 20:14:53 petere Exp $ * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.381 2009/12/02 04:38:35 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -83,8 +83,18 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
#define PGPASSFILE "pgpass.conf" #define PGPASSFILE "pgpass.conf"
#endif #endif
/* fall back options if they are not specified by arguments or defined /*
by environment variables */ * Pre-8.5 servers will return this SQLSTATE if asked to set
* application_name in a startup packet. We hard-wire the value rather
* than looking into errcodes.h since it reflects historical behavior
* rather than that of the current code.
*/
#define ERRCODE_APPNAME_UNKNOWN "42704"
/*
* fall back options if they are not specified by arguments or defined
* by environment variables
*/
#define DefaultHost "localhost" #define DefaultHost "localhost"
#define DefaultTty "" #define DefaultTty ""
#define DefaultOption "" #define DefaultOption ""
@ -262,7 +272,6 @@ static int parseServiceInfo(PQconninfoOption *options,
static char *pwdfMatchesString(char *buf, char *token); static char *pwdfMatchesString(char *buf, char *token);
static char *PasswordFromFile(char *hostname, char *port, char *dbname, static char *PasswordFromFile(char *hostname, char *port, char *dbname,
char *username); char *username);
static PostgresPollingStatusType pqAppnamePoll(PGconn *conn);
static void default_threadlock(int acquire); static void default_threadlock(int acquire);
@ -901,6 +910,7 @@ connectDBStart(PGconn *conn)
conn->addr_cur = addrs; conn->addr_cur = addrs;
conn->addrlist_family = hint.ai_family; conn->addrlist_family = hint.ai_family;
conn->pversion = PG_PROTOCOL(3, 0); conn->pversion = PG_PROTOCOL(3, 0);
conn->send_appname = true;
conn->status = CONNECTION_NEEDED; conn->status = CONNECTION_NEEDED;
/* /*
@ -1075,7 +1085,7 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_MADE: case CONNECTION_MADE:
break; break;
/* pqSetenvPoll/pqAppnamePoll will decide whether to proceed. */ /* We allow pqSetenvPoll to decide whether to proceed. */
case CONNECTION_SETENV: case CONNECTION_SETENV:
break; break;
@ -1880,6 +1890,35 @@ keep_going: /* We will come back to here until there is
if (res->resultStatus != PGRES_FATAL_ERROR) if (res->resultStatus != PGRES_FATAL_ERROR)
appendPQExpBuffer(&conn->errorMessage, appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("unexpected message from server during startup\n")); libpq_gettext("unexpected message from server during startup\n"));
else if (conn->send_appname &&
(conn->appname || conn->fbappname))
{
/*
* If we tried to send application_name, check to see
* if the error is about that --- pre-8.5 servers will
* reject it at this stage of the process. If so,
* close the connection and retry without sending
* application_name. We could possibly get a false
* SQLSTATE match here and retry uselessly, but there
* seems no great harm in that; we'll just get the
* same error again if it's unrelated.
*/
const char *sqlstate;
sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
if (sqlstate &&
strcmp(sqlstate, ERRCODE_APPNAME_UNKNOWN) == 0)
{
PQclear(res);
conn->send_appname = false;
/* Must drop the old connection */
pqsecure_close(conn);
closesocket(conn->sock);
conn->sock = -1;
conn->status = CONNECTION_NEEDED;
goto keep_going;
}
}
/* /*
* if the resultStatus is FATAL, then conn->errorMessage * if the resultStatus is FATAL, then conn->errorMessage
@ -1899,12 +1938,6 @@ keep_going: /* We will come back to here until there is
conn->addrlist = NULL; conn->addrlist = NULL;
conn->addr_cur = NULL; conn->addr_cur = NULL;
/*
* Note: To avoid changing the set of application-visible
* connection states, v2 environment setup and v3 application
* name setup both happen in the CONNECTION_SETENV state.
*/
/* Fire up post-connection housekeeping if needed */ /* Fire up post-connection housekeeping if needed */
if (PG_PROTOCOL_MAJOR(conn->pversion) < 3) if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
{ {
@ -1913,13 +1946,6 @@ keep_going: /* We will come back to here until there is
conn->next_eo = EnvironmentOptions; conn->next_eo = EnvironmentOptions;
return PGRES_POLLING_WRITING; return PGRES_POLLING_WRITING;
} }
else if (conn->sversion >= 80500 &&
(conn->appname || conn->fbappname))
{
conn->status = CONNECTION_SETENV;
conn->appname_state = APPNAME_STATE_CMD_SEND;
return PGRES_POLLING_WRITING;
}
/* Otherwise, we are open for business! */ /* Otherwise, we are open for business! */
conn->status = CONNECTION_OK; conn->status = CONNECTION_OK;
@ -1927,45 +1953,36 @@ keep_going: /* We will come back to here until there is
} }
case CONNECTION_SETENV: case CONNECTION_SETENV:
/*
* Do post-connection housekeeping (only needed in protocol 2.0).
*
* We pretend that the connection is OK for the duration of these
* queries.
*/
conn->status = CONNECTION_OK;
switch (pqSetenvPoll(conn))
{ {
PostgresPollingStatusType ret; case PGRES_POLLING_OK: /* Success */
break;
/* case PGRES_POLLING_READING: /* Still going */
* Do post-connection housekeeping (only needed in protocol conn->status = CONNECTION_SETENV;
* 2.0), or send the application name in PG8.5+. return PGRES_POLLING_READING;
*
* We pretend that the connection is OK for the duration of
* these queries.
*/
conn->status = CONNECTION_OK;
if (PG_PROTOCOL_MAJOR(conn->pversion) < 3) case PGRES_POLLING_WRITING: /* Still going */
ret = pqSetenvPoll(conn); conn->status = CONNECTION_SETENV;
else /* must be here to send app name */ return PGRES_POLLING_WRITING;
ret = pqAppnamePoll(conn);
switch (ret) default:
{ goto error_return;
case PGRES_POLLING_OK: /* Success */
break;
case PGRES_POLLING_READING: /* Still going */
conn->status = CONNECTION_SETENV;
return PGRES_POLLING_READING;
case PGRES_POLLING_WRITING: /* Still going */
conn->status = CONNECTION_SETENV;
return PGRES_POLLING_WRITING;
default:
goto error_return;
}
/* We are open for business! */
conn->status = CONNECTION_OK;
return PGRES_POLLING_OK;
} }
/* We are open for business! */
conn->status = CONNECTION_OK;
return PGRES_POLLING_OK;
default: default:
appendPQExpBuffer(&conn->errorMessage, appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid connection state %d, " libpq_gettext("invalid connection state %d, "
@ -2031,7 +2048,6 @@ makeEmptyPGconn(void)
conn->options_valid = false; conn->options_valid = false;
conn->nonblocking = false; conn->nonblocking = false;
conn->setenv_state = SETENV_STATE_IDLE; conn->setenv_state = SETENV_STATE_IDLE;
conn->appname_state = APPNAME_STATE_IDLE;
conn->client_encoding = PG_SQL_ASCII; conn->client_encoding = PG_SQL_ASCII;
conn->std_strings = false; /* unless server says differently */ conn->std_strings = false; /* unless server says differently */
conn->verbosity = PQERRORS_DEFAULT; conn->verbosity = PQERRORS_DEFAULT;
@ -4048,129 +4064,6 @@ pqGetHomeDirectory(char *buf, int bufsize)
#endif #endif
} }
/*
* pqAppnamePoll
*
* Polls the process of passing the application name to the backend.
*
* Ideally, we'd include the appname in the startup packet, but that would
* cause old backends to reject the unknown parameter. So we send it in a
* separate query after we have determined the backend version. Once there
* is no interest in pre-8.5 backends, this should be folded into the startup
* packet logic.
*/
static PostgresPollingStatusType
pqAppnamePoll(PGconn *conn)
{
PGresult *res;
if (conn == NULL || conn->status == CONNECTION_BAD)
return PGRES_POLLING_FAILED;
/* Check whether there is any data for us */
switch (conn->appname_state)
{
/* This is a reading state. */
case APPNAME_STATE_CMD_WAIT:
{
/* Load waiting data */
int n = pqReadData(conn);
if (n < 0)
goto error_return;
if (n == 0)
return PGRES_POLLING_READING;
break;
}
/* This is a writing state, so we just proceed. */
case APPNAME_STATE_CMD_SEND:
break;
/* Should we raise an error if called when not active? */
case APPNAME_STATE_IDLE:
return PGRES_POLLING_OK;
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid appname state %d, "
"probably indicative of memory corruption\n"),
conn->appname_state);
goto error_return;
}
/* We will loop here until there is nothing left to do in this call. */
for (;;)
{
switch (conn->appname_state)
{
case APPNAME_STATE_CMD_SEND:
{
const char *val;
char escVal[NAMEDATALEN*2 + 1];
char setQuery[NAMEDATALEN*2 + 26 + 1];
/* Use appname if present, otherwise use fallback */
val = conn->appname ? conn->appname : conn->fbappname;
/*
* Escape the data as needed. We can truncate to NAMEDATALEN,
* so there's no need to cope with malloc.
*/
PQescapeStringConn(conn, escVal, val, NAMEDATALEN, NULL);
sprintf(setQuery, "SET application_name = '%s'", escVal);
if (!PQsendQuery(conn, setQuery))
goto error_return;
conn->appname_state = APPNAME_STATE_CMD_WAIT;
break;
}
case APPNAME_STATE_CMD_WAIT:
{
if (PQisBusy(conn))
return PGRES_POLLING_READING;
res = PQgetResult(conn);
if (res)
{
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
PQclear(res);
goto error_return;
}
PQclear(res);
/* Keep reading until PQgetResult returns NULL */
}
else
{
/* Query finished, so we're done */
conn->appname_state = APPNAME_STATE_IDLE;
return PGRES_POLLING_OK;
}
break;
}
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid appname state %d, "
"probably indicative of memory corruption\n"),
conn->appname_state);
goto error_return;
}
}
/* Unreachable */
error_return:
conn->appname_state = APPNAME_STATE_IDLE;
return PGRES_POLLING_FAILED;
}
/* /*
* To keep the API consistent, the locking stubs are always provided, even * To keep the API consistent, the locking stubs are always provided, even
* if they are not required. * if they are not required.

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.39 2009/06/11 14:49:14 momjian Exp $ * $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.40 2009/12/02 04:38:35 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -1882,6 +1882,7 @@ build_startup_packet(const PGconn *conn, char *packet,
{ {
int packet_len = 0; int packet_len = 0;
const PQEnvironmentOption *next_eo; const PQEnvironmentOption *next_eo;
const char *val;
/* Protocol version comes first. */ /* Protocol version comes first. */
if (packet) if (packet)
@ -1893,50 +1894,38 @@ build_startup_packet(const PGconn *conn, char *packet,
packet_len += sizeof(ProtocolVersion); packet_len += sizeof(ProtocolVersion);
/* Add user name, database name, options */ /* Add user name, database name, options */
#define ADD_STARTUP_OPTION(optname, optval) \
do { \
if (packet) \
strcpy(packet + packet_len, optname); \
packet_len += strlen(optname) + 1; \
if (packet) \
strcpy(packet + packet_len, optval); \
packet_len += strlen(optval) + 1; \
} while(0)
if (conn->pguser && conn->pguser[0]) if (conn->pguser && conn->pguser[0])
{ ADD_STARTUP_OPTION("user", conn->pguser);
if (packet)
strcpy(packet + packet_len, "user");
packet_len += strlen("user") + 1;
if (packet)
strcpy(packet + packet_len, conn->pguser);
packet_len += strlen(conn->pguser) + 1;
}
if (conn->dbName && conn->dbName[0]) if (conn->dbName && conn->dbName[0])
{ ADD_STARTUP_OPTION("database", conn->dbName);
if (packet)
strcpy(packet + packet_len, "database");
packet_len += strlen("database") + 1;
if (packet)
strcpy(packet + packet_len, conn->dbName);
packet_len += strlen(conn->dbName) + 1;
}
if (conn->pgoptions && conn->pgoptions[0]) if (conn->pgoptions && conn->pgoptions[0])
ADD_STARTUP_OPTION("options", conn->pgoptions);
if (conn->send_appname)
{ {
if (packet) /* Use appname if present, otherwise use fallback */
strcpy(packet + packet_len, "options"); val = conn->appname ? conn->appname : conn->fbappname;
packet_len += strlen("options") + 1; if (val && val[0])
if (packet) ADD_STARTUP_OPTION("application_name", val);
strcpy(packet + packet_len, conn->pgoptions);
packet_len += strlen(conn->pgoptions) + 1;
} }
/* Add any environment-driven GUC settings needed */ /* Add any environment-driven GUC settings needed */
for (next_eo = options; next_eo->envName; next_eo++) for (next_eo = options; next_eo->envName; next_eo++)
{ {
const char *val;
if ((val = getenv(next_eo->envName)) != NULL) if ((val = getenv(next_eo->envName)) != NULL)
{ {
if (pg_strcasecmp(val, "default") != 0) if (pg_strcasecmp(val, "default") != 0)
{ ADD_STARTUP_OPTION(next_eo->pgName, val);
if (packet)
strcpy(packet + packet_len, next_eo->pgName);
packet_len += strlen(next_eo->pgName) + 1;
if (packet)
strcpy(packet + packet_len, val);
packet_len += strlen(val) + 1;
}
} }
} }

View File

@ -12,7 +12,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.145 2009/11/28 23:38:08 tgl Exp $ * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.146 2009/12/02 04:38:35 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -244,14 +244,6 @@ typedef enum
SETENV_STATE_IDLE SETENV_STATE_IDLE
} PGSetenvStatusType; } PGSetenvStatusType;
/* PGAppnameStatusType defines the state of the PQAppname state machine */
typedef enum
{
APPNAME_STATE_CMD_SEND, /* About to send the appname */
APPNAME_STATE_CMD_WAIT, /* Waiting for above send to complete */
APPNAME_STATE_IDLE
} PGAppnameStatusType;
/* Typedef for the EnvironmentOptions[] array */ /* Typedef for the EnvironmentOptions[] array */
typedef struct PQEnvironmentOption typedef struct PQEnvironmentOption
{ {
@ -359,8 +351,8 @@ struct pg_conn
struct addrinfo *addr_cur; /* the one currently being tried */ struct addrinfo *addr_cur; /* the one currently being tried */
int addrlist_family; /* needed to know how to free addrlist */ int addrlist_family; /* needed to know how to free addrlist */
PGSetenvStatusType setenv_state; /* for 2.0 protocol only */ PGSetenvStatusType setenv_state; /* for 2.0 protocol only */
PGAppnameStatusType appname_state;
const PQEnvironmentOption *next_eo; const PQEnvironmentOption *next_eo;
bool send_appname; /* okay to send application_name? */
/* Miscellaneous stuff */ /* Miscellaneous stuff */
int be_pid; /* PID of backend --- needed for cancels */ int be_pid; /* PID of backend --- needed for cancels */