]> granicus.if.org Git - postgresql/commitdiff
Use pselect(2) not select(2), if available, to wait in postmaster's loop.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 24 Apr 2017 18:03:14 +0000 (14:03 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 24 Apr 2017 18:03:14 +0000 (14:03 -0400)
Traditionally we've unblocked signals, called select(2), and then blocked
signals again.  The code expects that the select() will be cancelled with
EINTR if an interrupt occurs; but there's a race condition, which is that
an already-pending signal will be delivered as soon as we unblock, and then
when we reach select() there will be nothing preventing it from waiting.
This can result in a long delay before we perform any action that
ServerLoop was supposed to have taken in response to the signal.  As with
the somewhat-similar symptoms fixed by commit 893902085, the main practical
problem is slow launching of parallel workers.  The window for trouble is
usually pretty short, corresponding to one iteration of ServerLoop; but
it's not negligible.

To fix, use pselect(2) in place of select(2) where available, as that's
designed to solve exactly this problem.  Where not available, we continue
to use the old way, and are no worse off than before.

pselect(2) has been required by POSIX since about 2001, so most modern
platforms should have it.  A bigger portability issue is that some
implementations are said to be non-atomic, ie pselect() isn't really
any different from unblock/select/reblock.  Still, we're no worse off
than before on such a platform.

There is talk of rewriting the postmaster to use a WaitEventSet and
not do signal response work in signal handlers, at which point this
could be reverted, since we'd be using a self-pipe to solve the race
condition.  But that's not happening before v11 at the earliest.

Back-patch to 9.6.  The problem exists much further back, but the
worst symptom arises only in connection with parallel query, so it
does not seem worth taking any portability risks in older branches.

Discussion: https://postgr.es/m/9205.1492833041@sss.pgh.pa.us

configure
configure.in
src/backend/postmaster/postmaster.c
src/include/pg_config.h.in
src/include/pg_config.h.win32

index 7c86be3c3d0ff28464918850506f39a2ff301cc0..a1d6081b9659855ac3c5afa6d6b43ab6f90581df 100755 (executable)
--- a/configure
+++ b/configure
@@ -12892,7 +12892,7 @@ fi
 LIBS_including_readline="$LIBS"
 LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
 
-for ac_func in cbrt clock_gettime dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll pstat pthread_is_threaded_np readlink setproctitle setsid shm_open symlink sync_file_range towlower utime utimes wcstombs wcstombs_l
+for ac_func in cbrt clock_gettime dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll pselect pstat pthread_is_threaded_np readlink setproctitle setsid shm_open symlink sync_file_range towlower utime utimes wcstombs wcstombs_l
 do :
   as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
 ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
index e92494e12ddced332e7a738fabfce10d6b6b9f3b..83e53618e656fa67f9fad62768f8ceb94cebd137 100644 (file)
@@ -1429,7 +1429,7 @@ PGAC_FUNC_WCSTOMBS_L
 LIBS_including_readline="$LIBS"
 LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
 
-AC_CHECK_FUNCS([cbrt clock_gettime dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll pstat pthread_is_threaded_np readlink setproctitle setsid shm_open symlink sync_file_range towlower utime utimes wcstombs wcstombs_l])
+AC_CHECK_FUNCS([cbrt clock_gettime dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll pselect pstat pthread_is_threaded_np readlink setproctitle setsid shm_open symlink sync_file_range towlower utime utimes wcstombs wcstombs_l])
 
 AC_REPLACE_FUNCS(fseeko)
 case $host_os in
index 2bb43805331acca4429c42f14f54010dfcc0ac6d..7b9f444dc1b2442b4011e70c84d5b2b991a770f1 100644 (file)
@@ -614,9 +614,9 @@ PostmasterMain(int argc, char *argv[])
         * In the postmaster, we want to install non-ignored handlers *without*
         * SA_RESTART.  This is because they'll be blocked at all times except
         * when ServerLoop is waiting for something to happen, and during that
-        * window, we want signals to exit the select(2) wait so that ServerLoop
+        * window, we want signals to exit the pselect(2) wait so that ServerLoop
         * can respond if anything interesting happened.  On some platforms,
-        * signals marked SA_RESTART would not cause the select() wait to end.
+        * signals marked SA_RESTART would not cause the pselect() wait to end.
         * Child processes will generally want SA_RESTART, but we expect them to
         * set up their own handlers before unblocking signals.
         *
