Home | History | Annotate | Line # | Download | only in isc
      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