diff --git a/TSRM/TSRM.c b/TSRM/TSRM.c index 39b1db9b712d..557bab93ecef 100644 --- a/TSRM/TSRM.c +++ b/TSRM/TSRM.c @@ -10,7 +10,7 @@ +----------------------------------------------------------------------+ */ -#include "TSRM.h" +#include "TSRM_atexit.h" #ifdef ZTS @@ -24,18 +24,9 @@ # define TSRM_ASSERT(c) #endif -typedef struct _tsrm_tls_entry tsrm_tls_entry; - /* TSRMLS_CACHE_DEFINE; is already done in Zend, this is being always compiled statically. */ TSRMLS_CACHE_EXTERN(); -struct _tsrm_tls_entry { - void **storage; - int count; - THREAD_T thread_id; - tsrm_tls_entry *next; -}; - typedef struct { size_t size; ts_allocate_ctor ctor; @@ -68,6 +59,7 @@ static MUTEX_T tsrm_env_mutex; /* tsrm environ mutex */ static tsrm_thread_begin_func_t tsrm_new_thread_begin_handler = NULL; static tsrm_thread_end_func_t tsrm_new_thread_end_handler = NULL; static tsrm_shutdown_func_t tsrm_shutdown_handler = NULL; +static tsrm_shutdown_func_t tsrm_thread_free_handler = NULL; /* Debug support */ int tsrm_error(int level, const char *format, ...); @@ -105,7 +97,7 @@ static FILE *tsrm_error_file; #endif #ifdef TSRM_WIN32 -static DWORD tls_key; +static DWORD tls_key = TLS_OUT_OF_INDEXES; # define tsrm_tls_set(what) TlsSetValue(tls_key, (void*)(what)) # define tsrm_tls_get() TlsGetValue(tls_key) #else @@ -117,6 +109,16 @@ static pthread_key_t tls_key; TSRM_TLS bool in_main_thread = false; TSRM_TLS bool is_thread_shutdown = false; +void tsrm_free_thread_at_exit(tsrm_tls_entry *thread_resources) +{ + if (!thread_resources || thread_resources != TSRMLS_CACHE || in_main_thread || !tsrm_tls_table) { + return; + } + + tsrm_tls_set(thread_resources); + ts_free_thread(); +} + /* Startup TSRM (call once for the entire process) */ TSRM_API bool tsrm_startup(int expected_threads, int expected_resources, int debug_level, const char *debug_filename) {/*{{{*/ @@ -160,22 +162,22 @@ TSRM_API bool tsrm_startup(int expected_threads, int expected_resources, int deb tsrm_reserved_front = 0; tsrm_env_mutex = tsrm_mutex_alloc(); + tsrm_atexit(); return 1; }/*}}}*/ -static void ts_free_resources(tsrm_tls_entry *thread_resources) +static void ts_free_resources( + tsrm_tls_entry *thread_resources, bool destroy_tls_resources, bool run_dtors) { - bool own_thread = thread_resources->thread_id == tsrm_thread_id(); - /* Need to destroy in reverse order to respect dependencies. */ for (int i = thread_resources->count - 1; i >= 0; i--) { if (!resource_types_table[i].done) { - /* A __thread block of a foreign thread is inaccessible. */ - if (resource_types_table[i].tls_addr && !own_thread) { + /* Native TLS may only be accessed by its owning thread. */ + if (resource_types_table[i].tls_addr && !destroy_tls_resources) { continue; } - if (resource_types_table[i].dtor) { + if (run_dtors && resource_types_table[i].dtor) { resource_types_table[i].dtor(thread_resources->storage[i]); } @@ -189,14 +191,16 @@ static void ts_free_resources(tsrm_tls_entry *thread_resources) } /* Shutdown TSRM (call once for the entire process). Tears down every thread left - * in the table. For resources allocated with ts_allocate_tls_id(), only the dtor - * of the calling thread is invoked. */ + * in the table. Native TLS dtors only run for the calling thread. */ TSRM_API void tsrm_shutdown(void) {/*{{{*/ + tsrm_tls_entry *current_thread_resources; + if (is_thread_shutdown) { /* shutdown must only occur once */ return; } + current_thread_resources = tsrm_tls_get(); is_thread_shutdown = true; @@ -205,6 +209,8 @@ TSRM_API void tsrm_shutdown(void) return; } + tsrm_atexit_disarm(); + for (int i=0; inext; if (resource_types_table) { /* This call will already free p->storage for us */ - ts_free_resources(p); + ts_free_resources(p, p == current_thread_resources, true); } else { free(p->storage); } @@ -221,7 +227,9 @@ TSRM_API void tsrm_shutdown(void) } } free(tsrm_tls_table); + tsrm_tls_table = NULL; free(resource_types_table); + resource_types_table = NULL; tsrm_mutex_free(tsmm_mutex); tsrm_mutex_free(tsrm_env_mutex); TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Shutdown TSRM")); @@ -230,16 +238,19 @@ TSRM_API void tsrm_shutdown(void) } #ifdef TSRM_WIN32 TlsFree(tls_key); + tls_key = TLS_OUT_OF_INDEXES; #else pthread_setspecific(tls_key, 0); pthread_key_delete(tls_key); #endif + TSRMLS_CACHE = NULL; if (tsrm_shutdown_handler) { tsrm_shutdown_handler(); } tsrm_new_thread_begin_handler = NULL; tsrm_new_thread_end_handler = NULL; tsrm_shutdown_handler = NULL; + tsrm_thread_free_handler = NULL; tsrm_reserved_pos = 0; tsrm_reserved_size = 0; @@ -464,6 +475,9 @@ static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_ (*thread_resources_ptr)->count = id_count; (*thread_resources_ptr)->thread_id = thread_id; (*thread_resources_ptr)->next = NULL; +#ifdef TSRM_WIN32 + (*thread_resources_ptr)->thread_exit_data = NULL; +#endif /* Set thread local storage to this new thread resources structure */ set_thread_local_storage_resource_to(*thread_resources_ptr); @@ -491,6 +505,11 @@ static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_ if (tsrm_new_thread_end_handler) { tsrm_new_thread_end_handler(thread_id); } + + /* A thread exiting from a resource constructor cannot safely clean up an + * entry that is still being built while tsmm_mutex is held. */ + tsrm_atexit_set_data(*thread_resources_ptr); + is_thread_shutdown = false; }/*}}}*/ /* fetches the requested resource for the current thread */ @@ -551,24 +570,20 @@ TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id) * goes away, but its resources are never cleaned up, and then that thread ID is reused. * Since we don't always have a way to know when a thread goes away, we can't clean up * the thread's resources before the new thread spawns. - * To solve this issue, we'll free up the old thread resources gracefully (gracefully - * because there might still be resources open like database connection which need to - * be shut down cleanly). After freeing up, we'll create the new resources for this thread - * as if the stale resources never existed in the first place. From that point forward, - * it is as if that situation never occurred. + * The old native TLS is no longer accessible, so its destructors cannot run safely. + * Release the directly owned allocations and create fresh resources for the new thread. * The fact that this situation happens isn't that bad because a child process containing * threads will eventually be respawned anyway by the SAPI, so the stale threads won't last * forever. */ TSRM_ASSERT(thread_resources->thread_id == thread_id); if (thread_id == tsrm_thread_id() && !tsrm_tls_get()) { tsrm_tls_entry *next = thread_resources->next; - /* In case that extensions don't use the pointer passed from the dtor, but incorrectly - * use the global pointer, we need to setup the global pointer temporarily here. */ - set_thread_local_storage_resource_to(thread_resources); - /* Dead thread with a recycled id: its __thread blocks are gone, and this - * thread's blocks were never constructed, so keep tls dtors from running. */ - thread_resources->thread_id = 0; - ts_free_resources(thread_resources); + /* Keep signal handlers away from both the stale entry and the replacement + * until all of the replacement's resources have been constructed. */ + is_thread_shutdown = true; + /* The dead thread's native TLS is gone, and its remaining resource dtors + * may depend on those globals. Only release directly owned allocations. */ + ts_free_resources(thread_resources, false, false); free((char *) thread_resources - tsrm_reserved_front); /* Allocate a new resource at the same point in the linked list, and relink the next pointer */ allocate_new_resource(last_thread_resources, thread_id); @@ -591,34 +606,47 @@ TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id) /* frees all resources allocated for the current thread */ void ts_free_thread(void) {/*{{{*/ - tsrm_tls_entry *thread_resources; - THREAD_T thread_id = tsrm_thread_id(); + tsrm_tls_entry *thread_resources = tsrm_tls_get(); + tsrm_tls_entry *p; int hash_value; tsrm_tls_entry *last=NULL; TSRM_ASSERT(!in_main_thread); + if (!thread_resources) { + return; + } + + is_thread_shutdown = true; + /* Release resources that depend on TSRM before taking its lock. */ + if (tsrm_thread_free_handler) { + tsrm_thread_free_handler(); + } tsrm_mutex_lock(tsmm_mutex); - hash_value = THREAD_HASH_OF(thread_id, tsrm_tls_table_size); - thread_resources = tsrm_tls_table[hash_value]; + hash_value = THREAD_HASH_OF(thread_resources->thread_id, tsrm_tls_table_size); + p = tsrm_tls_table[hash_value]; - while (thread_resources) { - if (thread_resources->thread_id == thread_id) { - ts_free_resources(thread_resources); + while (p) { + if (p == thread_resources) { + ts_free_resources(thread_resources, true, true); if (last) { - last->next = thread_resources->next; + last->next = p->next; } else { - tsrm_tls_table[hash_value] = thread_resources->next; + tsrm_tls_table[hash_value] = p->next; } tsrm_tls_set(0); + tsrm_atexit_set_data(NULL); + TSRMLS_CACHE = NULL; free((char *) thread_resources - tsrm_reserved_front); - break; - } - if (thread_resources->next) { - last = thread_resources; + tsrm_mutex_unlock(tsmm_mutex); + return; } - thread_resources = thread_resources->next; + last = p; + p = p->next; } + tsrm_tls_set(0); + tsrm_atexit_set_data(NULL); + TSRMLS_CACHE = NULL; tsrm_mutex_unlock(tsmm_mutex); }/*}}}*/ @@ -626,6 +654,7 @@ void ts_free_thread(void) void ts_free_id(ts_rsrc_id id) {/*{{{*/ int rsrc_id = TSRM_UNSHUFFLE_RSRC_ID(id); + tsrm_tls_entry *current_thread_resources = tsrm_tls_get(); tsrm_mutex_lock(tsmm_mutex); @@ -637,7 +666,9 @@ void ts_free_id(ts_rsrc_id id) while (p) { if (p->count > rsrc_id && p->storage[rsrc_id]) { - if (resource_types_table) { + /* Native TLS may only be accessed by its owning thread. */ + if (resource_types_table + && (!resource_types_table[rsrc_id].tls_addr || p == current_thread_resources)) { if (resource_types_table[rsrc_id].dtor) { resource_types_table[rsrc_id].dtor(p->storage[rsrc_id]); } @@ -661,15 +692,20 @@ void ts_free_id(ts_rsrc_id id) TSRM_API void ts_apply_for_id(ts_rsrc_id id, void (*cb)(void *)) { int rsrc_id = TSRM_UNSHUFFLE_RSRC_ID(id); + tsrm_tls_entry *current_thread_resources = tsrm_tls_get(); tsrm_mutex_lock(tsmm_mutex); if (tsrm_tls_table && resource_types_table) { + bool tls_backed = resource_types_table[rsrc_id].tls_addr != NULL; + for (int i = 0; i < tsrm_tls_table_size; i++) { tsrm_tls_entry *p = tsrm_tls_table[i]; while (p) { - if (p->count > rsrc_id && p->storage[rsrc_id]) { + /* Native TLS may only be accessed by its owning thread. */ + if (p->count > rsrc_id && p->storage[rsrc_id] + && (!tls_backed || p == current_thread_resources)) { cb(p->storage[rsrc_id]); } p = p->next; @@ -802,6 +838,12 @@ TSRM_API void *tsrm_set_shutdown_handler(tsrm_shutdown_func_t shutdown_handler) }/*}}}*/ +void tsrm_set_thread_free_handler(tsrm_shutdown_func_t thread_free_handler) +{ + tsrm_thread_free_handler = thread_free_handler; +} + + /* * Debug support */ diff --git a/TSRM/TSRM.h b/TSRM/TSRM.h index 5fe5fb1cf354..390b0b09b6bd 100644 --- a/TSRM/TSRM.h +++ b/TSRM/TSRM.h @@ -101,7 +101,8 @@ TSRM_API void tsrm_reserve_fast_front(size_t size); TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, ptrdiff_t fixed_offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor); /* Resource whose per-thread storage is a native __thread block. - * Must be called at startup before any other thread exists. */ + * Must be called at startup before any other thread exists. Its destructor may + * only run on the owning thread. */ TSRM_API ts_rsrc_id ts_allocate_tls_id(ts_rsrc_id *rsrc_id, void *(*tls_addr)(void), size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor); /* fetches the requested resource for the current thread */ @@ -144,6 +145,8 @@ TSRM_API int tsrm_sigmask(int how, const sigset_t *set, sigset_t *oldset); TSRM_API void *tsrm_set_new_thread_begin_handler(tsrm_thread_begin_func_t new_thread_begin_handler); TSRM_API void *tsrm_set_new_thread_end_handler(tsrm_thread_end_func_t new_thread_end_handler); TSRM_API void *tsrm_set_shutdown_handler(tsrm_shutdown_func_t shutdown_handler); +/* Internal Zend hook, run before any of the thread's resources are destroyed. */ +void tsrm_set_thread_free_handler(tsrm_shutdown_func_t thread_free_handler); TSRM_API void *tsrm_get_ls_cache(void); TSRM_API size_t tsrm_get_ls_cache_tcb_offset(void); diff --git a/TSRM/TSRM_atexit.h b/TSRM/TSRM_atexit.h new file mode 100644 index 000000000000..babf62927438 --- /dev/null +++ b/TSRM/TSRM_atexit.h @@ -0,0 +1,39 @@ +/* + +----------------------------------------------------------------------+ + | Thread Safe Resource Manager | + +----------------------------------------------------------------------+ + | Copyright (c) 1999-2011, Andi Gutmans, Sascha Schumann, Zeev Suraski | + | This source file is subject to the TSRM license, that is bundled | + | with this package in the file LICENSE | + +----------------------------------------------------------------------+ + | Authors: Zeev Suraski | + +----------------------------------------------------------------------+ +*/ + +#ifndef TSRM_ATEXIT_H +#define TSRM_ATEXIT_H + +#include "TSRM.h" + +#ifdef ZTS + +typedef struct _tsrm_tls_entry tsrm_tls_entry; + +struct _tsrm_tls_entry { + void **storage; + int count; + THREAD_T thread_id; + tsrm_tls_entry *next; +#ifdef TSRM_WIN32 + void *thread_exit_data; +#endif +}; + +void tsrm_atexit(void); +void tsrm_atexit_set_data(tsrm_tls_entry *thread_resources); +void tsrm_atexit_disarm(void); +void tsrm_free_thread_at_exit(tsrm_tls_entry *thread_resources); + +#endif /* ZTS */ + +#endif /* TSRM_ATEXIT_H */ diff --git a/TSRM/TSRM_atexit_darwin.c b/TSRM/TSRM_atexit_darwin.c new file mode 100644 index 000000000000..7bd6f1404d72 --- /dev/null +++ b/TSRM/TSRM_atexit_darwin.c @@ -0,0 +1,73 @@ +/* + +----------------------------------------------------------------------+ + | Thread Safe Resource Manager | + +----------------------------------------------------------------------+ + | Copyright (c) 1999-2011, Andi Gutmans, Sascha Schumann, Zeev Suraski | + | This source file is subject to the TSRM license, that is bundled | + | with this package in the file LICENSE | + +----------------------------------------------------------------------+ + | Authors: Zeev Suraski | + +----------------------------------------------------------------------+ +*/ + +#include "TSRM_atexit.h" + +#if defined(ZTS) && !defined(TSRM_WIN32) && defined(__APPLE__) + +#include +#include + +static bool tsrm_thread_exit_armed = false; +static pthread_key_t tsrm_exit_key; +static TSRM_TLS bool tsrm_thread_exit_registered = false; + +static void tsrm_thread_exit_handler(void *arg) +{ + tsrm_thread_exit_registered = false; + if (!tsrm_thread_exit_armed) { + return; + } + tsrm_free_thread_at_exit(pthread_getspecific(tsrm_exit_key)); +} + +void tsrm_atexit(void) +{ + int error = pthread_key_create(&tsrm_exit_key, NULL); + tsrm_thread_exit_armed = error == 0; + if (!tsrm_thread_exit_armed) { + fprintf(stderr, "TSRM: pthread_key_create() failed (error %d); automatic thread cleanup is disabled\n", error); + } +} + +void tsrm_atexit_set_data(tsrm_tls_entry *thread_resources) +{ + if (!tsrm_thread_exit_armed) { + return; + } + + if (pthread_setspecific(tsrm_exit_key, thread_resources) != 0) { + return; + } + if (thread_resources && !tsrm_thread_exit_registered) { + tsrm_thread_exit_registered = true; + _tlv_atexit(tsrm_thread_exit_handler, NULL); + } +} + +void tsrm_atexit_disarm(void) +{ + if (!tsrm_thread_exit_armed) { + return; + } + tsrm_thread_exit_armed = false; + pthread_key_delete(tsrm_exit_key); +} + +#ifdef __GNUC__ +static void __attribute__((destructor)) tsrm_thread_exit_unload(void) +{ + tsrm_atexit_disarm(); +} +#endif + +#endif /* ZTS && !TSRM_WIN32 && __APPLE__ */ diff --git a/TSRM/TSRM_atexit_posix.c b/TSRM/TSRM_atexit_posix.c new file mode 100644 index 000000000000..d62d3c788115 --- /dev/null +++ b/TSRM/TSRM_atexit_posix.c @@ -0,0 +1,63 @@ +/* + +----------------------------------------------------------------------+ + | Thread Safe Resource Manager | + +----------------------------------------------------------------------+ + | Copyright (c) 1999-2011, Andi Gutmans, Sascha Schumann, Zeev Suraski | + | This source file is subject to the TSRM license, that is bundled | + | with this package in the file LICENSE | + +----------------------------------------------------------------------+ + | Authors: Zeev Suraski | + +----------------------------------------------------------------------+ +*/ + +#include "TSRM_atexit.h" + +#if defined(ZTS) && !defined(TSRM_WIN32) && !defined(__APPLE__) + +#include + +static bool tsrm_thread_exit_armed = false; +static pthread_key_t tsrm_exit_key; + +static void tsrm_thread_exit_handler(void *arg) +{ + if (tsrm_thread_exit_armed) { + tsrm_free_thread_at_exit(arg); + } +} + +void tsrm_atexit(void) +{ + int error = pthread_key_create(&tsrm_exit_key, tsrm_thread_exit_handler); + tsrm_thread_exit_armed = error == 0; + if (!tsrm_thread_exit_armed) { + fprintf(stderr, "TSRM: pthread_key_create() failed (error %d); automatic thread cleanup is disabled\n", error); + } +} + +void tsrm_atexit_set_data(tsrm_tls_entry *thread_resources) +{ + if (!tsrm_thread_exit_armed) { + return; + } + + pthread_setspecific(tsrm_exit_key, thread_resources); +} + +void tsrm_atexit_disarm(void) +{ + if (!tsrm_thread_exit_armed) { + return; + } + tsrm_thread_exit_armed = false; + pthread_key_delete(tsrm_exit_key); +} + +#ifdef __GNUC__ +static void __attribute__((destructor)) tsrm_thread_exit_unload(void) +{ + tsrm_atexit_disarm(); +} +#endif + +#endif /* ZTS && !TSRM_WIN32 && !__APPLE__ */ diff --git a/TSRM/TSRM_atexit_win.c b/TSRM/TSRM_atexit_win.c new file mode 100644 index 000000000000..4ba6a56bc0ca --- /dev/null +++ b/TSRM/TSRM_atexit_win.c @@ -0,0 +1,120 @@ +/* + +----------------------------------------------------------------------+ + | Thread Safe Resource Manager | + +----------------------------------------------------------------------+ + | Copyright (c) 1999-2011, Andi Gutmans, Sascha Schumann, Zeev Suraski | + | This source file is subject to the TSRM license, that is bundled | + | with this package in the file LICENSE | + +----------------------------------------------------------------------+ + | Authors: Zeev Suraski | + +----------------------------------------------------------------------+ +*/ + +#include "TSRM_atexit.h" + +#if defined(ZTS) && defined(TSRM_WIN32) + +#include +#include + +TSRMLS_CACHE_EXTERN(); + +static bool tsrm_thread_exit_armed = false; +static DWORD tsrm_exit_key = FLS_OUT_OF_INDEXES; +static BOOLEAN (NTAPI *tsrm_dll_shutdown_in_progress)(void); + +/* FLS is per-fiber, but TSRM resources are per-thread. Another fiber can free + * and reallocate the resources while this fiber retains its FLS value. Keep a + * separate record so callbacks can distinguish stale FLS values even if the + * resource allocation reuses the same address. */ +typedef struct { + tsrm_tls_entry *thread_resources; +} tsrm_thread_exit_data; + +static void WINAPI tsrm_thread_exit_handler(void *arg) +{ + tsrm_thread_exit_data *data = arg; + tsrm_tls_entry *thread_resources; + + if (tsrm_dll_shutdown_in_progress()) { + return; + } + /* FLS callbacks also run when another fiber is deleted. TSRM storage is + * thread-wide, so only tear it down for the exiting execution context. */ + if (!tsrm_thread_exit_armed) { + free(data); + return; + } + if (FlsGetValue(tsrm_exit_key) != data) { + free(data); + return; + } + FlsSetValue(tsrm_exit_key, NULL); + thread_resources = data->thread_resources; + if (thread_resources == TSRMLS_CACHE && thread_resources->thread_exit_data == data) { + tsrm_free_thread_at_exit(thread_resources); + } + free(data); +} + +void tsrm_atexit(void) +{ + tsrm_dll_shutdown_in_progress = (BOOLEAN (NTAPI *)(void)) (void *) GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlDllShutdownInProgress"); + /* Use FlsAlloc() because TlsAlloc() does not provide a thread-exit callback + * parameter. The callback calls ts_free_thread() if the SAPI has not done so + * explicitly, cleaning up EG(persistent_list) and other TSRM resources on the + * owning thread before its native TLS is released. */ + tsrm_exit_key = tsrm_dll_shutdown_in_progress ? FlsAlloc(tsrm_thread_exit_handler) : FLS_OUT_OF_INDEXES; + tsrm_thread_exit_armed = tsrm_exit_key != FLS_OUT_OF_INDEXES; + if (!tsrm_thread_exit_armed) { + fprintf(stderr, "TSRM: %s failed (error %lu); automatic thread cleanup is disabled\n", + tsrm_dll_shutdown_in_progress ? "FlsAlloc()" : "RtlDllShutdownInProgress lookup", + (unsigned long) GetLastError()); + } +} + +void tsrm_atexit_set_data(tsrm_tls_entry *thread_resources) +{ + tsrm_thread_exit_data *data; + + if (!tsrm_thread_exit_armed) { + return; + } + + data = FlsGetValue(tsrm_exit_key); + if (!thread_resources) { + if (data && FlsSetValue(tsrm_exit_key, NULL)) { + free(data); + } + return; + } + + if (!data) { + data = malloc(sizeof(tsrm_thread_exit_data)); + if (!data) { + return; + } + data->thread_resources = thread_resources; + if (!FlsSetValue(tsrm_exit_key, data)) { + free(data); + return; + } + thread_resources->thread_exit_data = data; + return; + } + data->thread_resources = thread_resources; + thread_resources->thread_exit_data = data; +} + +void tsrm_atexit_disarm(void) +{ + if (!tsrm_thread_exit_armed) { + return; + } + tsrm_thread_exit_armed = false; + DWORD key = tsrm_exit_key; + tsrm_exit_key = FLS_OUT_OF_INDEXES; + FlsFree(key); +} + +#endif /* ZTS && TSRM_WIN32 */ diff --git a/TSRM/config.w32 b/TSRM/config.w32 index fa7145ae4dc8..ecf7ed548114 100644 --- a/TSRM/config.w32 +++ b/TSRM/config.w32 @@ -1,4 +1,4 @@ // vim:ft=javascript -ADD_SOURCES("TSRM", "TSRM.c tsrm_win32.c"); +ADD_SOURCES("TSRM", "TSRM.c TSRM_atexit_win.c TSRM_atexit_darwin.c TSRM_atexit_posix.c tsrm_win32.c"); ADD_FLAG("CFLAGS_BD_TSRM", "/D ZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 4c5d74b49e9c..51f8e399b830 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -91,3 +91,11 @@ PHP 8.7 INTERNALS UPGRADE NOTES . The force_http_10 method has been removed. . The get_fd method has been removed. . The get_target_uid and get_target_gid methods have been removed. + +- Per-thread TSRM resources are now released best-effort at normal thread exit, + because native TLS resource destructors can only run on their owning thread and + tsrm_shutdown() cannot release them afterwards. Prefer explicitly calling ts_free_thread(). + +- tsrm_shutdown() and the bundled library-unload hooks automatically disarm + thread-exit cleanup. Embedders must stop and join all PHP worker threads + before calling tsrm_shutdown() or unloading PHP. diff --git a/Zend/zend.c b/Zend/zend.c index 2df65654a4ef..1e9a59242346 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -61,7 +61,6 @@ static HashTable *global_function_table = NULL; static HashTable *global_class_table = NULL; static HashTable *global_constants_table = NULL; static HashTable *global_auto_globals_table = NULL; -static HashTable *global_persistent_list = NULL; TSRMLS_MAIN_CACHE_DEFINE() # define GLOBAL_FUNCTION_TABLE global_function_table # define GLOBAL_CLASS_TABLE global_class_table @@ -857,13 +856,17 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{ } /* }}} */ -static void executor_globals_persistent_list_dtor(void *storage) +static void zend_thread_free_handler(void) { - zend_executor_globals *executor_globals = storage; + volatile bool completed = false; - if (&executor_globals->persistent_list != global_persistent_list) { - zend_destroy_rsrc_list(&executor_globals->persistent_list); - } + do { + zend_try { + zend_destroy_rsrc_list(&EG(persistent_list)); + completed = true; + } zend_end_try(); + } while (!completed); + zend_init_rsrc_plist(); } static void executor_globals_dtor(zend_executor_globals *executor_globals) /* {{{ */ @@ -1085,6 +1088,7 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ #ifdef ZTS tsrm_set_new_thread_end_handler(zend_new_thread_end_handler); + tsrm_set_thread_free_handler(zend_thread_free_handler); tsrm_set_shutdown_handler(zend_interned_strings_dtor); #endif @@ -1155,7 +1159,6 @@ zend_result zend_post_startup(void) /* {{{ */ EG(zend_constants) = NULL; executor_globals_ctor(executor_globals); - global_persistent_list = &EG(persistent_list); zend_copy_ini_directives(); #else global_map_ptr_last = CG(map_ptr_last); @@ -1172,12 +1175,12 @@ zend_result zend_post_startup(void) /* {{{ */ void zend_shutdown(void) /* {{{ */ { +#ifdef ZTS + tsrm_set_thread_free_handler(NULL); +#endif zend_vm_dtor(); zend_destroy_rsrc_list(&EG(persistent_list)); -#ifdef ZTS - ts_apply_for_id(executor_globals_id, executor_globals_persistent_list_dtor); -#endif zend_destroy_modules(); virtual_cwd_deactivate(); diff --git a/configure.ac b/configure.ac index b5dcf0da17f8..d0321145e492 100644 --- a/configure.ac +++ b/configure.ac @@ -1658,7 +1658,12 @@ PHP_INSTALL_HEADERS([Zend/Optimizer], m4_normalize([ zend_worklist.h ])) -PHP_ADD_SOURCES([TSRM], [TSRM.c], [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1]) +PHP_ADD_SOURCES([TSRM], m4_normalize([ + TSRM.c + TSRM_atexit_win.c + TSRM_atexit_darwin.c + TSRM_atexit_posix.c + ]), [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1]) PHP_ADD_SOURCES([main], m4_normalize([ explicit_bzero.c diff --git a/win32/dllmain.c b/win32/dllmain.c index 168bef2baa22..b0567e29fa15 100644 --- a/win32/dllmain.c +++ b/win32/dllmain.c @@ -15,6 +15,7 @@ #include #include +#include #ifdef HAVE_LIBXML #include @@ -31,6 +32,12 @@ BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID dummy) { BOOL ret = TRUE; +#ifdef ZTS + if (reason == DLL_PROCESS_DETACH && dummy == NULL) { + tsrm_atexit_disarm(); + } +#endif + #ifdef HAVE_LIBXML /* This imply that only LIBXML_STATIC_FOR_DLL is supported ATM. If that changes, this place will need some rework.