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