@@ -1588,40 +1588,20 @@ set_timer(struct async_ctx *actx, long timeout)
15881588
15891589/*
15901590 * Returns 1 if the timeout in the multiplexer set has expired since the last
1591- * call to set_timer(), 0 if the timer is still running, or -1 (with an
1592- * actx_error() report) if the timer cannot be queried.
1591+ * call to set_timer(), 0 if the timer is either still running or disarmed, or
1592+ * -1 (with an actx_error() report) if the timer cannot be queried.
15931593 */
15941594static int
15951595timer_expired (struct async_ctx * actx )
15961596{
1597- #if defined(HAVE_SYS_EPOLL_H )
1598- struct itimerspec spec = {0 };
1599-
1600- if (timerfd_gettime (actx -> timerfd , & spec ) < 0 )
1601- {
1602- actx_error (actx , "getting timerfd value: %m" );
1603- return -1 ;
1604- }
1605-
1606- /*
1607- * This implementation assumes we're using single-shot timers. If you
1608- * change to using intervals, you'll need to reimplement this function
1609- * too, possibly with the read() or select() interfaces for timerfd.
1610- */
1611- Assert (spec .it_interval .tv_sec == 0
1612- && spec .it_interval .tv_nsec == 0 );
1613-
1614- /* If the remaining time to expiration is zero, we're done. */
1615- return (spec .it_value .tv_sec == 0
1616- && spec .it_value .tv_nsec == 0 );
1617- #elif defined(HAVE_SYS_EVENT_H )
1597+ #if defined(HAVE_SYS_EPOLL_H ) || defined(HAVE_SYS_EVENT_H )
16181598 int res ;
16191599
1620- /* Is the timer queue ready? */
1600+ /* Is the timer ready? */
16211601 res = PQsocketPoll (actx -> timerfd , 1 /* forRead */ , 0 , 0 );
16221602 if (res < 0 )
16231603 {
1624- actx_error (actx , "checking kqueue for timeout : %m" );
1604+ actx_error (actx , "checking timer expiration : %m" );
16251605 return -1 ;
16261606 }
16271607
@@ -1653,6 +1633,36 @@ register_timer(CURLM *curlm, long timeout, void *ctx)
16531633 return 0 ;
16541634}
16551635
1636+ /*
1637+ * Removes any expired-timer event from the multiplexer. If was_expired is not
1638+ * NULL, it will contain whether or not the timer was expired at time of call.
1639+ */
1640+ static bool
1641+ drain_timer_events (struct async_ctx * actx , bool * was_expired )
1642+ {
1643+ int res ;
1644+
1645+ res = timer_expired (actx );
1646+ if (res < 0 )
1647+ return false;
1648+
1649+ if (res > 0 )
1650+ {
1651+ /*
1652+ * Timer is expired. We could drain the event manually from the
1653+ * timerfd, but it's easier to simply disable it; that keeps the
1654+ * platform-specific code in set_timer().
1655+ */
1656+ if (!set_timer (actx , -1 ))
1657+ return false;
1658+ }
1659+
1660+ if (was_expired )
1661+ * was_expired = (res > 0 );
1662+
1663+ return true;
1664+ }
1665+
16561666/*
16571667 * Prints Curl request debugging information to stderr.
16581668 *
@@ -2856,6 +2866,22 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
28562866 {
28572867 PostgresPollingStatusType status ;
28582868
2869+ /*
2870+ * Clear any expired timeout before calling back into
2871+ * Curl. Curl is not guaranteed to do this for us, because
2872+ * its API expects us to use single-shot (i.e.
2873+ * edge-triggered) timeouts, and ours are level-triggered
2874+ * via the mux.
2875+ *
2876+ * This can't be combined with the drain_socket_events()
2877+ * call below: we might accidentally clear a short timeout
2878+ * that was both set and expired during the call to
2879+ * drive_request().
2880+ */
2881+ if (!drain_timer_events (actx , NULL ))
2882+ goto error_return ;
2883+
2884+ /* Move the request forward. */
28592885 status = drive_request (actx );
28602886
28612887 if (status == PGRES_POLLING_FAILED )
@@ -2879,24 +2905,26 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
28792905 }
28802906
28812907 case OAUTH_STEP_WAIT_INTERVAL :
2882-
2883- /*
2884- * The client application is supposed to wait until our timer
2885- * expires before calling PQconnectPoll() again, but that
2886- * might not happen. To avoid sending a token request early,
2887- * check the timer before continuing.
2888- */
2889- if (!timer_expired (actx ))
28902908 {
2891- set_conn_altsock (conn , actx -> timerfd );
2892- return PGRES_POLLING_READING ;
2893- }
2909+ bool expired ;
28942910
2895- /* Disable the expired timer. */
2896- if (!set_timer (actx , -1 ))
2897- goto error_return ;
2911+ /*
2912+ * The client application is supposed to wait until our
2913+ * timer expires before calling PQconnectPoll() again, but
2914+ * that might not happen. To avoid sending a token request
2915+ * early, check the timer before continuing.
2916+ */
2917+ if (!drain_timer_events (actx , & expired ))
2918+ goto error_return ;
28982919
2899- break ;
2920+ if (!expired )
2921+ {
2922+ set_conn_altsock (conn , actx -> timerfd );
2923+ return PGRES_POLLING_READING ;
2924+ }
2925+
2926+ break ;
2927+ }
29002928 }
29012929
29022930 /*
0 commit comments