@@ -1670,6 +1670,8 @@ ServerLoop(void)
        for (;;)
        {
                fd_set          rmask;
+               fd_set     *rmask_p;
+               struct timeval timeout;
                int                     selres;
                time_t          now;
 
@@ -1679,37 +1681,64 @@ ServerLoop(void)
                 * We block all signals except while sleeping. That makes it safe for
                 * signal handlers, which again block all signals while executing, to
                 * do nontrivial work.
-                *
-                * If we are in PM_WAIT_DEAD_END state, then we don't want to accept
-                * any new connections, so we don't call select(), and just sleep.
                 */
-               memcpy((char *) &rmask, (char *) &readmask, sizeof(fd_set));
-
                if (pmState == PM_WAIT_DEAD_END)
                {
-                       PG_SETMASK(&UnBlockSig);
-
-                       pg_usleep(100000L); /* 100 msec seems reasonable */
-                       selres = 0;
-
-                       PG_SETMASK(&BlockSig);
+                       /*
+                        * If we are in PM_WAIT_DEAD_END state, then we don't want to
+                        * accept any new connections, so pass a null rmask.
+                        */
+                       rmask_p = NULL;
+                       timeout.tv_sec = 0;
+                       timeout.tv_usec = 100000;       /* 100 msec seems reasonable */
                }
                else
                {
-                       /* must set timeout each time; some OSes change it! */
-                       struct timeval timeout;
+                       /* Normal case: check sockets, and compute a suitable timeout */
+                       memcpy(&rmask, &readmask, sizeof(fd_set));
+                       rmask_p = &rmask;
 
                        /* Needs to run with blocked signals! */
                        DetermineSleepTime(&timeout);
+               }
 
+               /*
+                * We prefer to wait with pselect(2) if available, as using that,
+                * together with *not* using SA_RESTART for signals, guarantees that
+                * we will get kicked off the wait if a signal occurs.
+                *
+                * If we lack pselect(2), fake it with select(2).  This has a race
+                * condition: a signal that was already pending will be delivered
+                * before we reach the select(), and therefore the select() will wait,
+                * even though we might wish to do something in response.  Therefore,
+                * beware of putting any time-critical signal response logic into
+                * ServerLoop rather than into the signal handler itself.  It will run
+                * eventually, but maybe not till after a timeout delay.
+                *
+                * Some implementations of pselect() are reportedly not atomic, making
+                * the first alternative here functionally equivalent to the second.
+                * Not much we can do about that though.
+                */
+               {
+#ifdef HAVE_PSELECT
+                       /* pselect uses a randomly different timeout API, sigh */
+                       struct timespec ptimeout;
+
+                       ptimeout.tv_sec = timeout.tv_sec;
+                       ptimeout.tv_nsec = timeout.tv_usec * 1000;
+
+                       selres = pselect(nSockets, rmask_p, NULL, NULL,
+                                                        &ptimeout, &UnBlockSig);
+#else
                        PG_SETMASK(&UnBlockSig);
 
-                       selres = select(nSockets, &rmask, NULL, NULL, &timeout);
+                       selres = select(nSockets, rmask_p, NULL, NULL, &timeout);
 
                        PG_SETMASK(&BlockSig);
+#endif
                }
 
-               /* Now check the select() result */
+               /* Now check the select()/pselect() result */
                if (selres < 0)
                {
                        if (errno != EINTR && errno != EWOULDBLOCK)
index 7a05c7e5b856c078fa82ba77bcdebdad8977a967..75e511cfb6fc8d14afabd2db63360f50b2bfcf6c 100644 (file)
 /* Define to 1 if the assembler supports PPC's LWARX mutex hint bit. */
 #undef HAVE_PPC_LWARX_MUTEX_HINT
 
+/* Define to 1 if you have the `pselect' function. */
+#undef HAVE_PSELECT
+
 /* Define to 1 if you have the `pstat' function. */
 #undef HAVE_PSTAT
 
index 2a5fc1bab7619de68a245eacadc176c6b0f9f704..91d8f61edfe6bd877ed0569c446cb453bdf3f45b 100644 (file)
 /* Define to 1 if you have the <poll.h> header file. */
 /* #undef HAVE_POLL_H */
 
+/* Define to 1 if you have the `pselect' function. */
+/* #undef HAVE_PSELECT */
+
 /* Define to 1 if you have the `pstat' function. */
 /* #undef HAVE_PSTAT */