1
0
mirror of https://github.com/postgres/postgres.git synced 2025-10-29 22:49:41 +03:00

Rearrange libpq's error reporting to avoid duplicated error text.

Since commit ffa2e4670, libpq accumulates text in conn->errorMessage
across a whole query cycle.  In some situations, we may report more
than one error event within a cycle: the easiest case to reach is
where we report a FATAL error message from the server, and then a
bit later we detect loss of connection.  Since, historically, each
error PGresult bears the entire content of conn->errorMessage,
this results in duplication of the FATAL message in any output that
concatenates the contents of the PGresults.

Accumulation in errorMessage still seems like a good idea, especially
in view of the number of places that did ad-hoc error concatenation
before ffa2e4670.  So to fix this, let's track how much of
conn->errorMessage has been read out into error PGresults, and only
include new text in later PGresults.  The tricky part of that is
to be sure that we never discard an error PGresult once made (else
we'd risk dropping some text, a problem much worse than duplication).
While libpq formerly did that in some code paths, a little bit of
rearrangement lets us postpone making an error PGresult at all until
we are about to return it.

A side benefit of that postponement is that it now becomes practical
to return a dummy static PGresult in cases where we hit out-of-memory
while trying to manufacture an error PGresult.  This eliminates the
admittedly-very-rare case where we'd return NULL from PQgetResult,
indicating successful query completion, even though what actually
happened was an OOM failure.

Discussion: https://postgr.es/m/ab4288f8-be5c-57fb-2400-e3e857f53e46@enterprisedb.com
This commit is contained in:
Tom Lane
2022-02-18 15:35:15 -05:00
parent 6c417bbcc8
commit 618c16707a
7 changed files with 224 additions and 65 deletions

View File

@@ -496,8 +496,17 @@ struct pg_conn
PGdataValue *rowBuf; /* array for passing values to rowProcessor */
int rowBufLen; /* number of entries allocated in rowBuf */
/* Status for asynchronous result construction */
/*
* Status for asynchronous result construction. If result isn't NULL, it
* is a result being constructed or ready to return. If result is NULL
* and error_result is true, then we need to return a PGRES_FATAL_ERROR
* result, but haven't yet constructed it; text for the error has been
* appended to conn->errorMessage. (Delaying construction simplifies
* dealing with out-of-memory cases.) If next_result isn't NULL, it is a
* PGresult that will replace "result" after we return that one.
*/
PGresult *result; /* result being constructed */
bool error_result; /* do we need to make an ERROR result? */
PGresult *next_result; /* next result (used in single-row mode) */
/* Assorted state for SASL, SSL, GSS, etc */
@@ -567,8 +576,14 @@ struct pg_conn
* Buffer for current error message. This is cleared at the start of any
* connection attempt or query cycle; after that, all code should append
* messages to it, never overwrite.
*
* In some situations we might report an error more than once in a query
* cycle. If so, errorMessage accumulates text from all the errors, and
* errorReported tracks how much we've already reported, so that the
* individual error PGresult objects don't contain duplicative text.
*/
PQExpBufferData errorMessage; /* expansible string */
int errorReported; /* # bytes of string already reported */
/* Buffer for receiving various parts of messages */
PQExpBufferData workBuffer; /* expansible string */
@@ -644,7 +659,7 @@ extern pgthreadlock_t pg_g_threadlock;
/* === in fe-exec.c === */
extern void pqSetResultError(PGresult *res, PQExpBuffer errorMessage);
extern void pqSetResultError(PGresult *res, PQExpBuffer errorMessage, int offset);
extern void *pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary);
extern char *pqResultStrdup(PGresult *res, const char *str);
extern void pqClearAsyncResult(PGconn *conn);
@@ -830,6 +845,13 @@ extern void pqTraceOutputNoTypeByteMessage(PGconn *conn, const char *message);
/* === miscellaneous macros === */
/*
* Reset the conn's error-reporting state.
*/
#define pqClearConnErrorState(conn) \
(resetPQExpBuffer(&(conn)->errorMessage), \
(conn)->errorReported = 0)
/*
* this is so that we can check if a connection is non-blocking internally
* without the overhead of a function call