Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 105024a

Browse files
committed
Improve the granularity of PQsocketPoll's timeout parameter.
Commit f5e4ded exposed libpq's internal function PQsocketPoll without a lot of thought about whether that was an API we really wanted to chisel in stone. The main problem with it is the use of time_t to specify the timeout. While we do want an absolute time so that a loop around PQsocketPoll doesn't have problems with timeout slippage, time_t has only 1-second resolution. That's already problematic for libpq's own internal usage --- for example, pqConnectDBComplete has long had a kluge to treat "connect_timeout=1" as 2 seconds so that it doesn't accidentally round to nearly zero. And it's even less likely to be satisfactory for external callers. Hence, let's change this while we still can. The best idea seems to be to use an int64 count of microseconds since the epoch --- basically the same thing as the backend's TimestampTz, but let's use the standard Unix epoch (1970-01-01) since that's more likely for clients to be easy to calculate. Millisecond resolution would be plenty for foreseeable uses, but maybe the day will come that we're glad we used microseconds. Also, since time(2) isn't especially helpful for computing timeouts defined this way, introduce a new function PQgetCurrentTimeUSec to get the current time in this form. Remove the hack in pqConnectDBComplete, so that "connect_timeout=1" now means what you'd expect. We can also remove the "#include <time.h>" that f5e4ded added to libpq-fe.h, since there's no longer a need for time_t in that header. It seems better for v17 not to enlarge libpq-fe.h's include footprint from what it's historically been, anyway. I also failed to resist the temptation to do some wordsmithing on PQsocketPoll's documentation. Patch by me, per complaint from Dominique Devienne. Discussion: https://postgr.es/m/913559.1718055575@sss.pgh.pa.us
1 parent 6dfac24 commit 105024a

File tree

8 files changed

+153
-78
lines changed

8 files changed

+153
-78
lines changed

doc/src/sgml/libpq.sgml

