Skip to content

Commit 58faeb8

Browse files
committed
Improve libpq's error recovery for connection loss during COPY.
In pqSendSome, if the connection is already closed at entry, discard any queued output data before returning. There is no possibility of ever sending the data, and anyway this corresponds to what we'd do if we'd detected a hard error while trying to send(). This avoids possible indefinite bloat of the output buffer if the application keeps trying to send data (or even just keeps trying to do PQputCopyEnd, as psql indeed will). Because PQputCopyEnd won't transition out of PGASYNC_COPY_IN state until it's successfully queued the COPY END message, and pqPutMsgEnd doesn't distinguish a queuing failure from a pqSendSome failure, this omission allowed an infinite loop in psql if the connection closure occurred when we had at least 8K queued to send. It might be worth refactoring so that we can make that distinction, but for the moment the other changes made here seem to offer adequate defenses. To guard against other variants of this scenario, do not allow PQgetResult to return a PGRES_COPY_XXX result if the connection is already known dead. Make sure it returns PGRES_FATAL_ERROR instead. Per report from Stephen Frost. Back-patch to all active branches.
1 parent bc7ab30 commit 58faeb8

File tree

2 files changed

+36
-12
lines changed

2 files changed

+36
-12
lines changed

src/interfaces/libpq/fe-exec.c

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ static int PQsendQueryGuts(PGconn *conn,
6363
const int *paramFormats,
6464
int resultFormat);
6565
static void parseInput(PGconn *conn);
66+
static PGresult *getCopyResult(PGconn *conn, ExecStatusType copytype);
6667
static bool PQexecStart(PGconn *conn);
6768
static PGresult *PQexecFinish(PGconn *conn);
6869
static int PQsendDescribe(PGconn *conn, char desc_type,
@@ -1713,22 +1714,13 @@ PQgetResult(PGconn *conn)
17131714
conn->asyncStatus = PGASYNC_BUSY;
17141715
break;
17151716
case PGASYNC_COPY_IN:
1716-
if (conn->result && conn->result->resultStatus == PGRES_COPY_IN)
1717-
res = pqPrepareAsyncResult(conn);
1718-
else
1719-
res = PQmakeEmptyPGresult(conn, PGRES_COPY_IN);
1717+
res = getCopyResult(conn, PGRES_COPY_IN);
17201718
break;
17211719
case PGASYNC_COPY_OUT:
1722-
if (conn->result && conn->result->resultStatus == PGRES_COPY_OUT)
1723-
res = pqPrepareAsyncResult(conn);
1724-
else
1725-
res = PQmakeEmptyPGresult(conn, PGRES_COPY_OUT);
1720+
res = getCopyResult(conn, PGRES_COPY_OUT);
17261721
break;
17271722
case PGASYNC_COPY_BOTH:
1728-
if (conn->result && conn->result->resultStatus == PGRES_COPY_BOTH)
1729-
res = pqPrepareAsyncResult(conn);
1730-
else
1731-
res = PQmakeEmptyPGresult(conn, PGRES_COPY_BOTH);
1723+
res = getCopyResult(conn, PGRES_COPY_BOTH);
17321724
break;
17331725
default:
17341726
printfPQExpBuffer(&conn->errorMessage,
@@ -1765,6 +1757,36 @@ PQgetResult(PGconn *conn)
17651757
return res;
17661758
}
17671759

1760+
/*
1761+
* getCopyResult
1762+
* Helper for PQgetResult: generate result for COPY-in-progress cases
1763+
*/
1764+
static PGresult *
1765+
getCopyResult(PGconn *conn, ExecStatusType copytype)
1766+
{
1767+
/*
1768+
* If the server connection has been lost, don't pretend everything is
1769+
* hunky-dory; instead return a PGRES_FATAL_ERROR result, and reset the
1770+
* asyncStatus to idle (corresponding to what we'd do if we'd detected I/O
1771+
* error in the earlier steps in PQgetResult). The text returned in the
1772+
* result is whatever is in conn->errorMessage; we hope that was filled
1773+
* with something relevant when the lost connection was detected.
1774+
*/
1775+
if (conn->status != CONNECTION_OK)
1776+
{
1777+
pqSaveErrorResult(conn);
1778+
conn->asyncStatus = PGASYNC_IDLE;
1779+
return pqPrepareAsyncResult(conn);
1780+
}
1781+
1782+
/* If we have an async result for the COPY, return that */
1783+
if (conn->result && conn->result->resultStatus == copytype)
1784+
return pqPrepareAsyncResult(conn);
1785+
1786+
/* Otherwise, invent a suitable PGresult */
1787+
return PQmakeEmptyPGresult(conn, copytype);
1788+
}
1789+
17681790

17691791
/*
17701792
* PQexec

src/interfaces/libpq/fe-misc.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,8 @@ pqSendSome(PGconn *conn, int len)
808808
{
809809
printfPQExpBuffer(&conn->errorMessage,
810810
libpq_gettext("connection not open\n"));
811+
/* Discard queued data; no chance it'll ever be sent */
812+
conn->outCount = 0;
811813
return -1;
812814
}
813815

0 commit comments

Comments
 (0)