On 01/08/2025 00:48, Jacob Champion wrote:
On Fri, Jul 18, 2025 at 11:11 AM Jacob Champion
<[email protected]> wrote:
The attached still needs some documentation work
v2 does a bunch of commit message work, but I imagine it needs a good
bit of copy-editing for clarity.
I'm not in any hurry to smash this in. I think we still need
- independent verification of the architectural issue, to make sure
it's not any deeper or shallower than pqReadData()
- independent verification that this fixes the bugs that have been described
- measurement of the performance characteristics of the new code
- verification of the maximum amount of additional buffer memory that
can be consumed during the drain
- consensus that we want to maintain this new behavior
- discussion of what we want this code to look like going forward
I agree that making pqReadData() drain the transport buffer is the right
approach. I'm not sure if this can ever work with the OpenSSL readahead
that was discussed, but we don't need to solve that now.
Once we make pqReadData() to always drain the buffer, does
pqSocketCheck() still need to check for pending bytes? It seems harmless
to do it in any case, though, so probably best to keep it.
gss_read() calls pqReadReady() which now calls pqsecure_bytes_pending(),
which now also checks for bytes pending in the GSS buffer. Is that a
good thing or a bad thing or does it not matter?
In pqReadData we have this:
/*
* A return value of 0 could mean just that no data is now available, or
* it could mean EOF --- that is, the server has closed the connection.
* Since we have the socket in nonblock mode, the only way to tell the
* difference is to see if select() is saying that the file is ready.
* Grumble. Fortunately, we don't expect this path to be taken much,
* since in normal practice we should not be trying to read data unless
* the file selected for reading already.
*
* In SSL mode it's even worse: SSL_read() could say WANT_READ and then
* data could arrive before we make the pqReadReady() test, but the
second
* SSL_read() could still say WANT_READ because the data received was
not
* a complete SSL record. So we must play dumb and assume there is more
* data, relying on the SSL layer to detect true EOF.
*/
#ifdef USE_SSL
if (conn->ssl_in_use)
return 0;
#endif
Should we do the same for GSS as we do for SSL here?
Apart from the above-mentioned things, the patch looks bug-free to me.
However, it feels like a layering violation. The *_drain_pending()
functions should not write directly to conn->inBuffer, or expand the buffer.
To avoid that, I propose the attached to move the buffer-expansin logic
to the caller. The pqsecure API now has this:
/*
* Return the number of bytes available in the transport buffer.
*
* If pqsecure_read() is called for this number of bytes, it's
guaranteed to
* return successfully without reading from the underlying socket. See
* pqDrainPending() for a more complete discussion of the concepts
involved.
*/
ssize_t pqsecure_bytes_pending(PGconn *conn);
pqReadData(), or its pqDrainPending() subroutine to be precise, uses
that and pqsecure_read() to drain the buffer. This is less code because
it reuses pqsecure_read(), and doesn't require the TLS/GSS
implementation to reach out into connection->inBuffer.
This does add that assumption to pqsecure_read() that's mentioned in the
comment above though. If we want to avoid that assumption, we could add
another "pqsecure_read_pending()" function that's just like
pqsecure_read(), except that it would only read pending bytes and never
from the socket.
This is completely untested, I just wanted to show the internal design
for now.
- Heikki
From 9956f7448719c62614c2d459f5de8e1f9b9dc3f7 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Fri, 18 Jul 2025 10:06:13 -0700
Subject: [PATCH v3 1/2] libpq: Extend "read pending" check from SSL to GSS
An extra check for pending bytes in the SSL layer has been part of
pqReadReady() for a very long time (79ff2e96d). But when GSS transport
encryption was added, it didn't receive the same treatment. (As
79ff2e96d notes, "The bug that I fixed in this patch is exceptionally
hard to reproduce reliably.")
Without that check, it's possible to hit a hang in gssencmode, if the
server splits a large libpq message such that the final message in a
streamed response is part of the same wrapped token as the split
message:
DataRowDataRowDataRowDataRowDataRowData
-- token boundary --
RowDataRowCommandCompleteReadyForQuery
If the split message takes up enough memory to nearly fill libpq's
receive buffer, libpq may return from pqReadData() before the later
messages are pulled out of the PqGSSRecvBuffer. Without additional
socket activity from the server, pqReadReady() (via pqSocketCheck())
will never again return true, hanging the connection.
Pull the pending-bytes check into the pqsecure API layer, where both SSL
and GSS now implement it.
Note that this does not fix the root problem! Third party clients of
libpq have no way to call pqsecure_read_is_pending() in their own
polling. This just brings the GSS implementation up to par with the
existing SSL workaround; a broader fix is left to a subsequent commit.
(However, pgtls_read_pending() is renamed to pgtls_read_is_pending(), to
avoid conflation with the forthcoming pgtls_drain_pending().)
Discussion: https://postgr.es/m/CAOYmi%2BmpymrgZ76Jre2dx_PwRniS9YZojwH0rZnTuiGHCsj0rA%40mail.gmail.com
---
src/interfaces/libpq/fe-misc.c | 6 ++----
src/interfaces/libpq/fe-secure-gssapi.c | 6 ++++++
src/interfaces/libpq/fe-secure-openssl.c | 2 +-
src/interfaces/libpq/fe-secure.c | 19 +++++++++++++++++++
src/interfaces/libpq/libpq-int.h | 4 +++-
5 files changed, 31 insertions(+), 6 deletions(-)
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index dca44fdc5d2..434216ff89f 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -1099,14 +1099,12 @@ pqSocketCheck(PGconn *conn, int forRead, int forWrite, pg_usec_time_t end_time)
return -1;
}
-#ifdef USE_SSL
- /* Check for SSL library buffering read bytes */
- if (forRead && conn->ssl_in_use && pgtls_read_pending(conn))
+ /* Check for SSL/GSS library buffering read bytes */
+ if (forRead && pqsecure_read_is_pending(conn))
{
/* short-circuit the select */
return 1;
}
-#endif
}
/* We will retry as long as we get EINTR */
diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c
index 843b31e175f..5067b3de9f4 100644
--- a/src/interfaces/libpq/fe-secure-gssapi.c
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -471,6 +471,12 @@ gss_read(PGconn *conn, void *recv_buffer, size_t length, ssize_t *ret)
return PGRES_POLLING_OK;
}
+bool
+pg_GSS_read_is_pending(PGconn *conn)
+{
+ return PqGSSResultLength > PqGSSResultNext;
+}
+
/*
* Negotiate GSSAPI transport for a connection. When complete, returns
* PGRES_POLLING_OK. Will return PGRES_POLLING_READING or
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index d2045c73ae6..69256c91cdb 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -231,7 +231,7 @@ rloop:
}
bool
-pgtls_read_pending(PGconn *conn)
+pgtls_read_is_pending(PGconn *conn)
{
return SSL_pending(conn->ssl) > 0;
}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index e686681ba15..94c97ec26fb 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -243,6 +243,25 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
return n;
}
+/*
+ * Returns true if there are any bytes available in the transport buffer.
+ */
+bool
+pqsecure_read_is_pending(PGconn *conn)
+{
+#ifdef USE_SSL
+ if (conn->ssl_in_use)
+ return pgtls_read_is_pending(conn);
+#endif
+#ifdef ENABLE_GSS
+ if (conn->gssenc)
+ return pg_GSS_read_is_pending(conn);
+#endif
+
+ /* Plaintext connections have no transport buffer. */
+ return 0;
+}
+
/*
* Write data to a secure connection.
*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 0f3661b8889..e8a1f248805 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -819,6 +819,7 @@ extern int pqWriteReady(PGconn *conn);
extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
extern void pqsecure_close(PGconn *);
extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
+extern bool pqsecure_read_is_pending(PGconn *);
extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
extern ssize_t pqsecure_raw_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_write(PGconn *, const void *ptr, size_t len);
@@ -857,7 +858,7 @@ extern ssize_t pgtls_read(PGconn *conn, void *ptr, size_t len);
/*
* Is there unread data waiting in the SSL read buffer?
*/
-extern bool pgtls_read_pending(PGconn *conn);
+extern bool pgtls_read_is_pending(PGconn *conn);
/*
* Write data to a secure connection.
@@ -905,6 +906,7 @@ extern PostgresPollingStatusType pqsecure_open_gss(PGconn *conn);
*/
extern ssize_t pg_GSS_write(PGconn *conn, const void *ptr, size_t len);
extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len);
+extern bool pg_GSS_read_is_pending(PGconn *conn);
#endif
/* === in fe-trace.c === */
--
2.47.3
From 11a8f0a2c91b9ab81df90c6d306896f2269d82b5 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 5 Dec 2025 15:11:49 +0200
Subject: [PATCH v3 2/2] libpq: Drain all pending bytes from SSL/GSS during
pqReadData()
The previous commit strengthened a workaround for a hang when large
messages are split across TLS records/GSS tokens. Because that
workaround is implemented in libpq internals, it can only help us when
libpq itself is polling on the socket. In nonblocking situations, where
the client above libpq is expected to poll, the same bugs can show up.
As a contrived example, consider a large protocol-2.0 error coming back
from a server during PQconnectPoll(), split in an odd way across two
records:
-- TLS record (8192-byte payload) --
EEEE[...repeated a total of 8192 times]
-- TLS record (8193-byte payload) --
EEEE[...repeated a total of 8192 times]\0
The first record will fill the first half of the libpq receive buffer,
which is 16k long by default. The second record completely fills the
last half with its first 8192 bytes, leaving the terminating NULL in the
OpenSSL buffer. Since we still haven't seen the terminator at our level,
PQconnectPoll() will return PGRES_POLLING_READING, expecting to come
back when the server has sent "the rest" of the data. But there is
nothing left to read from the socket; OpenSSL had to pull all of the
data in the 8193-byte record off of the wire to decrypt it.
(A real server would probably not split up the records this way, nor
keep the connection open after sending a fatal connection error. But
servers that regularly use larger TLS records can get the libpq receive
buffer into the same state if DataRows are big enough, as reported on
the list.)
This is a layering violation. libpq makes decisions based on data in the
application buffer, above the transport buffer (whether SSL or GSS), but
clients are polling the socket, below the transport buffer. One way to
fix this in a backportable way, without changing APIs too much, is to
ensure data never stays in the transport buffer. Then pqReadData's
postconditions will look similar for both raw sockets and SSL/GSS: any
available data is either in the application buffer, or still on the
socket.
Building on the prior commit, make pqReadData() to drain all pending
data from the transport layer into conn->inBuffer, expanding the
buffer as necessary. This is not particularly efficient from an
architectural perspective (the pqsecure_read() implementations take
care to fit their packets into the current buffer, and that effort is
now completely discarded), but it's hopefully easier to reason about
than a full rewrite would be for the back branches.
Author: Jacob Champion <[email protected]>
Reported-by: Lars Kanis <[email protected]>
Discussion: https://postgr.es/m/2039ac58-d3e0-434b-ac1a-2a987f3b4cb1%40greiz-reinsdorf.de
Backpatch-through: 14
---
src/interfaces/libpq/fe-misc.c | 140 ++++++++++++++++++++++-
src/interfaces/libpq/fe-secure-gssapi.c | 7 +-
src/interfaces/libpq/fe-secure-openssl.c | 38 +++++-
src/interfaces/libpq/fe-secure.c | 14 ++-
src/interfaces/libpq/libpq-int.h | 8 +-
5 files changed, 189 insertions(+), 18 deletions(-)
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 434216ff89f..dd555799ae3 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -55,6 +55,8 @@ static int pqPutMsgBytes(const void *buf, size_t len, PGconn *conn);
static int pqSendSome(PGconn *conn, int len);
static int pqSocketCheck(PGconn *conn, int forRead, int forWrite,
pg_usec_time_t end_time);
+static int pqReadData_internal(PGconn *conn);
+static int pqDrainPending(PGconn *conn);
/*
* PQlibVersion: return the libpq version number
@@ -593,6 +595,13 @@ pqPutMsgEnd(PGconn *conn)
/* ----------
* pqReadData: read more data, if any is available
+ *
+ * Upon a successful return, callers may assume that either 1) all available
+ * bytes have been consumed from the socket, or 2) the socket is still marked
+ * readable by the OS. (In other words: after a successful pqReadData, it's safe
+ * to tell a client to poll for readable bytes on the socket without any further
+ * draining of the SSL/GSS transport buffers.)
+ *
* Possible return values:
* 1: successfully loaded at least one more byte
* 0: no data is presently available, but no error detected
@@ -605,8 +614,7 @@ pqPutMsgEnd(PGconn *conn)
int
pqReadData(PGconn *conn)
{
- int someread = 0;
- int nread;
+ int available;
if (conn->sock == PGINVALID_SOCKET)
{
@@ -614,6 +622,40 @@ pqReadData(PGconn *conn)
return -1;
}
+ available = pqReadData_internal(conn);
+ if (available < 0)
+ return -1;
+ else if (available > 0)
+ {
+ /*
+ * Make sure there are no bytes stuck in layers between conn->inBuffer
+ * and the socket, to make it safe for clients to poll on PQsocket().
+ */
+ if (pqDrainPending(conn))
+ return -1;
+ }
+ else
+ {
+ /*
+ * If we're not returning any bytes from the underlying transport,
+ * that must imply there aren't any in the transport buffer...
+ */
+ Assert(pqsecure_bytes_pending(conn) == 0);
+ }
+
+ return available;
+}
+
+/*
+ * Workhorse for pqReadData(). It's kept separate from the pqDrainPending()
+ * logic to avoid adding to this function's goto complexity.
+ */
+static int
+pqReadData_internal(PGconn *conn)
+{
+ int someread = 0;
+ int nread;
+
/* Left-justify any data in the buffer to make room */
if (conn->inStart < conn->inEnd)
{
@@ -724,6 +766,8 @@ retry3:
* SSL_read() could still say WANT_READ because the data received was not
* a complete SSL record. So we must play dumb and assume there is more
* data, relying on the SSL layer to detect true EOF.
+ *
+ * XXX: do we have the same issue with GSS encryption?
*/
#ifdef USE_SSL
@@ -800,6 +844,96 @@ definitelyFailed:
return -1;
}
+/*---
+ * Drains any transport data that is already buffered in userspace and adds it
+ * to conn->inBuffer, enlarging inBuffer if necessary. The drain fails if
+ * inBuffer cannot be made to hold all available transport data.
+ *
+ * We assume that the underlying secure transport implementation does not
+ * attempt to read any more data from the socket while draining the transport
+ * buffer. After a successful return, pqsecure_bytes_pending() must be zero.
+ *
+ * This operation is necessary to prevent deadlock, due to a layering violation
+ * designed into our asynchronous client API: pqReadData() and all the parsing
+ * routines above it receive data from the SSL/GSS transport buffer, but clients
+ * poll on the raw PQsocket() handle. So data can be "lost" in the intermediate
+ * layer if we don't take it out here.
+ *
+ * To illustrate what we're trying to prevent, say that the server is sending
+ * two messages at once in response to a query (Aaaa and Bb), the libpq buffer
+ * is five characters in size, and TLS records max out at three-character
+ * payloads.
+ *
+ * Client libpq SSL Socket
+ * | | | |
+ * | [ ] [ ] [ ] [1] Buffers are empty, client is
+ * x --------------------------> | polling on socket
+ * | | | |
+ * | [ ] [ ] [xxx] [2] First record is received; poll
+ * | <-------------------------- | signals read-ready
+ * | | | |
+ * x ---> [ ] [ ] [xxx] [3] Client calls PQconsumeInput()
+ * | | | |
+ * | [ ] -> [ ] [xxx] [4] libpq calls pqReadData() to fill
+ * | | | | the receive buffer
+ * | [ ] [Aaa] <-- [ ] [5] SSL pulls payload off the wire
+ * | | | | and decrypts it
+ * | [Aaa ] <- [ ] [ ] [6] pqsecure_read() takes all data
+ * | | | |
+ * | <--- [Aaa ] [ ] [ ] [7] PQconsumeInput() returns with a
+ * x --------------------------> | partial message, PQisBusy() is
+ * | | | | still true, client polls again
+ * | [Aaa ] [ ] [xxx] [8] Second record is received; poll
+ * | <-------------------------- | signals read-ready
+ * | | | |
+ * x ---> [Aaa ] [ ] [xxx] [9] Client calls PQconsumeInput()
+ * | | | |
+ * | [Aaa ] -> [ ] [xxx] [10] libpq calls pqReadData() to fill
+ * | | | | the receive buffer
+ * | [Aaa ] [aBb] <-- [ ] [11] SSL decrypts
+ * | | | |
+ * | [AaaaB] <- [b ] [ ] [12] pqsecure_read() fills its
+ * | | | | buffer, taking only two bytes
+ * | <--- [AaaaB] [b ] [ ] [13] PQconsumeInput() returns with a
+ * | | | | complete message buffered;
+ * | | | | PQisBusy() is false
+ * x ---> [AaaaB] [b ] [ ] [14] Client calls PQgetResult()
+ * | | | |
+ * | <--- [B ] [b ] [ ] [15] Aaaa is returned; PQisBusy() is
+ * x --------------------------> | true and client polls again
+ * . | | .
+ * . [B ] [b ] . [16] No packets, and client hangs.
+ * . | | .
+ *
+ */
+static int
+pqDrainPending(PGconn *conn)
+{
+ ssize_t bytes_pending;
+ ssize_t nread;
+
+ bytes_pending = pqsecure_bytes_pending(conn);
+ if (bytes_pending <= 0)
+ return bytes_pending;
+
+ /* Expand the input buffer if necessary. */
+ if (pqCheckInBufferSpace(conn->inEnd + (size_t) bytes_pending, conn))
+ return -1; /* errorMessage already set */
+
+ nread = pqsecure_read(conn, conn->inBuffer + conn->inEnd,
+ bytes_pending);
+
+ /* When there are bytes pending, the read function is not supposed to fail */
+ if (nread != bytes_pending)
+ {
+ libpq_append_conn_error(conn,
+ "drained only %zu of %zd pending bytes in transport buffer",
+ nread, bytes_pending);
+ return -1;
+ }
+ return 0;
+}
+
/*
* pqSendSome: send data waiting in the output buffer.
*
@@ -1100,7 +1234,7 @@ pqSocketCheck(PGconn *conn, int forRead, int forWrite, pg_usec_time_t end_time)
}
/* Check for SSL/GSS library buffering read bytes */
- if (forRead && pqsecure_read_is_pending(conn))
+ if (forRead && pqsecure_bytes_pending(conn) != 0)
{
/* short-circuit the select */
return 1;
diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c
index 5067b3de9f4..05abdbffcc6 100644
--- a/src/interfaces/libpq/fe-secure-gssapi.c
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -471,10 +471,11 @@ gss_read(PGconn *conn, void *recv_buffer, size_t length, ssize_t *ret)
return PGRES_POLLING_OK;
}
-bool
-pg_GSS_read_is_pending(PGconn *conn)
+ssize_t
+pg_GSS_bytes_pending(PGconn *conn)
{
- return PqGSSResultLength > PqGSSResultNext;
+ Assert(PqGSSResultLength >= PqGSSResultNext);
+ return (ssize_t) (PqGSSResultLength - PqGSSResultNext);
}
/*
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 69256c91cdb..5f652486a9c 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -230,10 +230,42 @@ rloop:
return n;
}
-bool
-pgtls_read_is_pending(PGconn *conn)
+ssize_t
+pgtls_bytes_pending(PGconn *conn)
{
- return SSL_pending(conn->ssl) > 0;
+ int pending;
+
+ /*
+ * OpenSSL readahead is documented to break SSL_pending(). Plus, we can't
+ * afford to have OpenSSL take bytes off the socket without processing
+ * them; that breaks the postconditions for pqsecure_drain_pending().
+ */
+ Assert(!SSL_get_read_ahead(conn->ssl));
+
+ /* Figure out how many bytes to take off the connection. */
+ pending = SSL_pending(conn->ssl);
+
+ if (pending < 0)
+ {
+ /* shouldn't be possible */
+ Assert(false);
+ libpq_append_conn_error(conn, "OpenSSL reports negative bytes pending");
+ return -1;
+ }
+ else if (pending == INT_MAX)
+ {
+ /*
+ * If we ever found a legitimate way to hit this, we'd need to loop
+ * around in the caller to call pgtls_bytes_pending() again. Throw an
+ * error rather than complicate the code in that way, because
+ * SSL_read() should be bounded to the size of a single TLS record,
+ * and conn->inBuffer can't currently go past INT_MAX in size anyway.
+ */
+ libpq_append_conn_error(conn, "OpenSSL reports INT_MAX bytes pending");
+ return -1;
+ }
+
+ return (ssize_t) pending;
}
ssize_t
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 94c97ec26fb..8ee4969d820 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -244,18 +244,22 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
}
/*
- * Returns true if there are any bytes available in the transport buffer.
+ * Return the number of bytes available in the transport buffer.
+ *
+ * If pqsecure_read() is called for this number of bytes, it's guaranteed to
+ * return successfully without reading from the underlying socket. See
+ * pqDrainPending() for a more complete discussion of the concepts involved.
*/
-bool
-pqsecure_read_is_pending(PGconn *conn)
+ssize_t
+pqsecure_bytes_pending(PGconn *conn)
{
#ifdef USE_SSL
if (conn->ssl_in_use)
- return pgtls_read_is_pending(conn);
+ return pgtls_bytes_pending(conn);
#endif
#ifdef ENABLE_GSS
if (conn->gssenc)
- return pg_GSS_read_is_pending(conn);
+ return pg_GSS_bytes_pending(conn);
#endif
/* Plaintext connections have no transport buffer. */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index e8a1f248805..37fe77e9ca7 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -819,7 +819,7 @@ extern int pqWriteReady(PGconn *conn);
extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
extern void pqsecure_close(PGconn *);
extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
-extern bool pqsecure_read_is_pending(PGconn *);
+extern ssize_t pqsecure_bytes_pending(PGconn *);
extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
extern ssize_t pqsecure_raw_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_write(PGconn *, const void *ptr, size_t len);
@@ -856,9 +856,9 @@ extern void pgtls_close(PGconn *conn);
extern ssize_t pgtls_read(PGconn *conn, void *ptr, size_t len);
/*
- * Is there unread data waiting in the SSL read buffer?
+ * Return the number of bytes available in the transport buffer.
*/
-extern bool pgtls_read_is_pending(PGconn *conn);
+extern ssize_t pgtls_bytes_pending(PGconn *conn);
/*
* Write data to a secure connection.
@@ -906,7 +906,7 @@ extern PostgresPollingStatusType pqsecure_open_gss(PGconn *conn);
*/
extern ssize_t pg_GSS_write(PGconn *conn, const void *ptr, size_t len);
extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len);
-extern bool pg_GSS_read_is_pending(PGconn *conn);
+extern ssize_t pg_GSS_bytes_pending(PGconn *conn);
#endif
/* === in fe-trace.c === */
--
2.47.3