@@ -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+
10281062JEMALLOC_NOINLINE
10291063void
10301064free_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);
0 commit comments