In [1] Dominique Devienne complained that PQsocketPoll would be
far more useful to him if it had better-than-one-second timeout
resolution. I initially pushed back on that on the grounds that
post-beta1 is a bit late to be redefining public APIs. Which it is,
but if we don't fix it now then we'll be stuck supporting that API
indefinitely. And it's not like one-second resolution is great
for our internal usage either --- for example, I see that psql
is now doing
end_time = time(NULL) + 1;
rc = PQsocketPoll(sock, forRead, !forRead, end_time);
which claims to be waiting one second, but actually it's waiting
somewhere between 0 and 1 second. So I thought I'd look into
whether we can still change it without too much pain, and I think
we can.
The $64 question is how to represent the end_time if not as time_t.
The only alternative POSIX offers AFAIK is gettimeofday's "struct
timeval", which is painful to compute with and I don't think it's
native on Windows. What I suggest is that we use int64 microseconds
since the epoch, which is the same idea as the backend's TimestampTz
except I think we'd better use the Unix epoch not 2000-01-01.
Then converting code is just a matter of changing variable types
and adding some zeroes to constants.
The next question is how to spell "int64" in libpq-fe.h. As a
client-exposed header, the portability constraints on it are pretty
stringent, so even in 2024 I'm loath to make it depend on <stdint.h>;
and certainly depending on our internal int64 typedef won't do.
What I did in the attached is to write "long long int", which is
required to be at least 64 bits by C99. Other opinions are possible
of course.
Lastly, we need a way to get current time in this form. My first
draft of the attached patch had the callers calling gettimeofday
and doing arithmetic from that, but it seems a lot better to provide
a function that just parallels time(2).
BTW, I think this removes the need for libpq-fe.h to #include <time.h>,
but I didn't remove that because it seems likely that some callers are
indirectly relying on it to be present. Removing it wouldn't gain
very much anyway.
Thoughts?
regards, tom lane
[1]
https://www.postgresql.org/message-id/CAFCRh-8hf%3D7V8UoF63aLxSkeFmXX8-1O5tRxHL61Pngb7V9rcw%40mail.gmail.com
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 80179f9978..8c0ea444ad 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -268,30 +268,50 @@ PGconn *PQsetdb(char *pghost,
<para>
<indexterm><primary>nonblocking connection</primary></indexterm>
Poll a connection's underlying socket descriptor retrieved with <xref linkend="libpq-PQsocket"/>.
+ The primary use of this function is iterating through the connection
+ sequence described in the documentation of
+ <xref linkend="libpq-PQconnectStartParams"/>.
<synopsis>
-int PQsocketPoll(int sock, int forRead, int forWrite, time_t end_time);
+int PQsocketPoll(int sock, int forRead, int forWrite,
+ long long int end_time_us);
</synopsis>
</para>
<para>
- This function sets up polling of a file descriptor. The underlying function is either
+ This function performs polling of a file descriptor, optionally with
+ a timeout. The underlying function is either
<function>poll(2)</function> or <function>select(2)</function>, depending on platform
- support. The primary use of this function is iterating through the connection sequence
- described in the documentation of <xref linkend="libpq-PQconnectStartParams"/>. If
- <parameter>forRead</parameter> is specified, the function waits for the socket to be ready
- for reading. If <parameter>forWrite</parameter> is specified, the function waits for the
- socket to be ready for write. See <literal>POLLIN</literal> and <literal>POLLOUT</literal>
+ support.
+ If <parameter>forRead</parameter> is nonzero, the
+ function will terminate when the socket is ready for
+ reading. If <parameter>forWrite</parameter> is nonzero,
+ the function will terminate when the
+ socket is ready for writing.
+ See <literal>POLLIN</literal> and <literal>POLLOUT</literal>
from <function>poll(2)</function>, or <parameter>readfds</parameter> and
- <parameter>writefds</parameter> from <function>select(2)</function> for more information. If
- <parameter>end_time</parameter> is not <literal>-1</literal>, it specifies the time at which
- this function should stop waiting for the condition to be met.
+ <parameter>writefds</parameter> from <function>select(2)</function> for more information.
+ </para>
+
+ <para>
+ The timeout is specified by <parameter>end_time_us</parameter>, which
+ is the time to stop waiting expressed as a number of microseconds since
+ the Unix epoch (that is, <type>time_t</type> times 1 million).
+ Timeout is infinite if <parameter>end_time_us</parameter>
+ is <literal>-1</literal>. Timeout is immediate (no blocking) if
+ end_time is <literal>0</literal> (or indeed, any time before now).
+ Timeout values can be calculated conveniently by adding the desired
+ number of microseconds to the result of
+ <xref linkend="libpq-PQgetCurrentTimeUSec"/>.
+ Note that the underlying system calls may have less than microsecond
+ precision, so that the actual delay may be imprecise.
</para>
<para>
The function returns a value greater than <literal>0</literal> if the specified condition
is met, <literal>0</literal> if a timeout occurred, or <literal>-1</literal> if an error
occurred. The error can be retrieved by checking the <literal>errno(3)</literal> value. In
- the event <literal>forRead</literal> and <literal>forWrite</literal> are not set, the
+ the event both <parameter>forRead</parameter>
+ and <parameter>forWrite</parameter> are zero, the
function immediately returns a timeout condition.
</para>
</listitem>
@@ -8039,6 +8059,25 @@ int PQlibVersion(void);
</listitem>
</varlistentry>
+ <varlistentry id="libpq-PQgetCurrentTimeUSec">
+ <term><function>PQgetCurrentTimeUSec</function><indexterm><primary>PQgetCurrentTimeUSec</primary></indexterm></term>
+
+ <listitem>
+ <para>
+ Retrieves the current time, expressed as the number of microseconds
+ since the Unix epoch (that is, <type>time_t</type> times 1 million).
+<synopsis>
+long long int PQgetCurrentTimeUSec(void);
+</synopsis>
+ </para>
+
+ <para>
+ This is especially useful for calculating timeout values to use with
+ <xref linkend="libpq-PQsocketPoll"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</sect1>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index fae5940b54..f717de7a4f 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -3764,7 +3764,7 @@ wait_until_connected(PGconn *conn)
{
int rc;
int sock;
- time_t end_time;
+ long long int end_time_us;
/*
* On every iteration of the connection sequence, let's check if the
@@ -3795,8 +3795,8 @@ wait_until_connected(PGconn *conn)
* solution happens to just be adding a timeout, so let's wait for 1
* second and check cancel_pressed again.
*/
- end_time = time(NULL) + 1;
- rc = PQsocketPoll(sock, forRead, !forRead, end_time);
+ end_time_us = PQgetCurrentTimeUSec() + 1000000;
+ rc = PQsocketPoll(sock, forRead, !forRead, end_time_us);
if (rc == -1)
return;
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 8ee0811510..5d8213e0b5 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -204,3 +204,4 @@ PQcancelReset 201
PQcancelFinish 202
PQsocketPoll 203
PQsetChunkedRowsMode 204
+PQgetCurrentTimeUSec 205
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 548ad118fb..774a8074fb 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2470,7 +2470,7 @@ int
pqConnectDBComplete(PGconn *conn)
{
PostgresPollingStatusType flag = PGRES_POLLING_WRITING;
- time_t finish_time = ((time_t) -1);
+ long long int end_time_us = -1;
int timeout = 0;
int last_whichhost = -2; /* certainly different from whichhost */
int last_whichaddr = -2; /* certainly different from whichaddr */
@@ -2519,7 +2519,7 @@ pqConnectDBComplete(PGconn *conn)
(conn->whichhost != last_whichhost ||
conn->whichaddr != last_whichaddr))
{
- finish_time = time(NULL) + timeout;
+ end_time_us = PQgetCurrentTimeUSec() + 1000000LL * timeout;
last_whichhost = conn->whichhost;
last_whichaddr = conn->whichaddr;
}
@@ -2534,7 +2534,7 @@ pqConnectDBComplete(PGconn *conn)
return 1; /* success! */
case PGRES_POLLING_READING:
- ret = pqWaitTimed(1, 0, conn, finish_time);
+ ret = pqWaitTimed(1, 0, conn, end_time_us);
if (ret == -1)
{
/* hard failure, eg select() problem, aborts everything */
@@ -2544,7 +2544,7 @@ pqConnectDBComplete(PGconn *conn)
break;
case PGRES_POLLING_WRITING:
- ret = pqWaitTimed(0, 1, conn, finish_time);
+ ret = pqWaitTimed(0, 1, conn, end_time_us);
if (ret == -1)
{
/* hard failure, eg select() problem, aborts everything */
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index f562cd8d34..70a56bbb7c 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -54,7 +54,7 @@
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,
- time_t end_time);
+ long long int end_time_us);
/*
* PQlibVersion: return the libpq version number
@@ -977,22 +977,25 @@ pqFlush(PGconn *conn)
int
pqWait(int forRead, int forWrite, PGconn *conn)
{
- return pqWaitTimed(forRead, forWrite, conn, (time_t) -1);
+ return pqWaitTimed(forRead, forWrite, conn, -1);
}
/*
- * pqWaitTimed: wait, but not past finish_time.
- *
- * finish_time = ((time_t) -1) disables the wait limit.
+ * pqWaitTimed: wait, but not past end_time_us.
*
* Returns -1 on failure, 0 if the socket is readable/writable, 1 if it timed out.
+ *
+ * The timeout is specified by end_time_us, which is the int64 number of
+ * microseconds since the Unix epoch (that is, time_t times 1 million).
+ * Timeout is infinite if end_time is -1. Timeout is immediate (no blocking)
+ * if end_time is 0 (or indeed, any time before now).
*/
int
-pqWaitTimed(int forRead, int forWrite, PGconn *conn, time_t finish_time)
+pqWaitTimed(int forRead, int forWrite, PGconn *conn, long long int end_time_us)
{
int result;
- result = pqSocketCheck(conn, forRead, forWrite, finish_time);
+ result = pqSocketCheck(conn, forRead, forWrite, end_time_us);
if (result < 0)
return -1; /* errorMessage is already set */
@@ -1013,7 +1016,7 @@ pqWaitTimed(int forRead, int forWrite, PGconn *conn, time_t finish_time)
int
pqReadReady(PGconn *conn)
{
- return pqSocketCheck(conn, 1, 0, (time_t) 0);
+ return pqSocketCheck(conn, 1, 0, 0);
}
/*
@@ -1023,7 +1026,7 @@ pqReadReady(PGconn *conn)
int
pqWriteReady(PGconn *conn)
{
- return pqSocketCheck(conn, 0, 1, (time_t) 0);
+ return pqSocketCheck(conn, 0, 1, 0);
}
/*
@@ -1035,7 +1038,7 @@ pqWriteReady(PGconn *conn)
* for read data directly.
*/
static int
-pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time)
+pqSocketCheck(PGconn *conn, int forRead, int forWrite, long long int end_time_us)
{
int result;
@@ -1058,7 +1061,7 @@ pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time)
/* We will retry as long as we get EINTR */
do
- result = PQsocketPoll(conn->sock, forRead, forWrite, end_time);
+ result = PQsocketPoll(conn->sock, forRead, forWrite, end_time_us);
while (result < 0 && SOCK_ERRNO == EINTR);
if (result < 0)
@@ -1079,11 +1082,13 @@ pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time)
* condition (without waiting). Return >0 if condition is met, 0
* if a timeout occurred, -1 if an error or interrupt occurred.
*
+ * The timeout is specified by end_time_us, which is the int64 number of
+ * microseconds since the Unix epoch (that is, time_t times 1 million).
* Timeout is infinite if end_time is -1. Timeout is immediate (no blocking)
* if end_time is 0 (or indeed, any time before now).
*/
int
-PQsocketPoll(int sock, int forRead, int forWrite, time_t end_time)
+PQsocketPoll(int sock, int forRead, int forWrite, long long int end_time_us)
{
/* We use poll(2) if available, otherwise select(2) */
#ifdef HAVE_POLL
@@ -1103,14 +1108,14 @@ PQsocketPoll(int sock, int forRead, int forWrite, time_t end_time)
input_fd.events |= POLLOUT;
/* Compute appropriate timeout interval */
- if (end_time == ((time_t) -1))
+ if (end_time_us == -1)
timeout_ms = -1;
else
{
- time_t now = time(NULL);
+ long long int now = PQgetCurrentTimeUSec();
- if (end_time > now)
- timeout_ms = (end_time - now) * 1000;
+ if (end_time_us > now)
+ timeout_ms = (end_time_us - now) / 1000;
else
timeout_ms = 0;
}
@@ -1138,17 +1143,22 @@ PQsocketPoll(int sock, int forRead, int forWrite, time_t end_time)
FD_SET(sock, &except_mask);
/* Compute appropriate timeout interval */
- if (end_time == ((time_t) -1))
+ if (end_time_us == -1)
ptr_timeout = NULL;
else
{
- time_t now = time(NULL);
+ long long int now = PQgetCurrentTimeUSec();
- if (end_time > now)
- timeout.tv_sec = end_time - now;
+ if (end_time_us > now)
+ {
+ timeout.tv_sec = (end_time_us - now) / 1000000;
+ timeout.tv_usec = (end_time_us - now) % 1000000;
+ }
else
+ {
timeout.tv_sec = 0;
- timeout.tv_usec = 0;
+ timeout.tv_usec = 0;
+ }
ptr_timeout = &timeout;
}
@@ -1157,6 +1167,22 @@ PQsocketPoll(int sock, int forRead, int forWrite, time_t end_time)
#endif /* HAVE_POLL */
}
+/*
+ * PQgetCurrentTimeUSec: get current time with microsecond precision
+ *
+ * This provides a platform-independent way of producing a reference
+ * value for PQsocketPoll's timeout parameter. We assume that "long long"
+ * is at least 64 bits wide on all platforms, as required by C99.
+ */
+long long int
+PQgetCurrentTimeUSec(void)
+{
+ struct timeval tval;
+
+ gettimeofday(&tval, NULL);
+ return (long long int) tval.tv_sec * 1000000 + tval.tv_usec;
+}
+
/*
* A couple of "miscellaneous" multibyte related functions. They used
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 73f6e65ae5..0d0359175a 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -673,7 +673,11 @@ extern int lo_export(PGconn *conn, Oid lobjId, const char *filename);
extern int PQlibVersion(void);
/* Poll a socket for reading and/or writing with an optional timeout */
-extern int PQsocketPoll(int sock, int forRead, int forWrite, time_t end_time);
+extern int PQsocketPoll(int sock, int forRead, int forWrite,
+ long long int end_time_us);
+
+/* Get current time in the form PQsocketPoll wants */
+extern long long int PQgetCurrentTimeUSec(void);
/* Determine length of multibyte encoded char at *s */
extern int PQmblen(const char *s, int encoding);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 3886204c70..7062998387 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -755,7 +755,7 @@ extern int pqReadData(PGconn *conn);
extern int pqFlush(PGconn *conn);
extern int pqWait(int forRead, int forWrite, PGconn *conn);
extern int pqWaitTimed(int forRead, int forWrite, PGconn *conn,
- time_t finish_time);
+ long long int end_time_us);
extern int pqReadReady(PGconn *conn);
extern int pqWriteReady(PGconn *conn);