Clean up some problems in new asynchronous-connection logic

in libpq --- mostly, poor response to error conditions.  You now actually
get to see the postmaster's 'The Data Base System is starting up' message,
which you didn't before.  I suspect the SSL code is still broken though.
This commit is contained in:
Tom Lane 2000-01-14 05:33:15 +00:00
parent 0e6d72db55
commit 2af360ed1c
4 changed files with 160 additions and 178 deletions

View File

@ -323,23 +323,12 @@ PostgresPollingStatusType *PQconnectPoll(PQconn *conn)
</listitem>
<listitem>
<para>
CONNECTION_AWAITING_RESPONSE: Waiting for a response from the backend.
CONNECTION_AWAITING_RESPONSE: Waiting for a response from the postmaster.
</para>
</listitem>
<listitem>
<para>
CONNECTION_AUTH_RESPONSE: Got an authentication response; about to deal
with it.
</para>
</listitem>
<listitem>
<para>
CONNECTION_ERROR_RESPONSE: Got an error response; about to deal with it.
</para>
</listitem>
<listitem>
<para>
CONNECTION_AUTH_OK: Received authentication; waiting for ReadyForQuery etc.
CONNECTION_AUTH_OK: Received authentication; waiting for backend startup.
</para>
</listitem>
<listitem>
@ -373,7 +362,7 @@ PostgresPollingStatusType *PQconnectPoll(PQconn *conn)
</para>
<para>
Note that if PQconnectStart returns a non-NULL pointer, you must call
PQfinish upon that, when you are finished with it, in order to dispose of
PQfinish when you are finished with it, in order to dispose of
the structure and any associated memory blocks. This must be done even if a
call to PQconnectStart or PQconnectPoll failed.
</para>

View File

