1 1.1 christos /* $NetBSD: trampoline.c,v 1.1 2024/02/18 20:57:51 christos Exp $ */ 2 1.1 christos 3 1.1 christos /* 4 1.1 christos * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 1.1 christos * 6 1.1 christos * SPDX-License-Identifier: MPL-2.0 7 1.1 christos * 8 1.1 christos * This Source Code Form is subject to the terms of the Mozilla Public 9 1.1 christos * License, v. 2.0. If a copy of the MPL was not distributed with this 10 1.1 christos * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 1.1 christos * 12 1.1 christos * See the COPYRIGHT file distributed with this work for additional 13 1.1 christos * information regarding copyright ownership. 14 1.1 christos */ 15 1.1 christos 16 1.1 christos /*! \file */ 17 1.1 christos 18 1.1 christos #include <inttypes.h> 19 1.1 christos #include <stdlib.h> 20 1.1 christos #include <uv.h> 21 1.1 christos 22 1.1 christos #include <isc/mem.h> 23 1.1 christos #include <isc/once.h> 24 1.1 christos #include <isc/thread.h> 25 1.1 christos #include <isc/util.h> 26 1.1 christos 27 1.1 christos #include "trampoline_p.h" 28 1.1 christos 29 1.1 christos #define ISC__TRAMPOLINE_UNUSED 0 30 1.1 christos 31 1.1 christos struct isc__trampoline { 32 1.1 christos int tid; /* const */ 33 1.1 christos uintptr_t self; 34 1.1 christos isc_threadfunc_t start; 35 1.1 christos isc_threadarg_t arg; 36 1.1 christos void *jemalloc_enforce_init; 37 1.1 christos }; 38 1.1 christos 39 1.1 christos /* 40 1.1 christos * We can't use isc_mem API here, because it's called too 41 1.1 christos * early and when the isc_mem_debugging flags are changed 42 1.1 christos * later and ISC_MEM_DEBUGSIZE or ISC_MEM_DEBUGCTX flags are 43 1.1 christos * added, neither isc_mem_put() nor isc_mem_free() can be used 44 1.1 christos * to free up the memory allocated here because the flags were 45 1.1 christos * not set when calling isc_mem_get() or isc_mem_allocate() 46 1.1 christos * here. 47 1.1 christos * 48 1.1 christos * Since this is a single allocation at library load and deallocation at library 49 1.1 christos * unload, using the standard allocator without the tracking is fine for this 50 1.1 christos * single purpose. 51 1.1 christos * 52 1.1 christos * We can't use isc_mutex API either, because we track whether the mutexes get 53 1.1 christos * properly destroyed, and we intentionally leak the static mutex here without 54 1.1 christos * destroying it to prevent data race between library destructor running while 55 1.1 christos * thread is being still created. 56 1.1 christos */ 57 1.1 christos 58 1.1 christos static uv_mutex_t isc__trampoline_lock; 59 1.1 christos static isc__trampoline_t **trampolines; 60 1.1 christos #if defined(HAVE_THREAD_LOCAL) 61 1.1 christos #include <threads.h> 62 1.1 christos thread_local size_t isc_tid_v = SIZE_MAX; 63 1.1 christos #elif defined(HAVE___THREAD) 64 1.1 christos __thread size_t isc_tid_v = SIZE_MAX; 65 1.1 christos #elif HAVE___DECLSPEC_THREAD 66 1.1 christos __declspec(thread) size_t isc_tid_v = SIZE_MAX; 67 1.1 christos #endif /* if defined(HAVE_THREAD_LOCAL) */ 68 1.1 christos static size_t isc__trampoline_min = 1; 69 1.1 christos static size_t isc__trampoline_max = 65; 70 1.1 christos 71 1.1 christos static isc_once_t start_once = ISC_ONCE_INIT; 72 1.1 christos static isc_once_t stop_once = ISC_ONCE_INIT; 73 1.1 christos 74 1.1 christos static isc__trampoline_t * 75 1.1 christos isc__trampoline_new(int tid, isc_threadfunc_t start, isc_threadarg_t arg) { 76 1.1 christos isc__trampoline_t *trampoline = calloc(1, sizeof(*trampoline)); 77 1.1 christos RUNTIME_CHECK(trampoline != NULL); 78 1.1 christos 79 1.1 christos *trampoline = (isc__trampoline_t){ 80 1.1 christos .tid = tid, 81 1.1 christos .start = start, 82 1.1 christos .arg = arg, 83 1.1 christos .self = ISC__TRAMPOLINE_UNUSED, 84 1.1 christos }; 85 1.1 christos 86 1.1 christos return (trampoline); 87 1.1 christos } 88 1.1 christos 89 1.1 christos static void 90 1.1 christos do_init(void) { 91 1.1 christos uv_mutex_init(&isc__trampoline_lock); 92 1.1 christos 93 1.1 christos trampolines = calloc(isc__trampoline_max, sizeof(trampolines[0])); 94 1.1 christos RUNTIME_CHECK(trampolines != NULL); 95 1.1 christos 96 1.1 christos /* Get the trampoline slot 0 for the main thread */ 97 1.1 christos trampolines[0] = isc__trampoline_new(0, NULL, NULL); 98 1.1 christos isc_tid_v = trampolines[0]->tid; 99 1.1 christos trampolines[0]->self = isc_thread_self(); 100 1.1 christos 101 1.1 christos /* Initialize the other trampolines */ 102 1.1 christos for (size_t i = 1; i < isc__trampoline_max; i++) { 103 1.1 christos trampolines[i] = NULL; 104 1.1 christos } 105 1.1 christos isc__trampoline_min = 1; 106 1.1 christos } 107 1.1 christos 108 1.1 christos void 109 1.1 christos isc__trampoline_initialize(void) { 110 1.1 christos isc_once_do(&start_once, do_init); 111 1.1 christos } 112 1.1 christos 113 1.1 christos static void 114 1.1 christos do_shutdown(void) { 115 1.1 christos /* 116 1.1 christos * When the program using the library exits abruptly and the library 117 1.1 christos * gets unloaded, there might be some existing trampolines from unjoined 118 1.1 christos * threads. We intentionally ignore those and don't check whether all 119 1.1 christos * trampolines have been cleared before exiting, so we leak a little bit 120 1.1 christos * of resources here, including the lock. 121 1.1 christos */ 122 1.1 christos free(trampolines[0]); 123 1.1 christos } 124 1.1 christos 125 1.1 christos void 126 1.1 christos isc__trampoline_shutdown(void) { 127 1.1 christos isc_once_do(&stop_once, do_shutdown); 128 1.1 christos } 129 1.1 christos 130 1.1 christos isc__trampoline_t * 131 1.1 christos isc__trampoline_get(isc_threadfunc_t start, isc_threadarg_t arg) { 132 1.1 christos isc__trampoline_t **tmp = NULL; 133 1.1 christos isc__trampoline_t *trampoline = NULL; 134 1.1 christos uv_mutex_lock(&isc__trampoline_lock); 135 1.1 christos again: 136 1.1 christos for (size_t i = isc__trampoline_min; i < isc__trampoline_max; i++) { 137 1.1 christos if (trampolines[i] == NULL) { 138 1.1 christos trampoline = isc__trampoline_new(i, start, arg); 139 1.1 christos trampolines[i] = trampoline; 140 1.1 christos isc__trampoline_min = i + 1; 141 1.1 christos goto done; 142 1.1 christos } 143 1.1 christos } 144 1.1 christos tmp = calloc(2 * isc__trampoline_max, sizeof(trampolines[0])); 145 1.1 christos RUNTIME_CHECK(tmp != NULL); 146 1.1 christos for (size_t i = 0; i < isc__trampoline_max; i++) { 147 1.1 christos tmp[i] = trampolines[i]; 148 1.1 christos } 149 1.1 christos for (size_t i = isc__trampoline_max; i < 2 * isc__trampoline_max; i++) { 150 1.1 christos tmp[i] = NULL; 151 1.1 christos } 152 1.1 christos free(trampolines); 153 1.1 christos trampolines = tmp; 154 1.1 christos isc__trampoline_max = isc__trampoline_max * 2; 155 1.1 christos goto again; 156 1.1 christos done: 157 1.1 christos INSIST(trampoline != NULL); 158 1.1 christos uv_mutex_unlock(&isc__trampoline_lock); 159 1.1 christos 160 1.1 christos return (trampoline); 161 1.1 christos } 162 1.1 christos 163 1.1 christos void 164 1.1 christos isc__trampoline_detach(isc__trampoline_t *trampoline) { 165 1.1 christos uv_mutex_lock(&isc__trampoline_lock); 166 1.1 christos REQUIRE(trampoline->self == isc_thread_self()); 167 1.1 christos REQUIRE(trampoline->tid > 0); 168 1.1 christos REQUIRE((size_t)trampoline->tid < isc__trampoline_max); 169 1.1 christos REQUIRE(trampolines[trampoline->tid] == trampoline); 170 1.1 christos 171 1.1 christos trampolines[trampoline->tid] = NULL; 172 1.1 christos 173 1.1 christos if (isc__trampoline_min > (size_t)trampoline->tid) { 174 1.1 christos isc__trampoline_min = trampoline->tid; 175 1.1 christos } 176 1.1 christos 177 1.1 christos free(trampoline->jemalloc_enforce_init); 178 1.1 christos free(trampoline); 179 1.1 christos 180 1.1 christos uv_mutex_unlock(&isc__trampoline_lock); 181 1.1 christos return; 182 1.1 christos } 183 1.1 christos 184 1.1 christos void 185 1.1 christos isc__trampoline_attach(isc__trampoline_t *trampoline) { 186 1.1 christos uv_mutex_lock(&isc__trampoline_lock); 187 1.1 christos REQUIRE(trampoline->self == ISC__TRAMPOLINE_UNUSED); 188 1.1 christos REQUIRE(trampoline->tid > 0); 189 1.1 christos REQUIRE((size_t)trampoline->tid < isc__trampoline_max); 190 1.1 christos REQUIRE(trampolines[trampoline->tid] == trampoline); 191 1.1 christos 192 1.1 christos /* Initialize the trampoline */ 193 1.1 christos isc_tid_v = trampoline->tid; 194 1.1 christos trampoline->self = isc_thread_self(); 195 1.1 christos 196 1.1 christos /* 197 1.1 christos * Ensure every thread starts with a malloc() call to prevent memory 198 1.1 christos * bloat caused by a jemalloc quirk. While this dummy allocation is 199 1.1 christos * not used for anything, free() must not be immediately called for it 200 1.1 christos * so that an optimizing compiler does not strip away such a pair of 201 1.1 christos * malloc() + free() calls altogether, as it would foil the fix. 202 1.1 christos */ 203 1.1 christos trampoline->jemalloc_enforce_init = malloc(8); 204 1.1 christos uv_mutex_unlock(&isc__trampoline_lock); 205 1.1 christos } 206 1.1 christos 207 1.1 christos isc_threadresult_t 208 1.1 christos isc__trampoline_run(isc_threadarg_t arg) { 209 1.1 christos isc__trampoline_t *trampoline = (isc__trampoline_t *)arg; 210 1.1 christos isc_threadresult_t result; 211 1.1 christos 212 1.1 christos isc__trampoline_attach(trampoline); 213 1.1 christos 214 1.1 christos /* Run the main function */ 215 1.1 christos result = (trampoline->start)(trampoline->arg); 216 1.1 christos 217 1.1 christos isc__trampoline_detach(trampoline); 218 1.1 christos 219 1.1 christos return (result); 220 1.1 christos } 221