mirror of
https://github.com/postgres/postgres.git
synced 2025-06-27 23:21:58 +03:00
Restructure libpq's handling of send failures.
Originally, if libpq got a failure (e.g., ECONNRESET) while trying to send data to the server, it would just report that and wash its hands of the matter. It was soon found that that wasn't a very pleasant way of coping with server-initiated disconnections, so we introduced a hack (pqHandleSendFailure) in the code that sends queries to make it peek ahead for server error reports before reporting the send failure. It now emerges that related cases can occur during connection setup; in particular, as of TLS 1.3 it's unsafe to assume that SSL connection failures will be reported by SSL_connect rather than during our first send attempt. We could have fixed that in a hacky way by applying pqHandleSendFailure after a startup packet send failure, but (a) pqHandleSendFailure explicitly disclaims suitability for use in any state except query startup, and (b) the problem still potentially exists for other send attempts in libpq. Instead, let's fix this in a more general fashion by eliminating pqHandleSendFailure altogether, and instead arranging to postpone all reports of send failures in libpq until after we've made an attempt to read and process server messages. The send failure won't be reported at all if we find a server message or detect input EOF. (Note: this removes one of the reasons why libpq typically overwrites, rather than appending to, conn->errorMessage: pqHandleSendFailure needed that behavior so that the send failure report would be replaced if we got a server message or read failure report. Eventually I'd like to get rid of that overwrite behavior altogether, but today is not that day. For the moment, pqSendSome is assuming that its callees will overwrite not append to conn->errorMessage.) Possibly this change should get back-patched someday; but it needs testing first, so let's not consider that till after v12 beta. Discussion: https://postgr.es/m/CAEepm=2n6Nv+5tFfe8YnkUm1fXgvxR0Mm1FoD+QKG-vLNGLyKg@mail.gmail.com
This commit is contained in:
@ -790,6 +790,32 @@ pqSaveErrorResult(PGconn *conn)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* As above, and append conn->write_err_msg to whatever other error we have.
|
||||
* This is used when we've detected a write failure and have exhausted our
|
||||
* chances of reporting something else instead.
|
||||
*/
|
||||
static void
|
||||
pqSaveWriteError(PGconn *conn)
|
||||
{
|
||||
/*
|
||||
* Ensure conn->result is an error result, and add anything in
|
||||
* conn->errorMessage to it.
|
||||
*/
|
||||
pqSaveErrorResult(conn);
|
||||
|
||||
/*
|
||||
* Now append write_err_msg to that. If it's null because of previous
|
||||
* strdup failure, do what we can. (It's likely our machinations here are
|
||||
* all getting OOM failures as well, but ...)
|
||||
*/
|
||||
if (conn->write_err_msg && conn->write_err_msg[0] != '\0')
|
||||
pqCatenateResultError(conn->result, conn->write_err_msg);
|
||||
else
|
||||
pqCatenateResultError(conn->result,
|
||||
libpq_gettext("write to server failed\n"));
|
||||
}
|
||||
|
||||
/*
|
||||
* This subroutine prepares an async result object for return to the caller.
|
||||
* If there is not already an async result object, build an error object
|
||||
@ -1224,7 +1250,7 @@ PQsendQuery(PGconn *conn, const char *query)
|
||||
pqPuts(query, conn) < 0 ||
|
||||
pqPutMsgEnd(conn) < 0)
|
||||
{
|
||||
pqHandleSendFailure(conn);
|
||||
/* error message should be set up already */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1243,7 +1269,7 @@ PQsendQuery(PGconn *conn, const char *query)
|
||||
*/
|
||||
if (pqFlush(conn) < 0)
|
||||
{
|
||||
pqHandleSendFailure(conn);
|
||||
/* error message should be set up already */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1389,7 +1415,7 @@ PQsendPrepare(PGconn *conn,
|
||||
return 1;
|
||||
|
||||
sendFailed:
|
||||
pqHandleSendFailure(conn);
|
||||
/* error message should be set up already */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1641,39 +1667,10 @@ PQsendQueryGuts(PGconn *conn,
|
||||
return 1;
|
||||
|
||||
sendFailed:
|
||||
pqHandleSendFailure(conn);
|
||||
/* error message should be set up already */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* pqHandleSendFailure: try to clean up after failure to send command.
|
||||
*
|
||||
* Primarily, what we want to accomplish here is to process any ERROR or
|
||||
* NOTICE messages that the backend might have sent just before it died.
|
||||
* Since we're in IDLE state, all such messages will get sent to the notice
|
||||
* processor.
|
||||
*
|
||||
* NOTE: this routine should only be called in PGASYNC_IDLE state.
|
||||
*/
|
||||
void
|
||||
pqHandleSendFailure(PGconn *conn)
|
||||
{
|
||||
/*
|
||||
* Accept and parse any available input data, ignoring I/O errors. Note
|
||||
* that if pqReadData decides the backend has closed the channel, it will
|
||||
* close our side of the socket --- that's just what we want here.
|
||||
*/
|
||||
while (pqReadData(conn) > 0)
|
||||
parseInput(conn);
|
||||
|
||||
/*
|
||||
* Be sure to parse available input messages even if we read no data.
|
||||
* (Note: calling parseInput within the above loop isn't really necessary,
|
||||
* but it prevents buffer bloat if there's a lot of data available.)
|
||||
*/
|
||||
parseInput(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Select row-by-row processing mode
|
||||
*/
|
||||
@ -1763,8 +1760,11 @@ PQisBusy(PGconn *conn)
|
||||
/* Parse any available data, if our state permits. */
|
||||
parseInput(conn);
|
||||
|
||||
/* PQgetResult will return immediately in all states except BUSY. */
|
||||
return conn->asyncStatus == PGASYNC_BUSY;
|
||||
/*
|
||||
* PQgetResult will return immediately in all states except BUSY, or if we
|
||||
* had a write failure.
|
||||
*/
|
||||
return conn->asyncStatus == PGASYNC_BUSY || conn->write_failed;
|
||||
}
|
||||
|
||||
|
||||
@ -1804,7 +1804,13 @@ PQgetResult(PGconn *conn)
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait for some more data, and load it. */
|
||||
/*
|
||||
* Wait for some more data, and load it. (Note: if the connection has
|
||||
* been lost, pqWait should return immediately because the socket
|
||||
* should be read-ready, either with the last server data or with an
|
||||
* EOF indication. We expect therefore that this won't result in any
|
||||
* undue delay in reporting a previous write failure.)
|
||||
*/
|
||||
if (flushResult ||
|
||||
pqWait(true, false, conn) ||
|
||||
pqReadData(conn) < 0)
|
||||
@ -1820,6 +1826,17 @@ PQgetResult(PGconn *conn)
|
||||
|
||||
/* Parse it. */
|
||||
parseInput(conn);
|
||||
|
||||
/*
|
||||
* If we had a write error, but nothing above obtained a query result
|
||||
* or detected a read error, report the write error.
|
||||
*/
|
||||
if (conn->write_failed && conn->asyncStatus == PGASYNC_BUSY)
|
||||
{
|
||||
pqSaveWriteError(conn);
|
||||
conn->asyncStatus = PGASYNC_IDLE;
|
||||
return pqPrepareAsyncResult(conn);
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the appropriate thing. */
|
||||
@ -2252,7 +2269,7 @@ PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target)
|
||||
return 1;
|
||||
|
||||
sendFailed:
|
||||
pqHandleSendFailure(conn);
|
||||
/* error message should be set up already */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user