@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.108 1999/12/02 00:26:15 tgl Exp $
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.109 2000/01/14 05:33:15 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -218,15 +218,15 @@ static struct EnvironmentOptions
* You should call PQfinish (if conn is not NULL) regardless of whether this
* call succeeded.
*
* ---------------- */
* ----------------
*/
PGconn *
PQconnectdb(const char *conninfo)
{
PGconn *conn = PQconnectStart(conninfo);
(void)(!conn || (conn->status == CONNECTION_BAD) ||
!connectDBComplete(conn));
if (conn && conn->status != CONNECTION_BAD)
(void) connectDBComplete(conn);
return conn;
}
@ -250,8 +250,8 @@ PQconnectdb(const char *conninfo)
*
* See PQconnectPoll for more info.
*
* ---------------- */
* ----------------
*/
PGconn *
PQconnectStart(const char *conninfo)
{
@ -306,7 +306,7 @@ PQconnectStart(const char *conninfo)
*/
if (!connectDBStart(conn))
{
/* Just in case we failed to set it in connectDBStart */
/* Just in case we failed to set it in connectDBStart */
conn->status = CONNECTION_BAD;
}
@ -374,15 +374,13 @@ PQconndefaults(void)
* ----------------
*/
PGconn *
PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions, const char *pgtty, const char *dbName, const char *login, const char *pwd)
PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions,
const char *pgtty, const char *dbName, const char *login,
const char *pwd)
{
PGconn *conn;
char *tmp;
/* An error message from some service we call. */
bool error = FALSE;
/* We encountered an error that prevents successful completion */
char *tmp; /* An error message from some service we call. */
bool error = FALSE; /* We encountered an error. */
int i;
conn = makeEmptyPGconn();
@ -484,7 +482,8 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions, cons
}
else
{
(void)(!connectDBStart(conn) || !connectDBComplete(conn));
if (connectDBStart(conn))
(void) connectDBComplete(conn);
}
return conn;
@ -676,8 +675,6 @@ connectDBStart(PGconn *conn)
#ifdef USE_SSL
StartupPacket np; /* Used to negotiate SSL connection */
char SSLok;
static int allow_ssl_try = 1; /* Allowed to do SSL negotiation */
int tried_ssl = 0; /* Set if SSL negotiation was tried */
#endif
if (!conn)
@ -797,12 +794,11 @@ connectDBStart(PGconn *conn)
#endif
#ifdef USE_SSL
/* This needs to be done before we set into nonblocking, since SSL negotiation
* does not like that mode */
/* This needs to be done before we set into nonblocking, since SSL
* negotiation does not like that mode */
/* Attempt to negotiate SSL usage */
if (allow_ssl_try) {
tried_ssl = 1;
if (conn->allow_ssl_try) {
memset((char *)&np, 0, sizeof(np));
np.protoVersion = htonl(NEGOTIATE_SSL_CODE);
if (pqPacketSend(conn, (char *) &np, sizeof(StartupPacket)) != STATUS_OK)
@ -812,9 +808,9 @@ connectDBStart(PGconn *conn)
errno, strerror(errno));
goto connect_errReturn;
}
/* Now receive the backends response */
/* Now receive the postmasters response */
if (recv(conn->sock, &SSLok, 1, 0) != 1) {
sprintf(conn->errorMessage, "PQconnectDB() -- couldn't read backend response: errno=%d\n%s\n",
sprintf(conn->errorMessage, "PQconnectDB() -- couldn't read postmaster response: errno=%d\n%s\n",
errno, strerror(errno));
goto connect_errReturn;
}
@ -845,9 +841,9 @@ connectDBStart(PGconn *conn)
else if (SSLok == 'E') {
/* Received error - probably protocol mismatch */
if (conn->Pfdebug)
fprintf(conn->Pfdebug, "Backend reports error, attempting fallback to pre-6.6.\n");
fprintf(conn->Pfdebug, "Postmaster reports error, attempting fallback to pre-6.6.\n");
close(conn->sock);
allow_ssl_try = 0;
conn->allow_ssl_try = FALSE;
return connectDBStart(conn);
}
else if (SSLok != 'N') {
@ -856,8 +852,6 @@ connectDBStart(PGconn *conn)
goto connect_errReturn;
}
}
else
allow_ssl_try = 1; /* We'll allow an attempt to use SSL next time */
#endif
/* ----------
@ -884,8 +878,8 @@ connectDBStart(PGconn *conn)
/* Something's gone wrong */
printfPQExpBuffer(&conn->errorMessage,
"connectDBStart() -- connect() failed: %s\n"
"Is the postmaster running%s at '%s' "
"and accepting connections on %s '%s'?\n",
"\tIs the postmaster running%s at '%s'\n"
"\tand accepting connections on %s '%s'?\n",
strerror(errno),
(family == AF_INET) ? " (with -i)" : "",
conn->pghost ? conn->pghost : "localhost",
@ -938,33 +932,31 @@ static int
connectDBComplete(PGconn *conn)
{
PostgresPollingStatusType flag;
int r = 0, w = 1;
do
{
if(pqWait(r, w, conn))
{
conn->status = CONNECTION_BAD;
return 0;
}
again:
switch(flag = PQconnectPoll(conn))
for (;;) {
flag = PQconnectPoll(conn);
switch (flag)
{
case PGRES_POLLING_ACTIVE:
goto again;
break;
case PGRES_POLLING_OK:
break;
return 1; /* success! */
case PGRES_POLLING_READING:
r = 1;
w = 0;
if (pqWait(1, 0, conn))
{
conn->status = CONNECTION_BAD;
return 0;
}
break;
case PGRES_POLLING_WRITING:
r = 0;
w = 1;
if (pqWait(0, 1, conn))
{
conn->status = CONNECTION_BAD;
return 0;
}
break;
default:
@ -972,12 +964,9 @@ connectDBComplete(PGconn *conn)
conn->status = CONNECTION_BAD;
return 0;
}
} while (flag != PGRES_POLLING_OK);
return 1;
}
}
/* ----------------
* PQconnectPoll
*
@ -993,17 +982,18 @@ connectDBComplete(PGconn *conn)
* there are a number of caveats:
*
* o If you call PQtrace, ensure that the stream object into which you trace
will not block.
* will not block.
* o If you do not supply an IP address for the remote host (i.e. you
* supply a host name instead) then this function will block on
* gethostbyname. You will be fine if using UDP (i.e. by supplying
* neither a host name nor a host address).
* gethostbyname. You will be fine if using Unix sockets (i.e. by
* supplying neither a host name nor a host address).
* o If your backend wants to use Kerberos authentication then you must
* supply both a host name and a host address, otherwise this function
* may block on gethostname.
* o This function will block if compiled with USE_SSL.
*
* ---------------- */
* ----------------
*/
PostgresPollingStatusType
PQconnectPoll(PGconn *conn)
{
@ -1024,8 +1014,6 @@ PQconnectPoll(PGconn *conn)
/* These are reading states */
case CONNECTION_AWAITING_RESPONSE:
case CONNECTION_AUTH_RESPONSE:
case CONNECTION_ERROR_RESPONSE:
case CONNECTION_AUTH_OK:
{
/* Load waiting data */
@ -1083,10 +1071,21 @@ PQconnectPoll(PGconn *conn)
}
else if (optval != 0)
{
/*
* When using a nonblocking connect, we will typically see
* connect failures at this point, so provide a friendly
* error message.
*/
printfPQExpBuffer(&conn->errorMessage,
"PQconnectPoll() -- "
"socket has error condition %d: %s.\n",
optval, strerror(optval));
"PQconnectPoll() -- connect() failed: %s\n"
"\tIs the postmaster running%s at '%s'\n"
"\tand accepting connections on %s '%s'?\n",
strerror(optval),
(conn->raddr.sa.sa_family == AF_INET) ? " (with -i)" : "",
conn->pghost ? conn->pghost : "localhost",
(conn->raddr.sa.sa_family == AF_INET) ?
"TCP/IP port" : "Unix socket",
conn->pgport);
goto error_return;
}
@ -1124,7 +1123,11 @@ PQconnectPoll(PGconn *conn)
if (conn->pgoptions)
strncpy(sp.options, conn->pgoptions, SM_OPTIONS);
/* Send the startup packet. */
/* Send the startup packet.
*
* Theoretically, this could block, but it really shouldn't
* since we only got here if the socket is write-ready.
*/
if (pqPacketSend(conn, (char *) &sp,
sizeof(StartupPacket)) != STATUS_OK)
@ -1141,16 +1144,19 @@ PQconnectPoll(PGconn *conn)
return PGRES_POLLING_READING;
}
/*
* Handle the authentication exchange: wait for backend messages
* Handle the authentication exchange: wait for postmaster messages
* and respond as necessary.
*/
case CONNECTION_AWAITING_RESPONSE:
{
char beresp;
AuthRequest areq;
/* Scan the message */
/* Scan the message from current point (note that if we find
* the message is incomplete, we will return without advancing
* inStart, and resume here next time).
*/
conn->inCursor = conn->inStart;
if (pqGetc(&beresp, conn))
@ -1162,28 +1168,29 @@ PQconnectPoll(PGconn *conn)
/* Handle errors. */
if (beresp == 'E')
{
conn->status = CONNECTION_ERROR_RESPONSE;
goto keep_going;
if (pqGets(&conn->errorMessage, conn))
{
/* We'll come back when there are more data */
return PGRES_POLLING_READING;
}
/* OK, we read the message; mark data consumed */
conn->inStart = conn->inCursor;
/* The postmaster typically won't end its message with a
* newline, so add one to conform to libpq conventions.
*/
appendPQExpBufferChar(&conn->errorMessage, '\n');
goto error_return;
}
/* Otherwise it should be an authentication request. */
if (beresp != 'R')
{
printfPQExpBuffer(&conn->errorMessage,
"PQconnectPoll() -- expected authentication "
"request\n");
"PQconnectPoll() -- expected "
"authentication request\n");
goto error_return;
}
/* Got an authentication request, so that's OK */
conn->status = CONNECTION_AUTH_RESPONSE;
goto keep_going;
}
case CONNECTION_AUTH_RESPONSE:
{
AuthRequest areq;
/* Get the type of request. */
if (pqGetInt((int *) &areq, 4, conn))
{
@ -1205,7 +1212,7 @@ PQconnectPoll(PGconn *conn)
conn->inStart = conn->inCursor;
/* Respond to the request if necessary. */
/* Note that conn->pghost must be non-NULL if we are going
/* Note that conn->pghost must be non-NULL if we are going to
* avoid the Kerberos code doing a hostname look-up. */
/* XXX fe-auth.c has not been fixed to support PQExpBuffers, so: */
if (fe_sendauth(areq, conn, conn->pghost, conn->pgpass,
@ -1216,10 +1223,10 @@ PQconnectPoll(PGconn *conn)
}
conn->errorMessage.len = strlen(conn->errorMessage.data);
/* This function has a section near the end that looks like it
* should block. I think that it will be OK though, since the
* socket is non-blocking, and thus the data should get out
* as quickly as possible. */
/* Just make sure that any data sent by fe_sendauth is flushed
* out. Although this theoretically could block, it really
* shouldn't since we don't send large auth responses.
*/
if (pqFlush(conn))
goto error_return;
@ -1230,21 +1237,12 @@ PQconnectPoll(PGconn *conn)
/* Set asyncStatus so that PQsetResult will think that what
* comes back next is the result of a query. See below. */
conn->asyncStatus = PGASYNC_BUSY;
goto keep_going;
}
conn->status = CONNECTION_AWAITING_RESPONSE;
return PGRES_POLLING_READING;
/* Look to see if we have more data yet. */
goto keep_going;
}
case CONNECTION_ERROR_RESPONSE:
if (pqGets(&conn->errorMessage, conn))
{
/* We'll come back when there are more data */
return PGRES_POLLING_READING;
}
goto error_return;
case CONNECTION_AUTH_OK:
{
/* ----------
@ -1254,14 +1252,12 @@ PQconnectPoll(PGconn *conn)
* messages indicating nonfatal warnings are also allowed by
* the protocol, as is a BackendKeyData message.) Easiest way
* to handle this is to let PQgetResult() read the messages. We
* just have to fake it out about the state of the connection.
* just have to fake it out about the state of the connection,
* by setting asyncStatus = PGASYNC_BUSY (done above).
*----------
*/
if (!PQconsumeInput(conn))
goto error_return;
if(PQisBusy(conn))
if (PQisBusy(conn))
return PGRES_POLLING_READING;
res = PQgetResult(conn);
@ -1273,13 +1269,21 @@ PQconnectPoll(PGconn *conn)
printfPQExpBuffer(&conn->errorMessage,
"PQconnectPoll() -- unexpected message "
"during startup\n");
/* if the resultStatus is FATAL, then conn->errorMessage
* already has a copy of the error; needn't copy it back.
* But add a newline if it's not there already, since
* postmaster error messages may not have one.
*/
if (conn->errorMessage.len <= 0 ||
conn->errorMessage.data[conn->errorMessage.len-1] != '\n')
appendPQExpBufferChar(&conn->errorMessage, '\n');
PQclear(res);
goto error_return;
}
/*
* Post-connection housekeeping. Send environment variables
* to server.
* Post-connection housekeeping. Prepare to send environment
* variables to server.
*/
if ((conn->setenv_handle = PQsetenvStart(conn)) == NULL)
@ -1292,10 +1296,10 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_SETENV:
/* We pretend that the connection is OK for the duration of
theses queries. */
these queries. */
conn->status = CONNECTION_OK;
switch(PQsetenvPoll(conn->setenv_handle))
switch (PQsetenvPoll(conn->setenv_handle))
{
case PGRES_POLLING_OK: /* Success */
conn->status = CONNECTION_OK;
@ -1318,8 +1322,7 @@ PQconnectPoll(PGconn *conn)
default:
printfPQExpBuffer(&conn->errorMessage,
"PQconnectPoll() -- unknown connection state - "
"probably indicative of memory corruption!\n",
sizeof(conn->errorMessage));
"probably indicative of memory corruption!\n");
goto error_return;
}
@ -1378,12 +1381,13 @@ PQsetenvStart(PGconn *conn)
* Polls the process of passing the values of a standard set of environment
* variables to the backend.
*
* ---------------- */
* ----------------
*/
PostgresPollingStatusType
PQsetenvPoll(PGsetenvHandle handle)
{
#ifdef MULTIBYTE
const char envname[] = "PGCLIENTENCODING";
static const char envname[] = "PGCLIENTENCODING";
#endif
if (!handle || handle->state == SETENV_STATE_FAILED)
@ -1451,9 +1455,6 @@ PQsetenvPoll(PGsetenvHandle handle)
{
const char *encoding = 0;
if (!PQconsumeInput(handle->conn))
goto error_return;
if (PQisBusy(handle->conn))
return PGRES_POLLING_READING;
@ -1535,9 +1536,6 @@ PQsetenvPoll(PGsetenvHandle handle)
case SETENV_STATE_OPTION_WAIT:
{
if (!PQconsumeInput(handle->conn))
goto error_return;
if (PQisBusy(handle->conn))
return PGRES_POLLING_READING;
@ -1593,7 +1591,8 @@ PQsetenvPoll(PGsetenvHandle handle)
* Aborts the process of passing the values of a standard set of environment
* variables to the backend.
*
* ---------------- */
* ----------------
*/
void
PQsetenvAbort(PGsetenvHandle handle)
{
@ -1619,54 +1618,51 @@ PQsetenvAbort(PGsetenvHandle handle)
* compatibility problems caused by giving it a return value, especially as
* this function has not been documented previously.
*
* ---------------- */
* ----------------
*/
int
PQsetenv(PGconn *conn)
{
PostgresPollingStatusType flag;
PGsetenvHandle handle;
int r = 0, w = 1;
if((handle = PQsetenvStart(conn)) == NULL)
PostgresPollingStatusType flag;
if ((handle = PQsetenvStart(conn)) == NULL)
return 0;
do
{
if(pqWait(r, w, conn))
{
/* XXX This is not a good sign - perhaps we should mark the
connection as bad here... */
return 0;
}
again:
switch(flag = PQsetenvPoll(handle))
for (;;) {
flag = PQsetenvPoll(handle);
switch (flag)
{
case PGRES_POLLING_ACTIVE:
goto again;
break;
case PGRES_POLLING_OK:
break;
return 1; /* success! */
case PGRES_POLLING_READING:
r = 1;
w = 0;
if (pqWait(1, 0, conn))
{
conn->status = CONNECTION_BAD;
return 0;
}
break;
case PGRES_POLLING_WRITING:
r = 0;
w = 1;
if (pqWait(0, 1, conn))
{
conn->status = CONNECTION_BAD;
return 0;
}
break;
default: /* Failed */
default:
/* Just in case we failed to set it in PQconnectPoll */
conn->status = CONNECTION_BAD;
return 0;
}
} while (flag != PGRES_POLLING_OK);
return 1;
}
}
/*
* makeEmptyPGconn
* - create a PGconn data structure with (as yet) no interesting data
@ -1679,7 +1675,7 @@ makeEmptyPGconn(void)
if (conn == NULL)
return conn;
/* Zero all pointers */
/* Zero all pointers and booleans */
MemSet((char *) conn, 0, sizeof(PGconn));
conn->noticeHook = defaultNoticeProcessor;
@ -1687,6 +1683,10 @@ makeEmptyPGconn(void)
conn->asyncStatus = PGASYNC_IDLE;
conn->notifyList = DLNewList();
conn->sock = -1;
#ifdef USE_SSL
conn->allow_ssl_try = TRUE;
#endif
/*
* The output buffer size is set to 8K, which is the usual size of pipe
* buffers on Unix systems. That way, when we are sending a large
@ -1709,6 +1709,7 @@ makeEmptyPGconn(void)
conn->errorMessage.data == NULL ||
conn->workBuffer.data == NULL)
{
/* out of memory already :-( */
freePGconn(conn);
conn = NULL;
}
@ -1841,10 +1842,9 @@ PQreset(PGconn *conn)
{
closePGconn(conn);
(void)(!connectDBStart(conn) || !connectDBComplete(conn));
if (connectDBStart(conn))
(void) connectDBComplete(conn);
}
return;
}
@ -1863,7 +1863,7 @@ PQresetStart(PGconn *conn)
return connectDBStart(conn);
}
return 1;
return 0;
}

