Skip to content

Commit 9b1befa

Browse files
committed
Add minimal initialized TSD.
We use the minimal_initilized tsd (which requires no cleanup) for free() specifically, if tsd hasn't been initialized yet. Any other activity will transit the state from minimal to normal. This is to workaround the case where a thread has no malloc calls in its lifetime until during thread termination, free() happens after tls destructors.
1 parent ae93fb0 commit 9b1befa

File tree

3 files changed

+60
-24
lines changed

3 files changed

+60
-24
lines changed

include/jemalloc/internal/tsd.h

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,10 @@ enum {
9999
tsd_state_nominal_slow = 1, /* Initialized but on slow path. */
100100
/* the above 2 nominal states should be lower values. */
101101
tsd_state_nominal_max = 1, /* used for comparison only. */
102-
tsd_state_purgatory = 2,
103-
tsd_state_reincarnated = 3,
104-
tsd_state_uninitialized = 4
102+
tsd_state_minimal_initialized = 2,
103+
tsd_state_purgatory = 3,
104+
tsd_state_reincarnated = 4,
105+
tsd_state_uninitialized = 5
105106
};
106107

107108
/* Manually limit tsd_state_t to a single byte. */
@@ -190,7 +191,8 @@ JEMALLOC_ALWAYS_INLINE t * \
190191
tsd_##n##p_get(tsd_t *tsd) { \
191192
assert(tsd->state == tsd_state_nominal || \
192193
tsd->state == tsd_state_nominal_slow || \
193-
tsd->state == tsd_state_reincarnated); \
194+
tsd->state == tsd_state_reincarnated || \
195+
tsd->state == tsd_state_minimal_initialized); \
194196
return tsd_##n##p_get_unsafe(tsd); \
195197
}
196198
MALLOC_TSD
@@ -225,7 +227,8 @@ MALLOC_TSD
225227
#define O(n, t, nt) \
226228
JEMALLOC_ALWAYS_INLINE void \
227229
tsd_##n##_set(tsd_t *tsd, t val) { \
228-
assert(tsd->state != tsd_state_reincarnated); \
230+
assert(tsd->state != tsd_state_reincarnated && \
231+
tsd->state != tsd_state_minimal_initialized); \
229232
*tsd_##n##p_get(tsd) = val; \
230233
}
231234
MALLOC_TSD
@@ -248,7 +251,7 @@ tsd_fast(tsd_t *tsd) {
248251
}
249252