+84-37
Original file line numberDiff line numberDiff line change
@@ -262,41 +262,6 @@ PGconn *PQsetdb(char *pghost,
262262
</listitem>
263263
</varlistentry>
264264

265-
<varlistentry id="libpq-PQsocketPoll">
266-
<term><function>PQsocketPoll</function><indexterm><primary>PQsocketPoll</primary></indexterm></term>
267-
<listitem>
268-
<para>
269-
<indexterm><primary>nonblocking connection</primary></indexterm>
270-
Poll a connection&apos;s underlying socket descriptor retrieved with <xref linkend="libpq-PQsocket"/>.
271-
<synopsis>
272-
int PQsocketPoll(int sock, int forRead, int forWrite, time_t end_time);
273-
</synopsis>
274-
</para>
275-
276-
<para>
277-
This function sets up polling of a file descriptor. The underlying function is either
278-
<function>poll(2)</function> or <function>select(2)</function>, depending on platform
279-
support. The primary use of this function is iterating through the connection sequence
280-
described in the documentation of <xref linkend="libpq-PQconnectStartParams"/>. If
281-
<parameter>forRead</parameter> is specified, the function waits for the socket to be ready
282-
for reading. If <parameter>forWrite</parameter> is specified, the function waits for the
283-
socket to be ready for write. See <literal>POLLIN</literal> and <literal>POLLOUT</literal>
284-
from <function>poll(2)</function>, or <parameter>readfds</parameter> and
285-
<parameter>writefds</parameter> from <function>select(2)</function> for more information. If
286-
<parameter>end_time</parameter> is not <literal>-1</literal>, it specifies the time at which
287-
this function should stop waiting for the condition to be met.
288-
</para>
289-
290-
<para>
291-
The function returns a value greater than <literal>0</literal> if the specified condition
292-
is met, <literal>0</literal> if a timeout occurred, or <literal>-1</literal> if an error
293-
occurred. The error can be retrieved by checking the <literal>errno(3)</literal> value. In
294-
the event <literal>forRead</literal> and <literal>forWrite</literal> are not set, the
295-
function immediately returns a timeout condition.
296-
</para>
297-
</listitem>
298-
</varlistentry>
299-
300265
<varlistentry id="libpq-PQconnectStartParams">
301266
<term><function>PQconnectStartParams</function><indexterm><primary>PQconnectStartParams</primary></indexterm></term>
302267
<term><function>PQconnectStart</function><indexterm><primary>PQconnectStart</primary></indexterm></term>
@@ -546,6 +511,70 @@ switch(PQstatus(conn))
546511
</listitem>
547512
</varlistentry>
548513

514+
<varlistentry id="libpq-PQsocketPoll">
515+
<term><function>PQsocketPoll</function><indexterm><primary>PQsocketPoll</primary></indexterm></term>
516+
<listitem>
517+
<para>
518+
<indexterm><primary>nonblocking connection</primary></indexterm>
519+
Poll a connection's underlying socket descriptor retrieved with
520+
<xref linkend="libpq-PQsocket"/>.
521+
The primary use of this function is iterating through the connection
522+
sequence described in the documentation of
523+
<xref linkend="libpq-PQconnectStartParams"/>.
524+
<synopsis>
525+
typedef pg_int64 pg_usec_time_t;
526+
527+
int PQsocketPoll(int sock, int forRead, int forWrite,
528+
pg_usec_time_t end_time);
529+
</synopsis>
530+
</para>
531+
532+
<para>
533+
This function performs polling of a file descriptor, optionally with
534+
a timeout.
535+
If <parameter>forRead</parameter> is nonzero, the
536+
function will terminate when the socket is ready for
537+
reading. If <parameter>forWrite</parameter> is nonzero,
538+
the function will terminate when the
539+
socket is ready for writing.
540+
</para>
541+
542+
<para>
543+
The timeout is specified by <parameter>end_time</parameter>, which
544+
is the time to stop waiting expressed as a number of microseconds since
545+
the Unix epoch (that is, <type>time_t</type> times 1 million).
546+
Timeout is infinite if <parameter>end_time</parameter>
547+
is <literal>-1</literal>. Timeout is immediate (no blocking) if
548+
end_time is <literal>0</literal> (or indeed, any time before now).
549+
Timeout values can be calculated conveniently by adding the desired
550+
number of microseconds to the result of
551+
<xref linkend="libpq-PQgetCurrentTimeUSec"/>.
552+
Note that the underlying system calls may have less than microsecond
553+
precision, so that the actual delay may be imprecise.
554+
</para>
555+
556+
<para>
557+
The function returns a value greater than <literal>0</literal> if the
558+
specified condition is met, <literal>0</literal> if a timeout occurred,
559+
or <literal>-1</literal> if an error occurred. The error can be
560+
retrieved by checking the <literal>errno(3)</literal> value. In the
561+
event both <parameter>forRead</parameter>
562+
and <parameter>forWrite</parameter> are zero, the function immediately
563+
returns a timeout indication.
564+
</para>
565+
566+
<para>
567+
<function>PQsocketPoll</function> is implemented using either
568+
<function>poll(2)</function> or <function>select(2)</function>,
569+
depending on platform. See <literal>POLLIN</literal>
570+
and <literal>POLLOUT</literal> from <function>poll(2)</function>,
571+
or <parameter>readfds</parameter> and
572+
<parameter>writefds</parameter> from <function>select(2)</function>,
573+
for more information.
574+
</para>
575+
</listitem>
576+
</varlistentry>
577+
549578
<varlistentry id="libpq-PQconndefaults">
550579
<term><function>PQconndefaults</function><indexterm><primary>PQconndefaults</primary></indexterm></term>
551580
<listitem>
@@ -1390,8 +1419,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
13901419
<para>
13911420
Maximum time to wait while connecting, in seconds (write as a decimal integer,
13921421
e.g., <literal>10</literal>). Zero, negative, or not specified means
1393-
wait indefinitely. The minimum allowed timeout is 2 seconds, therefore
1394-
a value of <literal>1</literal> is interpreted as <literal>2</literal>.
1422+
wait indefinitely.
13951423
This timeout applies separately to each host name or IP address.
13961424
For example, if you specify two hosts and <literal>connect_timeout</literal>
13971425
is 5, each host will time out if no connection is made within 5
@@ -8039,6 +8067,25 @@ int PQlibVersion(void);
80398067
</listitem>
80408068
</varlistentry>
80418069

8070+
<varlistentry id="libpq-PQgetCurrentTimeUSec">
8071+
<term><function>PQgetCurrentTimeUSec</function><indexterm><primary>PQgetCurrentTimeUSec</primary></indexterm></term>
8072+
8073+
<listitem>
8074+
<para>
8075+
Retrieves the current time, expressed as the number of microseconds
8076+
since the Unix epoch (that is, <type>time_t</type> times 1 million).
8077+
<synopsis>
8078+
pg_usec_time_t PQgetCurrentTimeUSec(void);
8079+
</synopsis>
8080+
</para>
8081+
8082+
<para>
8083+
This is primarily useful for calculating timeout values to use with
8084+
<xref linkend="libpq-PQsocketPoll"/>.
8085+
</para>
8086+
</listitem>
8087+
</varlistentry>
8088+
80428089
</variablelist>
80438090

80448091
</sect1>

src/bin/psql/command.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -3764,7 +3764,7 @@ wait_until_connected(PGconn *conn)
37643764
{
37653765
int rc;
37663766
int sock;
3767-
time_t end_time;
3767+
pg_usec_time_t end_time;
37683768

37693769
/*
37703770
* On every iteration of the connection sequence, let's check if the
@@ -3795,7 +3795,7 @@ wait_until_connected(PGconn *conn)
37953795
* solution happens to just be adding a timeout, so let's wait for 1
37963796
* second and check cancel_pressed again.
37973797
*/
3798-
end_time = time(NULL) + 1;
3798+
end_time = PQgetCurrentTimeUSec() + 1000000;
37993799
rc = PQsocketPoll(sock, forRead, !forRead, end_time);
38003800
if (rc == -1)
38013801
return;

src/interfaces/libpq/exports.txt

+1
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,4 @@ PQcancelReset 201
204204
PQcancelFinish 202
205205
PQsocketPoll 203
206206
PQsetChunkedRowsMode 204
207+
PQgetCurrentTimeUSec 205

src/interfaces/libpq/fe-connect.c

+5-18
Original file line numberDiff line numberDiff line change
@@ -2470,7 +2470,7 @@ int
24702470
pqConnectDBComplete(PGconn *conn)
24712471
{
24722472
PostgresPollingStatusType flag = PGRES_POLLING_WRITING;
2473-
time_t finish_time = ((time_t) -1);
2473+
pg_usec_time_t end_time = -1;
24742474
int timeout = 0;
24752475
int last_whichhost = -2; /* certainly different from whichhost */
24762476
int last_whichaddr = -2; /* certainly different from whichaddr */
@@ -2479,7 +2479,7 @@ pqConnectDBComplete(PGconn *conn)
24792479
return 0;
24802480

24812481
/*
2482-
* Set up a time limit, if connect_timeout isn't zero.
2482+
* Set up a time limit, if connect_timeout is greater than zero.
24832483
*/
24842484
if (conn->connect_timeout != NULL)
24852485
{
@@ -2490,19 +2490,6 @@ pqConnectDBComplete(PGconn *conn)
24902490
conn->status = CONNECTION_BAD;
24912491
return 0;
24922492
}
2493-
2494-
if (timeout > 0)
2495-
{
2496-
/*
2497-
* Rounding could cause connection to fail unexpectedly quickly;
2498-
* to prevent possibly waiting hardly-at-all, insist on at least
2499-
* two seconds.
2500-
*/
2501-
if (timeout < 2)
2502-
timeout = 2;
2503-
}
2504-
else /* negative means 0 */
2505-
timeout = 0;
25062493
}
25072494

25082495
for (;;)
@@ -2519,7 +2506,7 @@ pqConnectDBComplete(PGconn *conn)
25192506
(conn->whichhost != last_whichhost ||
25202507
conn->whichaddr != last_whichaddr))
25212508
{
2522-
finish_time = time(NULL) + timeout;
2509+
end_time = PQgetCurrentTimeUSec() + (pg_usec_time_t) timeout * 1000000;
25232510
last_whichhost = conn->whichhost;
25242511
last_whichaddr = conn->whichaddr;
25252512
}
@@ -2534,7 +2521,7 @@ pqConnectDBComplete(PGconn *conn)
25342521
return 1; /* success! */
25352522

25362523
case PGRES_POLLING_READING:
2537-
ret = pqWaitTimed(1, 0, conn, finish_time);
2524+
ret = pqWaitTimed(1, 0, conn, end_time);
25382525
if (ret == -1)
25392526
{
25402527
/* hard failure, eg select() problem, aborts everything */
@@ -2544,7 +2531,7 @@ pqConnectDBComplete(PGconn *conn)
25442531
break;
25452532

25462533
case PGRES_POLLING_WRITING:
2547-
ret = pqWaitTimed(0, 1, conn, finish_time);
2534+
ret = pqWaitTimed(0, 1, conn, end_time);
25482535
if (ret == -1)
25492536
{
25502537
/* hard failure, eg select() problem, aborts everything */

src/interfaces/libpq/fe-misc.c

+51-18
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
static int pqPutMsgBytes(const void *buf, size_t len, PGconn *conn);
5555
static int pqSendSome(PGconn *conn, int len);
5656
static int pqSocketCheck(PGconn *conn, int forRead, int forWrite,
57-
time_t end_time);
57+
pg_usec_time_t end_time);
5858

5959
/*
6060
* PQlibVersion: return the libpq version number
@@ -977,22 +977,25 @@ pqFlush(PGconn *conn)
977977
int
978978
pqWait(int forRead, int forWrite, PGconn *conn)
979979
{
980-
return pqWaitTimed(forRead, forWrite, conn, (time_t) -1);
980+
return pqWaitTimed(forRead, forWrite, conn, -1);
981981
}
982982

983983
/*
984-
* pqWaitTimed: wait, but not past finish_time.
985-
*
986-
* finish_time = ((time_t) -1) disables the wait limit.
984+
* pqWaitTimed: wait, but not past end_time.
987985
*
988986
* Returns -1 on failure, 0 if the socket is readable/writable, 1 if it timed out.
987+
*
988+
* The timeout is specified by end_time, which is the int64 number of
989+
* microseconds since the Unix epoch (that is, time_t times 1 million).
990+
* Timeout is infinite if end_time is -1. Timeout is immediate (no blocking)
991+
* if end_time is 0 (or indeed, any time before now).
989992
*/
990993
int
991-
pqWaitTimed(int forRead, int forWrite, PGconn *conn, time_t finish_time)
994+
pqWaitTimed(int forRead, int forWrite, PGconn *conn, pg_usec_time_t end_time)
992995
{
993996
int result;
994997

995-
result = pqSocketCheck(conn, forRead, forWrite, finish_time);
998+
result = pqSocketCheck(conn, forRead, forWrite, end_time);
996999

9971000
if (result < 0)
9981001
return -1; /* errorMessage is already set */
@@ -1013,7 +1016,7 @@ pqWaitTimed(int forRead, int forWrite, PGconn *conn, time_t finish_time)
10131016
int
10141017
pqReadReady(PGconn *conn)
10151018
{
1016-
return pqSocketCheck(conn, 1, 0, (time_t) 0);
1019+
return pqSocketCheck(conn, 1, 0, 0);
10171020
}
10181021

10191022
/*
@@ -1023,7 +1026,7 @@ pqReadReady(PGconn *conn)
10231026
int
10241027
pqWriteReady(PGconn *conn)
10251028
{
1026-
return pqSocketCheck(conn, 0, 1, (time_t) 0);
1029+
return pqSocketCheck(conn, 0, 1, 0);
10271030
}
10281031

10291032
/*
@@ -1035,7 +1038,7 @@ pqWriteReady(PGconn *conn)
10351038
* for read data directly.
10361039
*/
10371040
static int
1038-
pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time)
1041+
pqSocketCheck(PGconn *conn, int forRead, int forWrite, pg_usec_time_t end_time)
10391042
{
10401043
int result;
10411044

@@ -1079,11 +1082,13 @@ pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time)
10791082
* condition (without waiting). Return >0 if condition is met, 0
10801083
* if a timeout occurred, -1 if an error or interrupt occurred.
10811084
*
1085+
* The timeout is specified by end_time, which is the int64 number of
1086+
* microseconds since the Unix epoch (that is, time_t times 1 million).
10821087
* Timeout is infinite if end_time is -1. Timeout is immediate (no blocking)
10831088
* if end_time is 0 (or indeed, any time before now).
10841089
*/
10851090
int
1086-
PQsocketPoll(int sock, int forRead, int forWrite, time_t end_time)
1091+
PQsocketPoll(int sock, int forRead, int forWrite, pg_usec_time_t end_time)
10871092
{
10881093
/* We use poll(2) if available, otherwise select(2) */
10891094
#ifdef HAVE_POLL
@@ -1103,14 +1108,16 @@ PQsocketPoll(int sock, int forRead, int forWrite, time_t end_time)
11031108
input_fd.events |= POLLOUT;
11041109

11051110
/* Compute appropriate timeout interval */
1106-
if (end_time == ((time_t) -1))
1111+
if (end_time == -1)
11071112
timeout_ms = -1;
1113+
else if (end_time == 0)
1114+
timeout_ms = 0;
11081115
else
11091116
{
1110-
time_t now = time(NULL);
1117+
pg_usec_time_t now = PQgetCurrentTimeUSec();
11111118

11121119
if (end_time > now)
1113-
timeout_ms = (end_time - now) * 1000;
1120+
timeout_ms = (end_time - now) / 1000;
11141121
else
11151122
timeout_ms = 0;
11161123
}
@@ -1138,17 +1145,28 @@ PQsocketPoll(int sock, int forRead, int forWrite, time_t end_time)
11381145
FD_SET(sock, &except_mask);
11391146

11401147
/* Compute appropriate timeout interval */
1141-
if (end_time == ((time_t) -1))
1148+
if (end_time == -1)
11421149
ptr_timeout = NULL;
1150+
else if (end_time == 0)
1151+
{
1152+
timeout.tv_sec = 0;
1153+
timeout.tv_usec = 0;
1154+
ptr_timeout = &timeout;
1155+
}
11431156
else
11441157
{
1145-
time_t now = time(NULL);
1158+
pg_usec_time_t now = PQgetCurrentTimeUSec();
11461159

11471160
if (end_time > now)
1148-
timeout.tv_sec = end_time - now;
1161+
{
1162+
timeout.tv_sec = (end_time - now) / 1000000;
1163+
timeout.tv_usec = (end_time - now) % 1000000;
1164+
}
11491165
else
1166+
{
11501167
timeout.tv_sec = 0;
1151-
timeout.tv_usec = 0;
1168+
timeout.tv_usec = 0;
1169+
}
11521170
ptr_timeout = &timeout;
11531171
}
11541172

@@ -1157,6 +1175,21 @@ PQsocketPoll(int sock, int forRead, int forWrite, time_t end_time)
11571175
#endif /* HAVE_POLL */
11581176
}
11591177

1178+
/*
1179+
* PQgetCurrentTimeUSec: get current time with microsecond precision
1180+
*
1181+
* This provides a platform-independent way of producing a reference
1182+
* value for PQsocketPoll's timeout parameter.
1183+
*/
1184+
pg_usec_time_t
1185+
PQgetCurrentTimeUSec(void)
1186+
{
1187+
struct timeval tval;
1188+
1189+
gettimeofday(&tval, NULL);
1190+
return (pg_usec_time_t) tval.tv_sec * 1000000 + tval.tv_usec;
1191+
}
1192+
11601193

11611194
/*
11621195
* A couple of "miscellaneous" multibyte related functions. They used

0 commit comments

Comments
 (0)