Subject: bug#32452: 26.1; gnutls_try_handshake maxes out cpu retrying when server is a bit busy

From: Lars Ingebrigtsen <[email-masked]>
Date: Wed, 02 Mar 2022 02:43:40 +0100

Lars Ingebrigtsen <[email-masked]> writes:

> diff --git a/admin/merge-gnulib b/admin/merge-gnulib
> index fec469c017..ea3d23686f 100755

That wasn't the complete diff.  Here it is:

diff --git a/admin/merge-gnulib b/admin/merge-gnulib
index fec469c017..ea3d23686f 100755
--- a/admin/merge-gnulib
+++ b/admin/merge-gnulib
@@ -40,7 +40,7 @@ GNULIB_MODULES=
   getloadavg getopt-gnu getrandom gettime gettimeofday gitlog-to-changelog
   ieee754-h ignore-value intprops largefile libgmp lstat
   manywarnings memmem-simple mempcpy memrchr minmax mkostemp mktime
-  nproc nstrftime
+  nanosleep nproc nstrftime
   pathmax pipe2 pselect pthread_sigmask
   qcopy-acl readlink readlinkat regex
   sig2str sigdescr_np socklen stat-time std-gnu11 stdalign stddef stdio
diff --git a/lib/gnulib.mk.in b/lib/gnulib.mk.in
index 3a9f5b9818..b79eebc804 100644
--- a/lib/gnulib.mk.in
+++ b/lib/gnulib.mk.in
@@ -129,6 +129,7 @@
 #  minmax \
 #  mkostemp \
 #  mktime \
+#  nanosleep \
 #  nproc \
 #  nstrftime \
 #  pathmax \
@@ -2497,6 +2498,16 @@ EXTRA_libgnu_a_SOURCES += mktime.c
 endif
 ## end   gnulib module mktime-internal
 
+## begin gnulib module nanosleep
+ifeq (,$(OMIT_GNULIB_MODULE_nanosleep))
+
+ifneq (,$(GL_COND_OBJ_NANOSLEEP_CONDITION))
+libgnu_a_SOURCES += nanosleep.c
+endif
+
+endif
+## end   gnulib module nanosleep
+
 ## begin gnulib module nproc
 ifeq (,$(OMIT_GNULIB_MODULE_nproc))
 