View File

@ -6,7 +6,7 @@
*
* Copyright (c) 1994, Regents of the University of California
*
* $Id: libpq-fe.h,v 1.53 1999/11/30 03:08:19 momjian Exp $
* $Id: libpq-fe.h,v 1.54 2000/01/14 05:33:15 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -41,16 +41,9 @@ extern "C"
CONNECTION_STARTED, /* Waiting for connection to be made. */
CONNECTION_MADE, /* Connection OK; waiting to send. */
CONNECTION_AWAITING_RESPONSE, /* Waiting for a response
from the backend. */
CONNECTION_AUTH_RESPONSE, /* Got an authentication
response; about to deal
with it. */
CONNECTION_ERROR_RESPONSE, /* Got an error
response; about to deal
with it. */
from the postmaster. */
CONNECTION_AUTH_OK, /* Received authentication;
waiting for ReadyForQuery
etc. */
waiting for backend startup. */
CONNECTION_SETENV /* Negotiating environment. */
} ConnStatusType;

View File

@ -11,7 +11,7 @@
*
* Copyright (c) 1994, Regents of the University of California
*
* $Id: libpq-int.h,v 1.14 1999/11/30 03:08:19 momjian Exp $
* $Id: libpq-int.h,v 1.15 2000/01/14 05:33:15 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -27,10 +27,9 @@
/* include stuff found in fe only */
#include "pqexpbuffer.h"
#ifdef USE_SSL
#include "openssl/ssl.h"
#include "openssl/err.h"
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif
/* libpq supports this version of the frontend/backend protocol.
@ -63,7 +62,7 @@
/* Subsidiary-storage management structure for PGresult.
* See space management routines in fe-exec.c for details.
* Note that space[k] refers to the k'th byte starting from the physical
* head of the block.
* head of the block --- it's a union, not a struct!
*/
typedef union pgresult_data PGresult_data;
@ -228,7 +227,8 @@ struct pg_conn
PGsetenvHandle setenv_handle;
#ifdef USE_SSL
SSL *ssl;
bool allow_ssl_try; /* Allowed to try SSL negotiation */
SSL *ssl; /* SSL status, if have SSL connection */
#endif
/* Buffer for current error message */