diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index c96a52bb1b8..e3bf6a7449f 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -537,6 +537,10 @@ pqDropServerData(PGconn *conn) conn->last_sqlstate[0] = '\0'; conn->auth_req_received = false; conn->password_needed = false; + conn->write_failed = false; + if (conn->write_err_msg) + free(conn->write_err_msg); + conn->write_err_msg = NULL; conn->be_pid = 0; conn->be_key = 0; } @@ -3702,6 +3706,8 @@ freePGconn(PGconn *conn) /* Note that conn->Pfdebug is not ours to close or free */ if (conn->last_query) free(conn->last_query); + if (conn->write_err_msg) + free(conn->write_err_msg); if (conn->inBuffer) free(conn->inBuffer); if (conn->outBuffer) diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index ac969e7b3f8..62026538264 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -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; } diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c index e5ef8d44bd4..ea4c9d2ee0c 100644 --- a/src/interfaces/libpq/fe-misc.c +++ b/src/interfaces/libpq/fe-misc.c @@ -824,6 +824,13 @@ definitelyFailed: * * Return 0 on success, -1 on failure and 1 when not all data could be sent * because the socket would block and the connection is non-blocking. + * + * Upon write failure, conn->write_failed is set and the error message is + * saved in conn->write_err_msg, but we clear the output buffer and return + * zero anyway; this is because callers should soldier on until it's possible + * to read from the server and check for an error message. write_err_msg + * should be reported only when we are unable to obtain a server error first. + * (Thus, a -1 result is returned only for an internal *read* failure.) */ static int pqSendSome(PGconn *conn, int len) @@ -832,13 +839,32 @@ pqSendSome(PGconn *conn, int len) int remaining = conn->outCount; int result = 0; + /* + * If we already had a write failure, we will never again try to send data + * on that connection. Even if the kernel would let us, we've probably + * lost message boundary sync with the server. conn->write_failed + * therefore persists until the connection is reset, and we just discard + * all data presented to be written. + */ + if (conn->write_failed) + { + /* conn->write_err_msg should be set up already */ + conn->outCount = 0; + return 0; + } + if (conn->sock == PGINVALID_SOCKET) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("connection not open\n")); + conn->write_failed = true; + /* Transfer error message to conn->write_err_msg, if possible */ + /* (strdup failure is OK, we'll cope later) */ + conn->write_err_msg = strdup(conn->errorMessage.data); + resetPQExpBuffer(&conn->errorMessage); /* Discard queued data; no chance it'll ever be sent */ conn->outCount = 0; - return -1; + return 0; } /* while there's still data to send */ @@ -876,17 +902,24 @@ pqSendSome(PGconn *conn, int len) default: /* pqsecure_write set the error message for us */ + conn->write_failed = true; /* - * We used to close the socket here, but that's a bad idea - * since there might be unread data waiting (typically, a - * NOTICE message from the backend telling us it's - * committing hara-kiri...). Leave the socket open until - * pqReadData finds no more data can be read. But abandon - * attempt to send data. + * Transfer error message to conn->write_err_msg, if + * possible (strdup failure is OK, we'll cope later). + * + * Note: this assumes that pqsecure_write and its children + * will overwrite not append to conn->errorMessage. If + * that's ever changed, we could remember the length of + * conn->errorMessage at entry to this routine, and then + * save and delete just what was appended. */ + conn->write_err_msg = strdup(conn->errorMessage.data); + resetPQExpBuffer(&conn->errorMessage); + + /* Discard queued data; no chance it'll ever be sent */ conn->outCount = 0; - return -1; + return 0; } } else @@ -921,6 +954,9 @@ pqSendSome(PGconn *conn, int len) * can do, and works pretty well in practice. (The documentation * used to say that you only need to wait for write-ready, so * there are still plenty of applications like that out there.) + * + * Note that errors here don't result in write_failed becoming + * set. */ if (pqReadData(conn) < 0) { @@ -956,6 +992,7 @@ pqSendSome(PGconn *conn, int len) * * Return 0 on success, -1 on failure and 1 when not all data could be sent * because the socket would block and the connection is non-blocking. + * (See pqSendSome comments about how failure should be handled.) */ int pqFlush(PGconn *conn) diff --git a/src/interfaces/libpq/fe-protocol2.c b/src/interfaces/libpq/fe-protocol2.c index 1ab14213ee2..0e36974fc4a 100644 --- a/src/interfaces/libpq/fe-protocol2.c +++ b/src/interfaces/libpq/fe-protocol2.c @@ -1450,42 +1450,30 @@ pqFunctionCall2(PGconn *conn, Oid fnid, pqPutInt(fnid, 4, conn) != 0 || /* function id */ pqPutInt(nargs, 4, conn) != 0) /* # of args */ { - pqHandleSendFailure(conn); + /* error message should be set up already */ return NULL; } for (i = 0; i < nargs; ++i) { /* len.int4 + contents */ if (pqPutInt(args[i].len, 4, conn)) - { - pqHandleSendFailure(conn); return NULL; - } if (args[i].isint) { if (pqPutInt(args[i].u.integer, 4, conn)) - { - pqHandleSendFailure(conn); return NULL; - } } else { if (pqPutnchar((char *) args[i].u.ptr, args[i].len, conn)) - { - pqHandleSendFailure(conn); return NULL; - } } } if (pqPutMsgEnd(conn) < 0 || pqFlush(conn)) - { - pqHandleSendFailure(conn); return NULL; - } for (;;) { diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 47dbc3186f1..ec51fee383d 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -1926,50 +1926,35 @@ pqFunctionCall3(PGconn *conn, Oid fnid, pqPutInt(1, 2, conn) < 0 || /* format code: BINARY */ pqPutInt(nargs, 2, conn) < 0) /* # of args */ { - pqHandleSendFailure(conn); + /* error message should be set up already */ return NULL; } for (i = 0; i < nargs; ++i) { /* len.int4 + contents */ if (pqPutInt(args[i].len, 4, conn)) - { - pqHandleSendFailure(conn); return NULL; - } if (args[i].len == -1) continue; /* it's NULL */ if (args[i].isint) { if (pqPutInt(args[i].u.integer, args[i].len, conn)) - { - pqHandleSendFailure(conn); return NULL; - } } else { if (pqPutnchar((char *) args[i].u.ptr, args[i].len, conn)) - { - pqHandleSendFailure(conn); return NULL; - } } } if (pqPutInt(1, 2, conn) < 0) /* result format code: BINARY */ - { - pqHandleSendFailure(conn); return NULL; - } if (pqPutMsgEnd(conn) < 0 || pqFlush(conn)) - { - pqHandleSendFailure(conn); return NULL; - } for (;;) { diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 4a93d8edbc5..dbe0f7e5c0b 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -410,6 +410,8 @@ struct pg_conn bool password_needed; /* true if server demanded a password */ bool sigpipe_so; /* have we masked SIGPIPE via SO_NOSIGPIPE? */ bool sigpipe_flag; /* can we mask SIGPIPE via MSG_NOSIGNAL? */ + bool write_failed; /* have we had a write failure on sock? */ + char *write_err_msg; /* write error message, or NULL if OOM */ /* Transient state needed while establishing connection */ bool try_next_addr; /* time to advance to next address/host? */ @@ -585,7 +587,6 @@ extern void pqSaveMessageField(PGresult *res, char code, extern void pqSaveParameterStatus(PGconn *conn, const char *name, const char *value); extern int pqRowProcessor(PGconn *conn, const char **errmsgp); -extern void pqHandleSendFailure(PGconn *conn); /* === in fe-protocol2.c === */