]> granicus.if.org Git - postgresql/commitdiff
Fix potential deadlock with libpq non-blocking mode.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 23 Feb 2015 11:32:34 +0000 (13:32 +0200)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 23 Feb 2015 11:32:39 +0000 (13:32 +0200)
If libpq output buffer is full, pqSendSome() function tries to drain any
incoming data. This avoids deadlock, if the server e.g. sends a lot of
NOTICE messages, and blocks until we read them. However, pqSendSome() only
did that in blocking mode. In non-blocking mode, the deadlock could still
happen.

To fix, take a two-pronged approach:

1. Change the documentation to instruct that when PQflush() returns 1, you
should wait for both read- and write-ready, and call PQconsumeInput() if it
becomes read-ready. That fixes the deadlock, but applications are not going
to change overnight.

2. In pqSendSome(), drain the input buffer before returning 1. This
alleviates the problem for applications that only wait for write-ready. In
particular, a slow but steady stream of NOTICE messages during COPY FROM
STDIN will no longer cause a deadlock. The risk remains that the server
attempts to send a large burst of data and fills its output buffer, and at
the same time the client also sends enough data to fill its output buffer.
The application will deadlock if it goes to sleep, waiting for the socket
to become write-ready, before the server's data arrives. In practice,
NOTICE messages and such that the server might be sending are usually
short, so it's highly unlikely that the server would fill its output buffer
so quickly.

Backpatch to all supported versions.

doc/src/sgml/libpq.sgml
src/interfaces/libpq/fe-misc.c

index d0fbd487f1299655ba04a4bc823a35163ae17378..54dd71e03bfd5fe0e970d2d65439034e5b126c94 100644 (file)
@@ -4380,7 +4380,14 @@ int PQflush(PGconn *conn);
   <para>
    After sending any command or data on a nonblocking connection, call
    <function>PQflush</function>.  If it returns 1, wait for the socket
-   to be write-ready and call it again; repeat until it returns 0.  Once
+   to become read- or write-ready.  If it becomes write-ready, call
+   <function>PQflush</function> again.  If it becomes read-ready, call
+   <function>PQconsumeInput</function>, then call
+   <function>PQflush</function> again.  Repeat until
+   <function>PQflush</function> returns 0.  (It is necessary to check for
+   read-ready and drain the input with <function>PQconsumeInput</function>,
+   because the server can block trying to send us data, e.g. NOTICE
+   messages, and won't read our data until we read its.)  Once
    <function>PQflush</function> returns 0, wait for the socket to be
    read-ready and then read the response as described above.
   </para>
index 1d5959b9ed3d95b28926bce08469f011f63794f5..488841e1e6b3cc25a9f8b27bdbe7dd25356d7b13 100644 (file)
@@ -904,16 +904,6 @@ pqSendSome(PGconn *conn, int len)
                        /*
                         * We didn't send it all, wait till we can send more.
                         *
-                        * If the connection is in non-blocking mode we don't wait, but
-                        * return 1 to indicate that data is still pending.
-                        */
-                       if (pqIsnonblocking(conn))
-                       {
-                               result = 1;
-                               break;
-                       }
-
-                       /*
                         * There are scenarios in which we can't send data because the
                         * communications channel is full, but we cannot expect the server
                         * to clear the channel eventually because it's blocked trying to
@@ -924,12 +914,29 @@ pqSendSome(PGconn *conn, int len)
                         * again.  Furthermore, it is possible that such incoming data
                         * might not arrive until after we've gone to sleep.  Therefore,
                         * we wait for either read ready or write ready.
+                        *
+                        * In non-blocking mode, we don't wait here directly, but return
+                        * 1 to indicate that data is still pending.  The caller should
+                        * wait for both read and write ready conditions, and call
+                        * PQconsumeInput() on read ready, but just in case it doesn't, we
+                        * call pqReadData() ourselves before returning.  That's not
+                        * enough if the data has not arrived yet, but it's the best we
+                        * 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.)
                         */
                        if (pqReadData(conn) < 0)
                        {
                                result = -1;    /* error message already set up */
                                break;
                        }
+
+                       if (pqIsnonblocking(conn))
+                       {
+                               result = 1;
+                               break;
+                       }
+
                        if (pqWait(TRUE, TRUE, conn))
                        {
                                result = -1;