Skip to content

Commit ff5b082

Browse files
jchampiomacdice
andcommitted
oauth: Remove stale events from the kqueue multiplexer
If a socket is added to the kqueue, becomes readable/writable, and subsequently becomes non-readable/writable again, the kqueue itself will remain readable until either the socket registration is removed, or the stale event is cleared via a call to kevent(). In many simple cases, Curl itself will remove the socket registration quickly, but in real-world usage, this is not guaranteed to happen. The kqueue can then remain stuck in a permanently readable state until the request ends, which results in pointless wakeups for the client and wasted CPU time. Implement comb_multiplexer() to call kevent() and unstick any stale events that would cause unnecessary callbacks. This is called right after drive_request(), before we return control to the client to wait. Suggested-by: Thomas Munro <thomas.munro@gmail.com> Co-authored-by: Thomas Munro <thomas.munro@gmail.com> Reviewed-by: Thomas Munro <thomas.munro@gmail.com> Backpatch-through: 18 Discussion: https://postgr.es/m/CAOYmi+nDZxJHaWj9_jRSyf8uMToCADAmOfJEggsKW-kY7aUwHA@mail.gmail.com
1 parent b5cd746 commit ff5b082

File tree

1 file changed

+61
-6
lines changed

1 file changed

+61
-6
lines changed

src/interfaces/libpq-oauth/oauth-curl.c

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,53 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
13761376
#endif
13771377
}
13781378

1379+
/*
1380+
* If there is no work to do on any of the descriptors in the multiplexer, then
1381+
* this function must ensure that the multiplexer is not readable.
1382+
*
1383+
* Unlike epoll descriptors, kqueue descriptors only transition from readable to
1384+
* unreadable when kevent() is called and finds nothing, after removing
1385+
* level-triggered conditions that have gone away. We therefore need a dummy
1386+
* kevent() call after operations might have been performed on the monitored
1387+
* sockets or timer_fd. Any event returned is ignored here, but it also remains
1388+
* queued (being level-triggered) and leaves the descriptor readable. This is a
1389+
* no-op for epoll descriptors.
1390+
*/
1391+
static bool
1392+
comb_multiplexer(struct async_ctx *actx)
1393+
{
1394+
#if defined(HAVE_SYS_EPOLL_H)
1395+
/* The epoll implementation doesn't hold onto stale events. */
1396+
return true;
1397+
#elif defined(HAVE_SYS_EVENT_H)
1398+
struct timespec timeout = {0};
1399+
struct kevent ev;
1400+
1401+
/*
1402+
* Try to read a single pending event. We can actually ignore the result:
1403+
* either we found an event to process, in which case the multiplexer is
1404+
* correctly readable for that event at minimum, and it doesn't matter if
1405+
* there are any stale events; or we didn't find any, in which case the
1406+
* kernel will have discarded any stale events as it traveled to the end
1407+
* of the queue.
1408+
*
1409+
* Note that this depends on our registrations being level-triggered --
1410+
* even the timer, so we use a chained kqueue for that instead of an
1411+
* EVFILT_TIMER on the top-level mux. If we used edge-triggered events,
1412+
* this call would improperly discard them.
1413+
*/
1414+
if (kevent(actx->mux, NULL, 0, &ev, 1, &timeout) < 0)
1415+
{
1416+
actx_error(actx, "could not comb kqueue: %m");
1417+
return false;
1418+
}
1419+
1420+
return true;
1421+
#else
1422+
#error comb_multiplexer is not implemented on this platform
1423+
#endif
1424+
}
1425+
13791426
/*
13801427
* Enables or disables the timer in the multiplexer set. The timeout value is
13811428
* in milliseconds (negative values disable the timer).
@@ -2755,13 +2802,21 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
27552802

27562803
if (status == PGRES_POLLING_FAILED)
27572804
goto error_return;
2758-
else if (status != PGRES_POLLING_OK)
2759-
{
2760-
/* not done yet */
2761-
return status;
2762-
}
2805+
else if (status == PGRES_POLLING_OK)
2806+
break; /* done! */
27632807

2764-
break;
2808+
/*
2809+
* This request is still running.
2810+
*
2811+
* Make sure that stale events don't cause us to come back
2812+
* early. (Currently, this can occur only with kqueue.) If
2813+
* this is forgotten, the multiplexer can get stuck in a
2814+
* signaled state and we'll burn CPU cycles pointlessly.
2815+
*/
2816+
if (!comb_multiplexer(actx))
2817+
goto error_return;
2818+
2819+
return status;
27652820
}
27662821

27672822
case OAUTH_STEP_WAIT_INTERVAL:

0 commit comments

Comments
 (0)