Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 90 additions & 48 deletions TSRM/TSRM.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
+----------------------------------------------------------------------+
*/

#include "TSRM.h"
#include "TSRM_atexit.h"

#ifdef ZTS

Expand All @@ -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;
Expand Down Expand Up @@ -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, ...);
Expand Down Expand Up @@ -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
Expand All @@ -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)
{/*{{{*/
Expand Down Expand Up @@ -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]);
}

Expand All @@ -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;

Expand All @@ -205,14 +209,16 @@ TSRM_API void tsrm_shutdown(void)
return;
}

tsrm_atexit_disarm();

for (int i=0; i<tsrm_tls_table_size; i++) {
tsrm_tls_entry *p = tsrm_tls_table[i], *next_p;

while (p) {
next_p = p->next;
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);
}
Expand All @@ -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"));
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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);
Expand All @@ -591,41 +606,55 @@ 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);
}/*}}}*/

/* deallocates all occurrences of a given id */
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);

Expand All @@ -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]);
}
Expand All @@ -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;
Expand Down Expand Up @@ -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
*/
Expand Down
5 changes: 4 additions & 1 deletion TSRM/TSRM.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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);
Expand Down
39 changes: 39 additions & 0 deletions TSRM/TSRM_atexit.h
Original file line number Diff line number Diff line change
@@ -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 <zeev@php.net> |
+----------------------------------------------------------------------+
*/

#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 */
Loading
Loading