trampoline.c revision 1.1 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