250253
JEMALLOC_ALWAYS_INLINE tsd_t *
251-
tsd_fetch_impl(bool init, bool internal) {
254+
tsd_fetch_impl(bool init, bool minimal) {
252255
tsd_t *tsd = tsd_get(init);
253256

254257
if (!init && tsd_get_allocates() && tsd == NULL) {
@@ -257,19 +260,30 @@ tsd_fetch_impl(bool init, bool internal) {
257260
assert(tsd != NULL);
258261

259262
if (unlikely(tsd->state != tsd_state_nominal)) {
260-
return tsd_fetch_slow(tsd, internal);
263+
return tsd_fetch_slow(tsd, minimal);
261264
}
262265
assert(tsd_fast(tsd));
263266
tsd_assert_fast(tsd);
264267

265268
return tsd;
266269
}
267270

271+
/* Get a minimal TSD that requires no cleanup. See comments in free(). */
268272
JEMALLOC_ALWAYS_INLINE tsd_t *
269-
tsd_internal_fetch(void) {
273+
tsd_fetch_min(void) {
270274
return tsd_fetch_impl(true, true);
271275
}
272276

277+
/* For internal background threads use only. */
278+
JEMALLOC_ALWAYS_INLINE tsd_t *
279+
tsd_internal_fetch(void) {
280+
tsd_t *tsd = tsd_fetch_min();
281+
/* Use reincarnated state to prevent full initialization. */
282+
tsd->state = tsd_state_reincarnated;
283+
284+
return tsd;
285+
}
286+
273287
JEMALLOC_ALWAYS_INLINE tsd_t *
274288
tsd_fetch(void) {
275289
return tsd_fetch_impl(true, false);

src/jemalloc.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2264,7 +2264,15 @@ JEMALLOC_EXPORT void JEMALLOC_NOTHROW
22642264
je_free(void *ptr) {
22652265
UTRACE(ptr, 0, 0);
22662266
if (likely(ptr != NULL)) {
2267-
tsd_t *tsd = tsd_fetch();
2267+
/*
2268+
* We avoid setting up tsd fully (e.g. tcache, arena binding)
2269+
* based on only free() calls -- other activities trigger the
2270+
* minimal to full transition. This is because free() may
2271+
* happen during thread shutdown after tls deallocation: if a
2272+
* thread never had any malloc activities until then, a
2273+
* fully-setup tsd won't be destructed properly.
2274+
*/
2275+
tsd_t *tsd = tsd_fetch_min();
22682276
check_entry_exit_locking(tsd_tsdn(tsd));
22692277

22702278
tcache_t *tcache;

src/tsd.c

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ assert_tsd_data_cleanup_done(tsd_t *tsd) {
8787

8888
static bool
8989
tsd_data_init_nocleanup(tsd_t *tsd) {
90-
assert(tsd->state == tsd_state_reincarnated);
90+
assert(tsd->state == tsd_state_reincarnated ||
91+
tsd->state == tsd_state_minimal_initialized);
9192
/*
9293
* During reincarnation, there is no guarantee that the cleanup function
9394
* will be called (deallocation may happen after all tsd destructors).
@@ -103,27 +104,37 @@ tsd_data_init_nocleanup(tsd_t *tsd) {
103104
}
104105

105106
tsd_t *
106-
tsd_fetch_slow(tsd_t *tsd, bool internal) {
107-
if (internal) {
108-
/* For internal background threads use only. */
109-
assert(tsd->state == tsd_state_uninitialized);
110-
tsd->state = tsd_state_reincarnated;
111-
tsd_set(tsd);
112-
tsd_data_init_nocleanup(tsd);
113-
return tsd;
114-
}
107+
tsd_fetch_slow(tsd_t *tsd, bool minimal) {
108+
assert(!tsd_fast(tsd));
115109

116110
if (tsd->state == tsd_state_nominal_slow) {
117111
/* On slow path but no work needed. */
118112
assert(malloc_slow || !tsd_tcache_enabled_get(tsd) ||
119113
tsd_reentrancy_level_get(tsd) > 0 ||
120114
*tsd_arenas_tdata_bypassp_get(tsd));
121115
} else if (tsd->state == tsd_state_uninitialized) {
122-
tsd->state = tsd_state_nominal;
123-
tsd_slow_update(tsd);
124-
/* Trigger cleanup handler registration. */
125-
tsd_set(tsd);
126-
tsd_data_init(tsd);
116+
if (!minimal) {
117+
tsd->state = tsd_state_nominal;
118+
tsd_slow_update(tsd);
119+
/* Trigger cleanup handler registration. */
120+
tsd_set(tsd);
121+
tsd_data_init(tsd);
122+
} else {
123+
tsd->state = tsd_state_minimal_initialized;
124+
tsd_set(tsd);
125+
tsd_data_init_nocleanup(tsd);
126+
}
127+
} else if (tsd->state == tsd_state_minimal_initialized) {
128+
if (!minimal) {
129+
/* Switch to fully initialized. */
130+
tsd->state = tsd_state_nominal;
131+
assert(*tsd_reentrancy_levelp_get(tsd) >= 1);
132+
(*tsd_reentrancy_levelp_get(tsd))--;
133+
tsd_slow_update(tsd);
134+
tsd_data_init(tsd);
135+
} else {
136+
assert_tsd_data_cleanup_done(tsd);
137+
}
127138
} else if (tsd->state == tsd_state_purgatory) {
128139
tsd->state = tsd_state_reincarnated;
129140
tsd_set(tsd);
@@ -197,6 +208,9 @@ tsd_cleanup(void *arg) {
197208
case tsd_state_uninitialized:
198209
/* Do nothing. */
199210
break;
211+
case tsd_state_minimal_initialized:
212+
/* This implies the thread only did free() in its life time. */
213+
/* Fall through. */
200214
case tsd_state_reincarnated:
201215
/*
202216
* Reincarnated means another destructor deallocated memory

0 commit comments

Comments
 (0)