Fix corner-case bug in WaitEventSetWaitBlock on Windows.
authorRobert Haas <rhaas@postgresql.org>
Wed, 21 Dec 2016 16:01:48 +0000 (11:01 -0500)
committerRobert Haas <rhaas@postgresql.org>
Wed, 21 Dec 2016 16:11:36 +0000 (11:11 -0500)
If we do not reset the FD_READ event, WaitForMultipleObjects won't
return it again again unless we've meanwhile read from the socket,
which is generally true but not guaranteed.  WaitEventSetWaitBlock
itself may fail to return the event to the caller if the latch is
also set, and even if we changed that, the caller isn't obliged to
handle all returned events at once.  On non-Windows systems, the
socket-read event is purely level-triggered, so this issue does
not exist.  To fix, make Windows reset the event when needed.

This bug was introduced by 98a64d0bd713cb89e61bef6432befc4b7b5da59e,
and causes hangs when trying to use the pldebugger extension.

Patch by Amit Kapial.  Reported and tested by Ashutosh Sharma, who
also provided some analysis.  Further analysis by Michael Paquier.

src/backend/storage/ipc/latch.c
src/include/storage/latch.h

index 9def8a12d3ddfd2e5b0af7cc7a473e29edd9d04e..16d3cad25d29f4fec15429b7bb49338f00ddc4e1 100644 (file)
@@ -640,6 +640,9 @@ AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
    event->fd = fd;
    event->events = events;
    event->user_data = user_data;
+#ifdef WIN32
+   event->reset = false;
+#endif
 
    if (events == WL_LATCH_SET)
    {
@@ -1381,6 +1384,18 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
    DWORD       rc;
    WaitEvent  *cur_event;
 
+   /* Reset any wait events that need it */
+   for (cur_event = set->events;
+        cur_event < (set->events + set->nevents);
+        cur_event++)
+   {
+       if (cur_event->reset)
+       {
+           WaitEventAdjustWin32(set, cur_event);
+           cur_event->reset = false;
+       }
+   }
+
    /*
     * Sleep.
     *
@@ -1464,6 +1479,18 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
        {
            /* data available in socket */
            occurred_events->events |= WL_SOCKET_READABLE;
+
+           /*------
+            * WaitForMultipleObjects doesn't guarantee that a read event will
+            * be returned if the latch is set at the same time.  Even if it
+            * did, the caller might drop that event expecting it to reoccur
+            * on next call.  So, we must force the event to be reset if this
+            * WaitEventSet is used again in order to avoid an indefinite
+            * hang.  Refer https://msdn.microsoft.com/en-us/library/windows/desktop/ms741576(v=vs.85).aspx
+            * for the behavior of socket events.
+            *------
+            */
+           cur_event->reset = true;
        }
        if ((cur_event->events & WL_SOCKET_WRITEABLE) &&
            (resEvents.lNetworkEvents & FD_WRITE))
index 5179ecc0dbda376801eacd565150781254ed1360..bce7d114af170b1e16bcd88b7fcc318d57755aab 100644 (file)
@@ -133,6 +133,9 @@ typedef struct WaitEvent
    uint32      events;         /* triggered events */
    pgsocket    fd;             /* socket fd associated with event */
    void       *user_data;      /* pointer provided in AddWaitEventToSet */
+#ifdef WIN32
+   bool        reset;          /* Is reset of the event required? */
+#endif
 } WaitEvent;
 
 /* forward declaration to avoid exposing latch.c implementation details */