Skip to content

Commit fb5499a

Browse files
committed
Handle jemalloc calls after TSD teardown
This only changes behavior for the tsd_generic path, where pthread_getspecific() can report no TSD after the pthread key destructor has finished. Other TSD backends keep tsd_teardown_done() as a constant false, so the added checks compile out there. Avoid recreating TSD for late deallocations. Preserve existing reincarnation behavior for late allocations and nonzero reallocations. Add Linux CI coverage for force_tls=0, with and without --enable-debug, to exercise the Android-equivalent generic TSD path.
1 parent 82e379b commit fb5499a

11 files changed

Lines changed: 432 additions & 8 deletions

File tree

.github/workflows/linux-ci.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,16 @@ jobs:
508508
CXX: g++
509509
CONFIGURE_FLAGS: "--enable-debug --enable-experimental-smallocx --enable-stats --enable-prof"
510510
EXTRA_CFLAGS: "-Werror -Wno-array-bounds"
511+
- env:
512+
CC: gcc
513+
CXX: g++
514+
CONFIGURE_FLAGS: force_tls=0
515+
EXTRA_CFLAGS: "-Werror -Wno-array-bounds"
516+
- env:
517+
CC: gcc
518+
CXX: g++
519+
CONFIGURE_FLAGS: "force_tls=0 --enable-debug"
520+
EXTRA_CFLAGS: "-Werror -Wno-array-bounds"
511521
- env:
512522
CC: gcc
513523
CXX: g++

include/jemalloc/internal/tsd.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
/*
55
* We put the platform-specific data declarations and inlines into their own
66
* header files to avoid cluttering this file. They define tsd_boot0,
7-
* tsd_boot1, tsd_boot, tsd_booted_get, tsd_get_allocates, tsd_get, and tsd_set.
7+
* tsd_boot1, tsd_boot, tsd_booted_get, tsd_get_allocates,
8+
* tsd_teardown_done, tsd_get, and tsd_set.
89
*/
910
#ifdef JEMALLOC_MALLOC_THREAD_CLEANUP
1011
# include "jemalloc/internal/jemalloc_preamble.h"

include/jemalloc/internal/tsd_generic.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ void *tsd_init_check_recursion(tsd_init_head_t *head, tsd_init_block_t *block);
2727
void tsd_init_finish(tsd_init_head_t *head, tsd_init_block_t *block);
2828

2929
extern pthread_key_t tsd_tsd;
30+
extern pthread_key_t tsd_thread_initialized_tsd;
3031
extern tsd_init_head_t tsd_init_head;
3132
extern tsd_wrapper_t tsd_boot_wrapper;
3233
extern bool tsd_booted;
@@ -51,6 +52,12 @@ tsd_cleanup_wrapper(void *arg) {
5152
return;
5253
}
5354
}
55+
/*
56+
* Leave tsd_thread_initialized_tsd set. It has no destructor and
57+
* intentionally outlives the TSD wrapper, so later jemalloc calls can
58+
* distinguish this fully-torn-down thread from one that has never
59+
* initialized TSD.
60+
*/
5461
malloc_tsd_dalloc(wrapper);
5562
}
5663

@@ -63,6 +70,11 @@ tsd_wrapper_set(tsd_wrapper_t *wrapper) {
6370
malloc_write("<jemalloc>: Error setting TSD\n");
6471
abort();
6572
}
73+
if (pthread_setspecific(tsd_thread_initialized_tsd,
74+
(void *)&tsd_thread_initialized_tsd) != 0) {
75+
malloc_write("<jemalloc>: Error setting TSD\n");
76+
abort();
77+
}
6678
}
6779