diff --git a/lib/nanosleep.c b/lib/nanosleep.c
new file mode 100644
index 0000000000..446794edc0
--- /dev/null
+++ b/lib/nanosleep.c
@@ -0,0 +1,195 @@
+/* Provide a replacement for the POSIX nanosleep function.
+
+   Copyright (C) 1999-2000, 2002, 2004-2022 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* written by Jim Meyering
+   and Bruno Haible for the native Windows part */
+
+#include <config.h>
+
+#include <time.h>
+
+#include "intprops.h"
+#include "verify.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/select.h>
+#include <signal.h>
+
+#include <errno.h>
+
+#include <unistd.h>
+
+
+enum { BILLION = 1000 * 1000 * 1000 };
+
+#if HAVE_BUG_BIG_NANOSLEEP
+
+int
+nanosleep (const struct timespec *requested_delay,
+           struct timespec *remaining_delay)
+# undef nanosleep
+{
+  /* nanosleep mishandles large sleeps due to internal overflow problems.
+     The worst known case of this is Linux 2.6.9 with glibc 2.3.4, which
+     can't sleep more than 24.85 days (2^31 milliseconds).  Similarly,
+     cygwin 1.5.x, which can't sleep more than 49.7 days (2^32 milliseconds).
+     Solve this by breaking the sleep up into smaller chunks.  */
+
+  if (requested_delay->tv_nsec < 0 || BILLION <= requested_delay->tv_nsec)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  {
+    /* Verify that time_t is large enough.  */
+    verify (TYPE_MAXIMUM (time_t) / 24 / 24 / 60 / 60);
+    const time_t limit = 24 * 24 * 60 * 60;
+    time_t seconds = requested_delay->tv_sec;
+    struct timespec intermediate;
+    intermediate.tv_nsec = requested_delay->tv_nsec;
+
+    while (limit < seconds)
+      {
+        int result;
+        intermediate.tv_sec = limit;
+        result = nanosleep (&intermediate, remaining_delay);
+        seconds -= limit;
+        if (result)
+          {
+            if (remaining_delay)
+              remaining_delay->tv_sec += seconds;
+            return result;
+          }
+        intermediate.tv_nsec = 0;
+      }
+    intermediate.tv_sec = seconds;
+    return nanosleep (&intermediate, remaining_delay);
+  }
+}
+
+#elif defined _WIN32 && ! defined __CYGWIN__
+/* Native Windows platforms.  */
+
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+
+/* The Windows API function Sleep() has a resolution of about 15 ms and takes
+   at least 5 ms to execute.  We use this function for longer time periods.
+   Additionally, we use busy-looping over short time periods, to get a
+   resolution of about 0.01 ms.  In order to measure such short timespans,
+   we use the QueryPerformanceCounter() function.  */
+
+int
+nanosleep (const struct timespec *requested_delay,
+           struct timespec *remaining_delay)
+{
+  static bool initialized;
+  /* Number of performance counter increments per nanosecond,
+     or zero if it could not be determined.  */
+  static double ticks_per_nanosecond;
+
+  if (requested_delay->tv_nsec < 0 || BILLION <= requested_delay->tv_nsec)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  /* For requested delays of one second or more, 15ms resolution is
+     sufficient.  */
+  if (requested_delay->tv_sec == 0)
+    {
+      if (!initialized)
+        {
+          /* Initialize ticks_per_nanosecond.  */
+          LARGE_INTEGER ticks_per_second;
+
+          if (QueryPerformanceFrequency (&ticks_per_second))
+            ticks_per_nanosecond =
+              (double) ticks_per_second.QuadPart / 1000000000.0;
+
+          initialized = true;
+        }
+      if (ticks_per_nanosecond)
+        {
+          /* QueryPerformanceFrequency worked.  We can use
+             QueryPerformanceCounter.  Use a combination of Sleep and
+             busy-looping.  */
+          /* Number of milliseconds to pass to the Sleep function.
+             Since Sleep can take up to 8 ms less or 8 ms more than requested
+             (or maybe more if the system is loaded), we subtract 10 ms.  */
+          int sleep_millis = (int) requested_delay->tv_nsec / 1000000 - 10;
+          /* Determine how many ticks to delay.  */
+          LONGLONG wait_ticks = requested_delay->tv_nsec * ticks_per_nanosecond;
+          /* Start.  */
+          LARGE_INTEGER counter_before;
+          if (QueryPerformanceCounter (&counter_before))
+            {
+              /* Wait until the performance counter has reached this value.
+                 We don't need to worry about overflow, because the performance
+                 counter is reset at reboot, and with a frequency of 3.6E6
+                 ticks per second 63 bits suffice for over 80000 years.  */
+              LONGLONG wait_until = counter_before.QuadPart + wait_ticks;
+              /* Use Sleep for the longest part.  */
+              if (sleep_millis > 0)
+                Sleep (sleep_millis);
+              /* Busy-loop for the rest.  */
+              for (;;)
+                {
+                  LARGE_INTEGER counter_after;
+                  if (!QueryPerformanceCounter (&counter_after))
+                    /* QueryPerformanceCounter failed, but succeeded earlier.
+                       Should not happen.  */
+                    break;
+                  if (counter_after.QuadPart >= wait_until)
+                    /* The requested time has elapsed.  */
+                    break;
+                }
+              goto done;
+            }
+        }
+    }
+  /* Implementation for long delays and as fallback.  */
+  Sleep (requested_delay->tv_sec * 1000 + requested_delay->tv_nsec / 1000000);
+
+ done:
+  /* Sleep is not interruptible.  So there is no remaining delay.  */
+  if (remaining_delay != NULL)
+    {
+      remaining_delay->tv_sec = 0;
+      remaining_delay->tv_nsec = 0;
+    }
+  return 0;
+}
+
+#else
+/* Other platforms lacking nanosleep.
+   It's not clear whether these are still practical porting targets.
+   For now, just fall back on pselect.  */
+
+/* Suspend execution for at least *REQUESTED_DELAY seconds.  The
+   *REMAINING_DELAY part isn't implemented yet.  */
+
+int
+nanosleep (const struct timespec *requested_delay,
+           struct timespec *remaining_delay)
+{
+  return pselect (0, NULL, NULL, NULL, requested_delay, NULL);
+}
+#endif
diff --git a/m4/gnulib-comp.m4 b/m4/gnulib-comp.m4
index 1d31239d2d..fb5f1b52a4 100644
--- a/m4/gnulib-comp.m4
+++ b/m4/gnulib-comp.m4
@@ -140,6 +140,7 @@ AC_DEFUN
   # Code from module mktime:
   # Code from module mktime-internal:
   # Code from module multiarch:
+  # Code from module nanosleep:
   # Code from module nocrash:
   # Code from module nproc:
   # Code from module nstrftime:
@@ -430,6 +431,10 @@ AC_DEFUN
   fi
   gl_TIME_MODULE_INDICATOR([mktime])
   gl_MULTIARCH
+  gl_FUNC_NANOSLEEP
+  gl_CONDITIONAL([GL_COND_OBJ_NANOSLEEP],
+                 [test $HAVE_NANOSLEEP = 0 || test $REPLACE_NANOSLEEP = 1])
+  gl_TIME_MODULE_INDICATOR([nanosleep])
   gl_NPROC
   gl_FUNC_GNU_STRFTIME
   gl_PATHMAX
@@ -1304,6 +1309,7 @@ AC_DEFUN
   lib/mkostemp.c
   lib/mktime-internal.h
   lib/mktime.c
+  lib/nanosleep.c
   lib/nproc.c
   lib/nproc.h
   lib/nstrftime.c
@@ -1456,6 +1462,7 @@ AC_DEFUN
   m4/mktime.m4
   m4/mode_t.m4
   m4/multiarch.m4
+  m4/nanosleep.m4
   m4/nocrash.m4
   m4/nproc.m4
   m4/nstrftime.m4
diff --git a/m4/nanosleep.m4 b/m4/nanosleep.m4
new file mode 100644
index 0000000000..1964b1ea47
--- /dev/null
+++ b/m4/nanosleep.m4
@@ -0,0 +1,139 @@
+# serial 41
+
+dnl From Jim Meyering.
+dnl Check for the nanosleep function.
+dnl If not found, use the supplied replacement.
+dnl
+
+# Copyright (C) 1999-2001, 2003-2022 Free Software Foundation, Inc.
+
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_NANOSLEEP],
+[
+ AC_REQUIRE([gl_TIME_H_DEFAULTS])
+ AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+
+ dnl Persuade glibc and Solaris <time.h> to declare nanosleep.
+ AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
+
+ AC_CHECK_DECLS_ONCE([alarm])
+
+ nanosleep_save_libs=$LIBS
+
+ # Solaris 2.5.1 needs -lposix4 to get the nanosleep function.
+ # Solaris 7 prefers the library name -lrt to the obsolescent name -lposix4.
+ LIB_NANOSLEEP=
+ AC_SUBST([LIB_NANOSLEEP])
+ AC_SEARCH_LIBS([nanosleep], [rt posix4],
+                [test "$ac_cv_search_nanosleep" = "none required" ||
+                 LIB_NANOSLEEP=$ac_cv_search_nanosleep])
+ if test "x$ac_cv_search_nanosleep" != xno; then
+   dnl The system has a nanosleep function.
+
+   AC_REQUIRE([gl_MULTIARCH])
+   if test $APPLE_UNIVERSAL_BUILD = 1; then
+     # A universal build on Apple Mac OS X platforms.
+     # The test result would be 'no (mishandles large arguments)' in 64-bit
+     # mode but 'yes' in 32-bit mode. But we need a configuration result that
+     # is valid in both modes.
+     gl_cv_func_nanosleep='no (mishandles large arguments)'
+   fi
+
+   AC_CACHE_CHECK([for working nanosleep],
+    [gl_cv_func_nanosleep],
+    [
+     AC_RUN_IFELSE(
+       [AC_LANG_SOURCE([[
+          #include <errno.h>
+          #include <limits.h>
+          #include <signal.h>
+          #include <time.h>
+          #include <unistd.h>
+          #define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
+          #define TYPE_MAXIMUM(t) \
+            ((t) (! TYPE_SIGNED (t) \
+                  ? (t) -1 \
+                  : ((((t) 1 << (sizeof (t) * CHAR_BIT - 2)) - 1) * 2 + 1)))
+
+          #if HAVE_DECL_ALARM
+          static void
+          check_for_SIGALRM (int sig)
+          {
+            if (sig != SIGALRM)
+              _exit (1);
+          }
+          #endif
+
+          int
+          main ()
+          {
+            static struct timespec ts_sleep;
+            static struct timespec ts_remaining;
+            /* Test for major problems first.  */
+            if (! nanosleep)
+              return 2;
+            ts_sleep.tv_sec = 0;
+            ts_sleep.tv_nsec = 1;
+            #if HAVE_DECL_ALARM
+            {
+              static struct sigaction act;
+              act.sa_handler = check_for_SIGALRM;
+              sigemptyset (&act.sa_mask);
+              sigaction (SIGALRM, &act, NULL);
+              alarm (1);
+              if (nanosleep (&ts_sleep, NULL) != 0)
+                return 3;
+              /* Test for a minor problem: the handling of large arguments.  */
+              ts_sleep.tv_sec = TYPE_MAXIMUM (time_t);
+              ts_sleep.tv_nsec = 999999999;
+              alarm (1);
+              if (nanosleep (&ts_sleep, &ts_remaining) != -1)
+                return 4;
+              if (errno != EINTR)
+                return 5;
+              if (ts_remaining.tv_sec <= TYPE_MAXIMUM (time_t) - 10)
+                return 6;
+            }
+            #else /* A simpler test for native Windows.  */
+            if (nanosleep (&ts_sleep, &ts_remaining) < 0)
+              return 3;
+            #endif
+            return 0;
+          }]])],
+       [gl_cv_func_nanosleep=yes],
+       [case $? in dnl (
+        4|5|6) gl_cv_func_nanosleep='no (mishandles large arguments)';; dnl (
+        *)   gl_cv_func_nanosleep=no;;
+        esac],
+       [case "$host_os" in dnl ((
+          linux*) # Guess it halfway works when the kernel is Linux.
+            gl_cv_func_nanosleep='guessing no (mishandles large arguments)' ;;
+          mingw*) # Guess no on native Windows.
+            gl_cv_func_nanosleep='guessing no' ;;
+          *)      # If we don't know, obey --enable-cross-guesses.
+            gl_cv_func_nanosleep="$gl_cross_guess_normal" ;;
+        esac
+       ])
+    ])
+   case "$gl_cv_func_nanosleep" in
+     *yes)
+       REPLACE_NANOSLEEP=0
+       ;;
+     *)
+       REPLACE_NANOSLEEP=1
+       case "$gl_cv_func_nanosleep" in
+         *"mishandles large arguments"*)
+           AC_DEFINE([HAVE_BUG_BIG_NANOSLEEP], [1],
+             [Define to 1 if nanosleep mishandles large arguments.])
+           ;;
+       esac
+       ;;
+   esac
+ else
+   HAVE_NANOSLEEP=0
+ fi
+ LIBS=$nanosleep_save_libs
+])
diff --git a/src/gnutls.c b/src/gnutls.c
index 3ec3837067..5389b15b1e 100644
--- a/src/gnutls.c
+++ b/src/gnutls.c
@@ -616,6 +616,7 @@ gnutls_try_handshake (struct Lisp_Process *proc)
   gnutls_session_t state = proc->gnutls_state;
   int ret;
   bool non_blocking = proc->is_non_blocking_client;
+  struct timespec delay = { 0, 1000 * 1000 * 10 };
 
   if (proc->gnutls_complete_negotiation_p)
     non_blocking = false;
@@ -630,6 +631,7 @@ gnutls_try_handshake (struct Lisp_Process *proc)
       maybe_quit ();
       if (non_blocking && ret != GNUTLS_E_INTERRUPTED)
 	break;
+      nanosleep (&delay, NULL);
     }
 
   proc->gnutls_initstage = GNUTLS_STAGE_HANDSHAKE_TRIED;

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no