]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add isc_trampoline API to have simple accounting around threads
authorOndřej Surý <ondrej@sury.org>
Tue, 16 Feb 2021 14:57:39 +0000 (15:57 +0100)
committerOndřej Surý <ondrej@isc.org>
Fri, 26 Feb 2021 20:14:17 +0000 (21:14 +0100)
The current isc_hp API uses internal tid_v variable that gets
incremented for each new thread using hazard pointers.  This tid_v
variable is then used as a index to global shared table with hazard
pointers state.  Since the tid_v is only incremented and never
decremented the table could overflow very quickly if we create set of
threads for short period of time, they finish the work and cease to
exist.  Then we create identical set of threads and so on and so on.
This is not a problem for a normal `named` operation as the set of
threads is stable, but the problematic place are the unit tests where we
test network manager or other APIs (task, timer) that create threads.

This commits adds a thin wrapper around any function called from
isc_thread_create() that adds unique-but-reusable small digit thread id
that can be used as index to f.e. hazard pointer tables.  The trampoline
wrapper ensures that the thread ids will be reused, so the highest
thread_id number doesn't grow indefinitely when threads are created and
destroyed and then created again.  This fixes the hazard pointer table
overflow on machines with many cores. [GL #2396]

(cherry picked from commit cbbecfcc822d7607adfcfcdaa35ccad05fa972c6)

13 files changed:
lib/isc/Makefile.in
lib/isc/lib.c
lib/isc/pthreads/include/isc/thread.h
lib/isc/pthreads/thread.c
lib/isc/trampoline.c [new file with mode: 0644]
lib/isc/trampoline_p.h [new file with mode: 0644]
lib/isc/win32/DLLMain.c
lib/isc/win32/include/isc/thread.h
lib/isc/win32/libisc.def.in
lib/isc/win32/libisc.vcxproj.filters.in
lib/isc/win32/libisc.vcxproj.in
lib/isc/win32/thread.c
util/copyrights

index d3d3f05597d6fe668663ee3a0658dcb2e41127b8..c720f4a41bf15739c6d1df3c7ac30de08defc1f3 100644 (file)
@@ -60,6 +60,7 @@ OBJS =                pk11.@O@ pk11_result.@O@ \
                region.@O@ regex.@O@ result.@O@ rwlock.@O@ \
                safe.@O@ serial.@O@ siphash.@O@ sockaddr.@O@ stats.@O@ \
                string.@O@ symtab.@O@ task.@O@ taskpool.@O@ tls.@O@ \
+               trampoline.@O@ \
                utf8.@O@ tm.@O@ timer.@O@ version.@O@ \
                ${UNIXOBJS} ${THREADOBJS}
 SYMTBLOBJS =   backtrace-emptytbl.@O@
@@ -78,6 +79,7 @@ SRCS =                pk11.c pk11_result.c \
                ratelimiter.c region.c regex.c result.c rwlock.c \
                safe.c serial.c siphash.c sockaddr.c stats.c string.c \
                symtab.c task.c taskpool.c timer.c tls.c \
+               trampoline.c \
                utf8.c tm.c version.c
 
 LIBS =         ${OPENSSL_LIBS} ${JSON_C_LIBS} ${LIBUV_LIBS} ${LIBXML2_LIBS} ${ZLIB_LIBS} @LIBS@
index dad744903dd06d184de295c317a57e842eb50839..b1afa2b35542a663d27ffbdd5a6651a084718f76 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "mem_p.h"
 #include "tls_p.h"
+#include "trampoline_p.h"
 
 /***
  *** Functions
@@ -38,10 +39,12 @@ void
 isc__initialize(void) {
        isc__mem_initialize();
        isc__tls_initialize();
+       isc__trampoline_initialize();
 }
 
 void
 isc__shutdown(void) {
+       isc__trampoline_shutdown();
        isc__tls_shutdown();
        isc__mem_shutdown();
 }
index f9d3f69f7f0db7bece7e595465a326c1276db786..bc95381d7b7b44affb07b512623dfea1733654b8 100644 (file)
 #include <isc/lang.h>
 #include <isc/result.h>
 
+#if defined(HAVE_THREAD_LOCAL)
+#include <threads.h>
+extern thread_local size_t isc_tid_v;
+#elif defined(HAVE___THREAD)
+extern __thread size_t isc_tid_v;
+#endif /* if defined(HAVE_THREAD_LOCAL) */
+
 ISC_LANG_BEGINDECLS
 
 typedef pthread_t isc_thread_t;
@@ -56,7 +63,6 @@ isc_thread_setaffinity(int cpu);
 
 #if defined(HAVE_TLS)
 #if defined(HAVE_THREAD_LOCAL)
-#include <threads.h>
 #define ISC_THREAD_LOCAL static thread_local
 #elif defined(HAVE___THREAD)
 #define ISC_THREAD_LOCAL static __thread
index 6477aa876f22468796e6ac6278041fa68a31cee6..21a3cd8029f047b6090aad214eb9b1badd1d0caf 100644 (file)
@@ -30,6 +30,8 @@
 #include <isc/thread.h>
 #include <isc/util.h>
 
+#include "trampoline_p.h"
+
 #ifndef THREAD_MINSTACKSIZE
 #define THREAD_MINSTACKSIZE (1024U * 1024)
 #endif /* ifndef THREAD_MINSTACKSIZE */
@@ -45,6 +47,10 @@ void
 isc_thread_create(isc_threadfunc_t func, isc_threadarg_t arg,
                  isc_thread_t *thread) {
        pthread_attr_t attr;
+       isc__trampoline_t *trampoline_arg;
+
+       trampoline_arg = isc__trampoline_get(func, arg);
+
 #if defined(HAVE_PTHREAD_ATTR_GETSTACKSIZE) && \
        defined(HAVE_PTHREAD_ATTR_SETSTACKSIZE)
        size_t stacksize;
@@ -70,7 +76,8 @@ isc_thread_create(isc_threadfunc_t func, isc_threadarg_t arg,
 #endif /* if defined(HAVE_PTHREAD_ATTR_GETSTACKSIZE) && \
        * defined(HAVE_PTHREAD_ATTR_SETSTACKSIZE) */
 
-       ret = pthread_create(thread, &attr, func, arg);
+       ret = pthread_create(thread, &attr, isc__trampoline_run,
+                            trampoline_arg);
        if (ret != 0) {
                _FATAL(ret, "pthread_create()");
        }
diff --git a/lib/isc/trampoline.c b/lib/isc/trampoline.c
new file mode 100644 (file)
index 0000000..200594d
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdlib.h>
+
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/once.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include "trampoline_p.h"
+
+#define ISC__TRAMPOLINE_UNUSED 0
+
+struct isc__trampoline {
+       int tid; /* const */
+       isc_thread_t self;
+       isc_threadfunc_t start;
+       isc_threadarg_t arg;
+};
+
+static isc_once_t isc__trampoline_initialize_once = ISC_ONCE_INIT;
+static isc_once_t isc__trampoline_shutdown_once = ISC_ONCE_INIT;
+static isc_mutex_t isc__trampoline_lock;
+static isc__trampoline_t **trampolines;
+#if defined(HAVE_THREAD_LOCAL)
+#include <threads.h>
+thread_local size_t isc_tid_v = SIZE_MAX;
+#elif defined(HAVE___THREAD)
+__thread size_t isc_tid_v = SIZE_MAX;
+#elif HAVE___DECLSPEC_THREAD
+__declspec(thread) size_t isc_tid_v = SIZE_MAX;
+#endif /* if defined(HAVE_THREAD_LOCAL) */
+static size_t isc__trampoline_min = 1;
+static size_t isc__trampoline_max = 65;
+
+/*
+ * We can't use isc_mem API here, because it's called too
+ * early and when the isc_mem_debugging flags are changed
+ * later and ISC_MEM_DEBUGSIZE or ISC_MEM_DEBUGCTX flags are
+ * added, neither isc_mem_put() nor isc_mem_free() can be used
+ * to free up the memory allocated here because the flags were
+ * not set when calling isc_mem_get() or isc_mem_allocate()
+ * here.
+ *
+ * Actually, since this is a single allocation at library load
+ * and deallocation at library unload, using the standard
+ * allocator without the tracking is fine for this purpose.
+ */
+static isc__trampoline_t *
+isc__trampoline_new(int tid, isc_threadfunc_t start, isc_threadarg_t arg) {
+       isc__trampoline_t *trampoline = calloc(1, sizeof(*trampoline));
+       RUNTIME_CHECK(trampoline != NULL);
+
+       *trampoline = (isc__trampoline_t){
+               .tid = tid,
+               .start = start,
+               .arg = arg,
+               .self = ISC__TRAMPOLINE_UNUSED,
+       };
+
+       return (trampoline);
+}
+
+static void
+trampoline_initialize(void) {
+       isc_mutex_init(&isc__trampoline_lock);
+
+       trampolines = calloc(isc__trampoline_max, sizeof(trampolines[0]));
+       RUNTIME_CHECK(trampolines != NULL);
+
+       /* Get the trampoline slot 0 for the main thread */
+       trampolines[0] = isc__trampoline_new(0, NULL, NULL);
+       trampolines[0]->self = isc_thread_self();
+       isc_tid_v = trampolines[0]->tid;
+
+       /* Initialize the other trampolines */
+       for (size_t i = 1; i < isc__trampoline_max; i++) {
+               trampolines[i] = NULL;
+       }
+       isc__trampoline_min = 1;
+}
+
+void
+isc__trampoline_initialize(void) {
+       isc_result_t result = isc_once_do(&isc__trampoline_initialize_once,
+                                         trampoline_initialize);
+       RUNTIME_CHECK(result == ISC_R_SUCCESS);
+}
+
+static void
+trampoline_shutdown(void) {
+       /*
+        * When the program using the library exits abruptly and the library
+        * gets unloaded, there might be some existing trampolines from unjoined
+        * threads.  We intentionally ignore those and don't check whether all
+        * trampolines have been cleared before exiting.
+        */
+       free(trampolines[0]);
+       free(trampolines);
+       trampolines = NULL;
+       isc_mutex_destroy(&isc__trampoline_lock);
+}
+
+void
+isc__trampoline_shutdown(void) {
+       isc_result_t result = isc_once_do(&isc__trampoline_shutdown_once,
+                                         trampoline_shutdown);
+       RUNTIME_CHECK(result == ISC_R_SUCCESS);
+}
+
+isc__trampoline_t *
+isc__trampoline_get(isc_threadfunc_t start, isc_threadarg_t arg) {
+       isc__trampoline_t **tmp = NULL;
+       isc__trampoline_t *trampoline = NULL;
+       LOCK(&isc__trampoline_lock);
+again:
+       for (size_t i = isc__trampoline_min; i < isc__trampoline_max; i++) {
+               if (trampolines[i] == NULL) {
+                       trampoline = isc__trampoline_new(i, start, arg);
+                       trampolines[i] = trampoline;
+                       isc__trampoline_min = i + 1;
+                       goto done;
+               }
+       }
+       tmp = calloc(2 * isc__trampoline_max, sizeof(trampolines[0]));
+       RUNTIME_CHECK(tmp != NULL);
+       for (size_t i = 0; i < isc__trampoline_max; i++) {
+               tmp[i] = trampolines[i];
+       }
+       for (size_t i = isc__trampoline_max; i < 2 * isc__trampoline_max; i++) {
+               tmp[i] = NULL;
+       }
+       free(trampolines);
+       trampolines = tmp;
+       isc__trampoline_max = isc__trampoline_max * 2;
+       goto again;
+done:
+       INSIST(trampoline != NULL);
+       UNLOCK(&isc__trampoline_lock);
+
+       return (trampoline);
+}
+
+static void
+trampoline_put(isc__trampoline_t *trampoline) {
+       LOCK(&isc__trampoline_lock);
+       REQUIRE(trampoline->tid > 0 &&
+               (size_t)trampoline->tid < isc__trampoline_max);
+       REQUIRE(trampoline->self == isc_thread_self());
+       REQUIRE(trampolines[trampoline->tid] == trampoline);
+
+       trampolines[trampoline->tid] = NULL;
+
+       if (isc__trampoline_min > (size_t)trampoline->tid) {
+               isc__trampoline_min = trampoline->tid;
+       }
+
+       free(trampoline);
+
+       UNLOCK(&isc__trampoline_lock);
+       return;
+}
+
+isc_threadresult_t
+isc__trampoline_run(isc_threadarg_t arg) {
+       isc__trampoline_t *trampoline = (isc__trampoline_t *)arg;
+       isc_threadresult_t result;
+
+       REQUIRE(trampoline->tid > 0 &&
+               (size_t)trampoline->tid < isc__trampoline_max);
+       REQUIRE(trampoline->self == ISC__TRAMPOLINE_UNUSED);
+
+       /* Initialize the trampoline */
+       isc_tid_v = trampoline->tid;
+       trampoline->self = isc_thread_self();
+
+       /* Run the main function */
+       result = (trampoline->start)(trampoline->arg);
+
+       trampoline_put(trampoline);
+
+       return (result);
+}
diff --git a/lib/isc/trampoline_p.h b/lib/isc/trampoline_p.h
new file mode 100644 (file)
index 0000000..a089a97
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <isc/thread.h>
+
+typedef struct isc__trampoline isc__trampoline_t;
+
+void
+isc__trampoline_initialize(void);
+/*%<
+ * Initialize the thread trampoline internal structures, must be called only
+ * once as a library constructor (see lib/isc/lib.c).
+ */
+
+void
+isc__trampoline_shutdown(void);
+/*%<
+ * Destroy the thread trampoline internal structures, must be called only
+ * once as a library destructor (see lib/isc/lib.c).
+ */
+
+isc__trampoline_t *
+isc__trampoline_get(isc_threadfunc_t start_routine, isc_threadarg_t arg);
+/*%<
+ * Get a free thread trampoline structure and initialize it with
+ * start_routine and arg passed to start_routine.
+ *
+ * Requires:
+ *\li  'start_routine' is a valid non-NULL thread start_routine
+ */
+
+isc_threadresult_t
+isc__trampoline_run(isc_threadarg_t arg);
+/*%<
+ * Run the thread trampoline, this will get passed to the actual
+ * pthread_create() (or Windows equivalent), initialize the isc_tid_v.
+ *
+ * Requires:
+ *\li  'arg' is a valid isc_trampoline_t
+ *
+ * Returns:
+ *\li  return value from start_routine (see isc__trampoline_get())
+ */
index 0ae51771987d22869d40274f38492e5f86ac7ef6..187e84fbcd7a8f7f56ede195e86bdbb24c83ad1b 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "mem_p.h"
 #include "tls_p.h"
+#include "trampoline_p.h"
 
 /*
  * Called when we enter the DLL
@@ -32,6 +33,7 @@ __declspec(dllexport) BOOL WINAPI
        case DLL_PROCESS_ATTACH:
                isc__mem_initialize();
                isc__tls_initialize();
+               isc__trampoline_initialize();
                break;
 
        /*
@@ -39,6 +41,7 @@ __declspec(dllexport) BOOL WINAPI
         * termination or a call to FreeLibrary.
         */
        case DLL_PROCESS_DETACH:
+               isc__trampoline_shutdown();
                isc__tls_shutdown();
                isc__mem_shutdown();
                break;
index d689eac4c60f7d446398c08b530473e6e83c738e..3b0eb0631f5a1543ed8e6d46aeee3bb3dc4dc005 100644 (file)
@@ -17,6 +17,8 @@
 #include <isc/lang.h>
 #include <isc/result.h>
 
+extern __declspec(thread) size_t isc_tid_v;
+
 /*
  * Inlines to help with wait return checking
  */
index c3631bf308eb8b61360310950eabcf53c4f7b59d..79fee67763afcd1057761ddeace538d85d5e55b8 100644 (file)
@@ -717,6 +717,10 @@ isc__tls_shutdown
 isc_tlsctx_createclient
 isc_tlsctx_createserver
 isc_tlsctx_free
+isc__trampoline_initialize
+isc__trampoline_shutdown
+isc__trampoline_get
+isc__trampoline_run
 isc_tm_timegm
 isc_tm_strptime
 isc_utf8_bom
@@ -799,6 +803,7 @@ isc_commandline_reset               DATA
 isc_dscp_check_value           DATA
 isc_hashctx                    DATA
 isc_mem_debugging              DATA
+isc_tid_v                      DATA
 @IF PKCS11
 pk11_verbose_init              DATA
 @END PKCS11
index 38a3a475aa4690243f8089346aadb54588d7b74a..874527210691bd1998092b2c3dec7488a8c53b75 100644 (file)
     <ClInclude Include="..\openssl_shim.h">
       <Filter>Win32 Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\trampoline_p.h">
+      <Filter>Win32 Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="errno2result.h">
       <Filter>Win32 Header Files</Filter>
     </ClInclude>
     <ClCompile Include="..\tls.c">
       <Filter>Library Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\trampoline.c">
+      <Filter>Library Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\tm.c">
       <Filter>Library Source Files</Filter>
     </ClCompile>
index 5698bb0f3660da286ada4304f517185714515095..25ac4a7f1be91bf2752065affbaa8aa08c0a1498 100644 (file)
@@ -372,6 +372,7 @@ copy InstallFiles ..\Build\Release\
     <ClInclude Include="include\isc\win32os.h" />
     <ClInclude Include="..\entropy_private.h" />
     <ClInclude Include="..\openssl_shim.h" />
+    <ClInclude Include="..\trampoline_p.h" />
     <ClInclude Include="syslog.h" />
     <ClInclude Include="unistd.h" />
     <ClInclude Include="..\..\versions.h" />
@@ -442,6 +443,7 @@ copy InstallFiles ..\Build\Release\
     <ClCompile Include="..\task.c" />
     <ClCompile Include="..\taskpool.c" />
     <ClCompile Include="..\timer.c" />
+    <ClCompile Include="..\trampoline.c" />
     <ClCompile Include="..\tls.c" />
     <ClCompile Include="..\tm.c" />
     <ClCompile Include="..\utf8.c" />
index 814fc9dc0dc8df934a7578f9a1af8c45de3b0c31..9e9707b8bd14326341bb0da5563abc6dc00a96ef 100644 (file)
 
 #include <process.h>
 
+#include <isc/mutex.h>
+#include <isc/once.h>
 #include <isc/strerr.h>
 #include <isc/thread.h>
 #include <isc/util.h>
 
+#include "trampoline_p.h"
+
 void
 isc_thread_create(isc_threadfunc_t start, isc_threadarg_t arg,
                  isc_thread_t *threadp) {
        isc_thread_t thread;
        unsigned int id;
+       isc__trampoline_t *trampoline_arg;
+
+       trampoline_arg = isc__trampoline_get(start, arg);
 
-       thread = (isc_thread_t)_beginthreadex(NULL, 0, start, arg, 0, &id);
+       thread = (isc_thread_t)_beginthreadex(NULL, 0, isc__trampoline_run,
+                                             trampoline_arg, 0, &id);
        if (thread == NULL) {
                char strbuf[ISC_STRERRORSIZE];
                strerror_r(errno, strbuf, sizeof(strbuf));
index d5260916e16150379f6e98bacedb3c5bc0482ade..43c27b90da9f3b2ba7687202a80997524dd7f3c6 100644 (file)
 ./lib/isc/tls.c                                        C       2021
 ./lib/isc/tls_p.h                              C       2021
 ./lib/isc/tm.c                                 C       2014,2016,2018,2019,2020,2021
+./lib/isc/trampoline.c                         C       2021
+./lib/isc/trampoline_p.h                       C       2021
 ./lib/isc/unix/dir.c                           C       1999,2000,2001,2004,2005,2007,2008,2009,2011,2012,2016,2017,2018,2019,2020,2021
 ./lib/isc/unix/errno.c                         C       2016,2018,2019,2020,2021
 ./lib/isc/unix/errno2result.c                  C       2000,2001,2002,2004,2005,2007,2011,2012,2013,2016,2018,2019,2020,2021