6880
JEMALLOC_ALWAYS_INLINE tsd_wrapper_t *
@@ -116,6 +128,15 @@ tsd_boot0(void) {
116128
if (pthread_key_create(&tsd_tsd, tsd_cleanup_wrapper) != 0) {
117129
return true;
118130
}
131+
/*
132+
* This key has no destructor. It records that the current thread once
133+
* had jemalloc TSD, so a later NULL tsd_tsd value means teardown has
134+
* finished rather than initialization not having happened yet.
135+
*/
136+
if (pthread_key_create(&tsd_thread_initialized_tsd, NULL) != 0) {
137+
pthread_key_delete(tsd_tsd);
138+
return true;
139+
}
119140
tsd_booted = true;
120141
tsd_wrapper_set(&tsd_boot_wrapper);
121142
tsd_init_finish(&tsd_init_head, &block);
@@ -160,6 +181,12 @@ tsd_get_allocates(void) {
160181
return true;
161182
}
162183

184+
JEMALLOC_ALWAYS_INLINE bool
185+
tsd_teardown_done(void) {
186+
return tsd_booted && pthread_getspecific(tsd_tsd) == NULL
187+
&& pthread_getspecific(tsd_thread_initialized_tsd) != NULL;
188+
}
189+
163190
/* Get/set. */
164191
JEMALLOC_ALWAYS_INLINE tsd_t *
165192
tsd_get(bool init) {

include/jemalloc/internal/tsd_malloc_thread_cleanup.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ tsd_get_allocates(void) {
5050
return false;
5151
}
5252

53+
JEMALLOC_ALWAYS_INLINE bool
54+
tsd_teardown_done(void) {
55+
return false;
56+
}
57+
5358
/* Get/set. */
5459
JEMALLOC_ALWAYS_INLINE tsd_t *
5560
tsd_get(bool init) {

include/jemalloc/internal/tsd_tls.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ tsd_get_allocates(void) {
4343
return false;
4444
}
4545

46+
JEMALLOC_ALWAYS_INLINE bool
47+
tsd_teardown_done(void) {
48+
return false;
49+
}
50+
4651
/* Get/set. */
4752
JEMALLOC_ALWAYS_INLINE tsd_t *
4853
tsd_get(bool init) {

include/jemalloc/internal/tsd_win.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ tsd_get_allocates(void) {
146146
return true;
147147
}
148148

149+
JEMALLOC_ALWAYS_INLINE bool
150+
tsd_teardown_done(void) {
151+
return false;
152+
}
153+
149154
/* Get/set. */
150155
JEMALLOC_ALWAYS_INLINE tsd_t *
151156
tsd_get(bool init) {
@@ -220,6 +225,11 @@ tsd_get_allocates(void) {
220225
return false;
221226
}
222227

228+
JEMALLOC_ALWAYS_INLINE bool
229+
tsd_teardown_done(void) {
230+
return false;
231+
}
232+
223233
/* Get/set. */
224234
JEMALLOC_ALWAYS_INLINE tsd_t *
225235
tsd_get(bool init) {

scripts/README_GH_ACTIONS.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ The script can generate workflows for different platforms:
2727

2828
### Linux CI (`linux-ci.yml`)
2929
- **test-linux** (AMD64): `ubuntu-latest` (x86_64)
30-
- ~96 configurations covering GCC, Clang, various flags
30+
- ~98 configurations covering GCC, Clang, various flags
3131
- **test-linux-arm64** (ARM64): `ubuntu-24.04-arm` (aarch64)
3232
- ~14 configurations including large hugepage tests
3333
- **Note:** Free ARM64 runners (Public Preview) - may have longer queue times during peak hours
3434

35-
**Total:** 110 configurations
35+
**Total:** 112 configurations
3636

3737
### macOS CI (`macos-ci.yml`)
3838
- **test-macos** (Intel): `macos-15-intel` (x86_64)
@@ -161,7 +161,7 @@ The Windows workflow uses:
161161
### Linux Build Process
162162
- Ubuntu Latest for AMD64, Ubuntu 24.04 for ARM64
163163
- Installs 32-bit cross-compilation dependencies when needed
164-
- Most comprehensive test matrix (110 configurations)
164+
- Most comprehensive test matrix (112 configurations)
165165

166166
## Relationship to Travis CI
167167

@@ -178,4 +178,3 @@ To regenerate all workflows after modifying `gen_gh_actions.py`:
178178
```
179179

180180
**Note**: The generated files should not be edited by hand. All changes should be made to `gen_gh_actions.py` and then regenerated.
181-

scripts/gen_gh_actions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,18 @@ def generate_linux_job(arch):
273273
'CXX': 'g++',
274274
'CONFIGURE_FLAGS': '--enable-debug --enable-experimental-smallocx --enable-stats --enable-prof',
275275
'EXTRA_CFLAGS': '-Werror -Wno-array-bounds'
276+
},
277+
{
278+
'CC': 'gcc',
279+
'CXX': 'g++',
280+
'CONFIGURE_FLAGS': 'force_tls=0',
281+
'EXTRA_CFLAGS': '-Werror -Wno-array-bounds'
282+
},
283+
{
284+
'CC': 'gcc',
285+
'CXX': 'g++',
286+
'CONFIGURE_FLAGS': 'force_tls=0 --enable-debug',
287+
'EXTRA_CFLAGS': '-Werror -Wno-array-bounds'
276288
}
277289
]
278290

src/jemalloc.c

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,12 +1025,50 @@ isfree(tsd_t *tsd, void *ptr, size_t usize, tcache_t *tcache, bool slow_path) {
10251025
thread_dalloc_event(tsd, usize);
10261026
}
10271027

1028+
JEMALLOC_ALWAYS_INLINE bool
1029+
dealloc_no_tsd(void *ptr) {
1030+
/*
1031+
* On the generic pthread_getspecific() TSD path, a deallocation can run
1032+
* after the thread's TSD has been torn down. Using tsd_fetch_min() here
1033+
* would allocate and publish a fresh TSD wrapper mid-teardown,
1034+
* reincarnating the state teardown just released (the crashes this change
1035+
* fixes). Instead we hand the object straight back to its arena via
1036+
* idalloctm() with a NULL tsdn.
1037+
*
1038+
* This deliberately bypasses the per-thread bookkeeping that the normal
1039+
* ifree()/isfree() slow path performs, none of which is available or
1040+
* meaningful without a live TSD. The trade-offs, all acceptable for
1041+
* these rare teardown-time frees (the memory itself is still correctly
1042+
* returned to the arena):
1043+
* - Profiling: prof_free() is skipped, so a sampled object is not
1044+
* unregistered from its prof context; its bytes stay counted as live
1045+
* in prof stats and in any final heap/leak dump.
1046+
* - Junk filling: opt_junk_free is not applied to the freed region.
1047+
* - Sized dealloc: for sdallocx() the caller-supplied size is ignored
1048+
* (szind is looked up from the extent map), so the sized-dealloc
1049+
* safety check does not run.
1050+
* - Thread events: thread_dalloc_event() does not fire, so this free
1051+
* does not advance decay / tcache GC or dalloc stats.
1052+
*/
1053+
if (!tsd_teardown_done()) {
1054+
return false;
1055+
}
1056+
1057+
idalloctm(TSDN_NULL, ptr, /* tcache */ NULL, /* alloc_ctx */ NULL,
1058+
/* is_internal */ false, /* slow_path */ true);
1059+
return true;
1060+
}
1061+
10281062
JEMALLOC_NOINLINE
10291063
void
10301064
free_default(void *ptr) {
10311065
UTRACE(ptr, 0, 0);
10321066
if (likely(ptr != NULL)) {
10331067
int saved_errno = get_errno();
1068+
if (unlikely(dealloc_no_tsd(ptr))) {
1069+
set_errno(saved_errno);
1070+
return;
1071+
}
10341072
/*
10351073
* We avoid setting up tsd fully (e.g. tcache, arena binding)
10361074
* based on only free() calls -- other activities trigger the
@@ -1568,6 +1606,9 @@ do_realloc_nonnull_zero(void *ptr) {
15681606
return do_rallocx(ptr, 1, MALLOCX_TCACHE_NONE, true);
15691607
} else if (opt_zero_realloc_action == zero_realloc_action_free) {
15701608
UTRACE(ptr, 0, 0);
1609+
if (unlikely(dealloc_no_tsd(ptr))) {
1610+
return NULL;
1611+
}
15711612
tsd_t *tsd = tsd_fetch();
15721613
check_entry_exit_locking(tsd_tsdn(tsd));
15731614

@@ -1843,6 +1884,12 @@ je_dallocx(void *ptr, int flags) {
18431884
assert(ptr != NULL);
18441885
assert(malloc_initialized() || malloc_is_initializer());
18451886

1887+
UTRACE(ptr, 0, 0);
1888+
if (unlikely(dealloc_no_tsd(ptr))) {
1889+
LOG("core.dallocx.exit", "");
1890+
return;
1891+
}
1892+
18461893
tsd_t *tsd = tsd_fetch_min();
18471894
bool fast = tsd_fast(tsd);
18481895
check_entry_exit_locking(tsd_tsdn(tsd));
@@ -1851,7 +1898,6 @@ je_dallocx(void *ptr, int flags) {
18511898
tcache_t *tcache = tcache_get_from_ind(tsd, tcache_ind, !fast,
18521899
/* is_alloc */ false);
18531900

1854-
UTRACE(ptr, 0, 0);
18551901
if (likely(fast)) {
18561902
tsd_assert_fast(tsd);
18571903
ifree(tsd, ptr, tcache, false);
@@ -1879,6 +1925,12 @@ sdallocx_default(void *ptr, size_t size, int flags) {
18791925
assert(ptr != NULL);
18801926
assert(malloc_initialized() || malloc_is_initializer());
18811927

1928+
UTRACE(ptr, 0, 0);
1929+
if (unlikely(dealloc_no_tsd(ptr))) {
1930+
set_errno(saved_errno);
1931+
return;
1932+
}
1933+
18821934
tsd_t *tsd = tsd_fetch_min();
18831935
bool fast = tsd_fast(tsd);
18841936
size_t usize = inallocx(tsd_tsdn(tsd), size, flags);
@@ -1888,7 +1940,6 @@ sdallocx_default(void *ptr, size_t size, int flags) {
18881940
tcache_t *tcache = tcache_get_from_ind(tsd, tcache_ind, !fast,
18891941
/* is_alloc */ false);
18901942

1891-
UTRACE(ptr, 0, 0);
18921943
if (likely(fast)) {
18931944
tsd_assert_fast(tsd);
18941945
isfree(tsd, ptr, usize, tcache, false);

src/tsd.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ struct tsd_init_head_s {
5454
};
5555

5656
pthread_key_t tsd_tsd;
57+
pthread_key_t tsd_thread_initialized_tsd;
5758
tsd_init_head_t tsd_init_head = {
5859
ql_head_initializer(blocks), MALLOC_MUTEX_INITIALIZER};
5960

0 commit comments

Comments
 (0)