| /* |
| * Copyright (C) 2013 Martin Willi |
| * Copyright (C) 2013 revosec AG |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or (at your |
| * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * for more details. |
| */ |
| |
| #include "thread.h" |
| |
| #include <utils/debug.h> |
| #include <threading/spinlock.h> |
| #include <threading/thread.h> |
| #include <collections/hashtable.h> |
| #include <collections/array.h> |
| |
| |
| typedef struct private_thread_t private_thread_t; |
| |
| struct private_thread_t { |
| |
| /** |
| * Public interface. |
| */ |
| thread_t public; |
| |
| /** |
| * GetCurrentThreadId() of thread |
| */ |
| DWORD id; |
| |
| /** |
| * Printable thread id returned by thread_current_id() |
| */ |
| u_int tid; |
| |
| /** |
| * Windows thread handle |
| */ |
| HANDLE handle; |
| |
| /** |
| * Main function of this thread (NULL for the main thread). |
| */ |
| thread_main_t main; |
| |
| /** |
| * Argument for the main function. |
| */ |
| void *arg; |
| |
| /** |
| * Thread return value |
| */ |
| void *ret; |
| |
| /** |
| * Stack of cleanup handlers, as cleanup_t |
| */ |
| array_t *cleanup; |
| |
| /** |
| * Thread specific values for this thread |
| */ |
| hashtable_t *tls; |
| |
| /** |
| * Thread terminated? |
| */ |
| bool terminated; |
| |
| /** |
| * Thread detached? |
| */ |
| bool detached; |
| |
| /** |
| * Is thread in cancellable state |
| */ |
| bool cancelability; |
| |
| /** |
| * Has the thread been canceled by thread->cancel()? |
| */ |
| bool canceled; |
| |
| /** |
| * Did we schedule an APC to docancel()? |
| */ |
| bool cancel_pending; |
| |
| /** |
| * Active condition variable thread is waiting in, if any |
| */ |
| CONDITION_VARIABLE *condvar; |
| }; |
| |
| /** |
| * Global list of threads, GetCurrentThreadId() => private_thread_t |
| */ |
| static hashtable_t *threads; |
| |
| /** |
| * Lock for threads table |
| */ |
| static spinlock_t *threads_lock; |
| |
| /** |
| * Counter to assign printable thread IDs |
| */ |
| static u_int threads_ids = 0; |
| |
| /** |
| * Forward declaration |
| */ |
| static private_thread_t *create_internal(DWORD id); |
| |
| /** |
| * Set leak detective state |
| */ |
| static inline bool set_leak_detective(bool state) |
| { |
| #ifdef LEAK_DETECTIVE |
| if (lib && lib->leak_detective) |
| { |
| return lib->leak_detective->set_state(lib->leak_detective, state); |
| } |
| #endif |
| return FALSE; |
| } |
| |
| /** |
| * Store thread in index |
| */ |
| static void put_thread(private_thread_t *this) |
| { |
| bool old; |
| |
| old = set_leak_detective(FALSE); |
| threads_lock->lock(threads_lock); |
| |
| threads->put(threads, (void*)(uintptr_t)this->id, this); |
| |
| threads_lock->unlock(threads_lock); |
| set_leak_detective(old); |
| } |
| |
| /** |
| * Remove thread from index |
| */ |
| static void remove_thread(private_thread_t *this) |
| { |
| bool old; |
| |
| old = set_leak_detective(FALSE); |
| threads_lock->lock(threads_lock); |
| |
| threads->remove(threads, (void*)(uintptr_t)this->id); |
| |
| threads_lock->unlock(threads_lock); |
| set_leak_detective(old); |
| } |
| |
| /** |
| * Get thread data for calling thread |
| */ |
| static private_thread_t *get_current_thread() |
| { |
| private_thread_t *this; |
| |
| threads_lock->lock(threads_lock); |
| |
| this = threads->get(threads, (void*)(uintptr_t)GetCurrentThreadId()); |
| |
| threads_lock->unlock(threads_lock); |
| |
| if (!this) |
| { |
| this = create_internal(GetCurrentThreadId()); |
| put_thread(this); |
| } |
| |
| return this; |
| } |
| |
| /** |
| * See header. |
| */ |
| void* thread_tls_put(void *key, void *value) |
| { |
| private_thread_t *thread; |
| bool old; |
| |
| thread = get_current_thread(); |
| |
| old = set_leak_detective(FALSE); |
| value = thread->tls->put(thread->tls, key, value); |
| set_leak_detective(old); |
| |
| return value; |
| } |
| |
| /** |
| * See header. |
| */ |
| void* thread_tls_get(void *key) |
| { |
| private_thread_t *thread; |
| void *value; |
| bool old; |
| |
| thread = get_current_thread(); |
| |
| old = set_leak_detective(FALSE); |
| value = thread->tls->get(thread->tls, key); |
| set_leak_detective(old); |
| |
| return value; |
| } |
| |
| /** |
| * See header. |
| */ |
| void* thread_tls_remove(void *key) |
| { |
| private_thread_t *thread; |
| void *value; |
| bool old; |
| |
| thread = get_current_thread(); |
| |
| old = set_leak_detective(FALSE); |
| threads_lock->lock(threads_lock); |
| value = thread->tls->remove(thread->tls, key); |
| threads_lock->unlock(threads_lock); |
| set_leak_detective(old); |
| |
| return value; |
| } |
| |
| /** |
| * Thread cleanup data |
| */ |
| typedef struct { |
| /** Cleanup callback function */ |
| thread_cleanup_t cb; |
| /** Argument provided to the cleanup function */ |
| void *arg; |
| } cleanup_t; |
| |
| /** |
| * Invoke pushed/tls cleanup handlers |
| */ |
| static void docleanup(private_thread_t *this) |
| { |
| enumerator_t *enumerator; |
| cleanup_t cleanup, *tls; |
| bool old; |
| |
| old = set_leak_detective(FALSE); |
| |
| while (array_remove(this->cleanup, -1, &cleanup)) |
| { |
| set_leak_detective(old); |
| cleanup.cb(cleanup.arg); |
| set_leak_detective(FALSE); |
| } |
| |
| threads_lock->lock(threads_lock); |
| enumerator = this->tls->create_enumerator(this->tls); |
| while (enumerator->enumerate(enumerator, NULL, &tls)) |
| { |
| this->tls->remove_at(this->tls, enumerator); |
| |
| set_leak_detective(old); |
| thread_tls_cleanup(tls); |
| set_leak_detective(FALSE); |
| } |
| enumerator->destroy(enumerator); |
| threads_lock->unlock(threads_lock); |
| |
| set_leak_detective(old); |
| } |
| |
| /** |
| * Clean up and destroy a thread |
| */ |
| static void destroy(private_thread_t *this) |
| { |
| bool old; |
| |
| docleanup(this); |
| |
| old = set_leak_detective(FALSE); |
| |
| array_destroy(this->cleanup); |
| this->tls->destroy(this->tls); |
| if (this->handle) |
| { |
| CloseHandle(this->handle); |
| } |
| free(this); |
| |
| set_leak_detective(old); |
| } |
| |
| /** |
| * End a thread, destroy when detached |
| */ |
| static void end_thread(private_thread_t *this) |
| { |
| if (this->detached) |
| { |
| remove_thread(this); |
| destroy(this); |
| } |
| else |
| { |
| this->terminated = TRUE; |
| docleanup(this); |
| } |
| } |
| |
| /** |
| * See header. |
| */ |
| void thread_set_active_condvar(CONDITION_VARIABLE *condvar) |
| { |
| private_thread_t *thread; |
| |
| thread = get_current_thread(); |
| |
| threads_lock->lock(threads_lock); |
| thread->condvar = condvar; |
| threads_lock->unlock(threads_lock); |
| |
| /* this is a cancellation point, as condvar wait is one */ |
| SleepEx(0, TRUE); |
| } |
| |
| /** |
| * APC to cancel a thread |
| */ |
| static void WINAPI docancel(ULONG_PTR dwParam) |
| { |
| private_thread_t *this = (private_thread_t*)dwParam; |
| |
| /* make sure cancel() does not access this anymore */ |
| threads_lock->lock(threads_lock); |
| threads_lock->unlock(threads_lock); |
| |
| end_thread(this); |
| ExitThread(0); |
| } |
| |
| METHOD(thread_t, cancel, void, |
| private_thread_t *this) |
| { |
| this->canceled = TRUE; |
| if (this->cancelability) |
| { |
| threads_lock->lock(threads_lock); |
| if (!this->cancel_pending) |
| { |
| this->cancel_pending = TRUE; |
| QueueUserAPC(docancel, this->handle, (uintptr_t)this); |
| if (this->condvar) |
| { |
| WakeAllConditionVariable(this->condvar); |
| } |
| } |
| threads_lock->unlock(threads_lock); |
| } |
| } |
| |
| METHOD(thread_t, kill_, void, |
| private_thread_t *this, int sig) |
| { |
| } |
| |
| METHOD(thread_t, detach, void, |
| private_thread_t *this) |
| { |
| this->detached = TRUE; |
| } |
| |
| METHOD(thread_t, join, void*, |
| private_thread_t *this) |
| { |
| void *ret; |
| |
| if (this->detached) |
| { |
| return NULL; |
| } |
| |
| while (!this->terminated) |
| { |
| /* join is a cancellation point, use alertable wait */ |
| WaitForSingleObjectEx(this->handle, INFINITE, TRUE); |
| } |
| |
| ret = this->ret; |
| |
| remove_thread(this); |
| destroy(this); |
| |
| return ret; |
| } |
| |
| /** |
| * Main function wrapper for threads |
| */ |
| static DWORD thread_cb(private_thread_t *this) |
| { |
| /* Enable cancelability once the thread starts. We must check for any |
| * pending cancellation request an queue the APC that gets executed |
| * at the first cancellation point. */ |
| this->cancelability = TRUE; |
| if (this->canceled) |
| { |
| cancel(this); |
| } |
| |
| this->ret = this->main(this->arg); |
| |
| end_thread(this); |
| |
| return 0; |
| } |
| |
| /** |
| * Create an internal thread object. |
| */ |
| static private_thread_t *create_internal(DWORD id) |
| { |
| private_thread_t *this; |
| bool old; |
| |
| old = set_leak_detective(FALSE); |
| |
| INIT(this, |
| .public = { |
| .cancel = _cancel, |
| .kill = _kill_, |
| .detach = _detach, |
| .join = _join, |
| }, |
| .cleanup = array_create(sizeof(cleanup_t), 0), |
| .tls = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4), |
| .id = id, |
| .cancelability = TRUE, |
| ); |
| |
| set_leak_detective(old); |
| |
| threads_lock->lock(threads_lock); |
| this->tid = threads_ids++; |
| threads_lock->unlock(threads_lock); |
| |
| if (id) |
| { |
| this->handle = OpenThread(THREAD_ALL_ACCESS, FALSE, id); |
| } |
| return this; |
| } |
| |
| /** |
| * Described in header. |
| */ |
| thread_t *thread_create(thread_main_t main, void *arg) |
| { |
| private_thread_t *this; |
| |
| this = create_internal(0); |
| |
| this->main = main; |
| this->arg = arg; |
| /* not cancellable until started */ |
| this->cancelability = FALSE; |
| |
| this->handle = CreateThread(NULL, 0, (void*)thread_cb, this, |
| CREATE_SUSPENDED, &this->id); |
| if (!this->handle) |
| { |
| destroy(this); |
| return NULL; |
| } |
| |
| put_thread(this); |
| |
| DBG2(DBG_LIB, "created thread %u", this->id); |
| |
| ResumeThread(this->handle); |
| |
| return &this->public; |
| } |
| |
| /** |
| * Described in header. |
| */ |
| thread_t *thread_current() |
| { |
| return &get_current_thread()->public; |
| } |
| |
| /** |
| * Described in header. |
| */ |
| u_int thread_current_id() |
| { |
| #ifdef USE_THREAD_IDS |
| return get_current_thread()->id; |
| #else |
| return get_current_thread()->tid; |
| #endif |
| } |
| |
| /** |
| * Described in header. |
| */ |
| void thread_cleanup_push(thread_cleanup_t cb, void *arg) |
| { |
| private_thread_t *this; |
| cleanup_t cleanup = { |
| .cb = cb, |
| .arg = arg, |
| }; |
| bool old; |
| |
| this = get_current_thread(); |
| |
| old = set_leak_detective(FALSE); |
| array_insert(this->cleanup, -1, &cleanup); |
| set_leak_detective(old); |
| } |
| |
| /** |
| * Described in header |
| */ |
| void thread_cleanup_pop(bool execute) |
| { |
| private_thread_t *this; |
| cleanup_t cleanup = {}; |
| bool old; |
| |
| this = get_current_thread(); |
| |
| old = set_leak_detective(FALSE); |
| array_remove(this->cleanup, -1, &cleanup); |
| set_leak_detective(old); |
| |
| if (execute) |
| { |
| cleanup.cb(cleanup.arg); |
| } |
| } |
| |
| /** |
| * Described in header. |
| */ |
| void thread_cleanup_popall() |
| { |
| private_thread_t *this; |
| cleanup_t cleanup = {}; |
| bool old; |
| |
| this = get_current_thread(); |
| while (array_count(this->cleanup)) |
| { |
| old = set_leak_detective(FALSE); |
| array_remove(this->cleanup, -1, &cleanup); |
| set_leak_detective(old); |
| |
| cleanup.cb(cleanup.arg); |
| } |
| } |
| |
| /** |
| * Described in header. |
| */ |
| bool thread_cancelability(bool enable) |
| { |
| private_thread_t *this; |
| bool old; |
| |
| this = get_current_thread(); |
| old = this->cancelability; |
| this->cancelability = enable; |
| |
| if (enable && !old && this->canceled) |
| { |
| cancel(this); |
| } |
| return old; |
| } |
| |
| /** |
| * Described in header. |
| */ |
| void thread_cancellation_point() |
| { |
| bool old; |
| |
| old = thread_cancelability(TRUE); |
| SleepEx(0, TRUE); |
| thread_cancelability(old); |
| } |
| |
| /** |
| * Described in header. |
| */ |
| void thread_exit(void *val) |
| { |
| private_thread_t *this; |
| |
| this = get_current_thread(); |
| this->ret = val; |
| |
| end_thread(this); |
| ExitThread(0); |
| } |
| |
| /** |
| * Clean up thread data while it detaches |
| */ |
| static void cleanup_tls() |
| { |
| private_thread_t *this; |
| bool old; |
| |
| old = set_leak_detective(FALSE); |
| threads_lock->lock(threads_lock); |
| |
| this = threads->remove(threads, (void*)(uintptr_t)GetCurrentThreadId()); |
| |
| threads_lock->unlock(threads_lock); |
| set_leak_detective(old); |
| |
| if (this) |
| { |
| /* If the thread exited, but has not been joined, it is in terminated |
| * state. We must not mangle it, as we target externally spawned |
| * threads only. */ |
| if (!this->terminated && !this->detached) |
| { |
| destroy(this); |
| } |
| } |
| } |
| |
| /** |
| * DllMain called for dll events |
| */ |
| BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) |
| { |
| switch (fdwReason) |
| { |
| case DLL_THREAD_DETACH: |
| cleanup_tls(); |
| break; |
| default: |
| break; |
| } |
| return TRUE; |
| } |
| |
| /* |
| * Described in header. |
| */ |
| void threads_init() |
| { |
| threads_lock = spinlock_create(); |
| threads = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4); |
| |
| /* reset counter should we initialize more than once */ |
| threads_ids = 0; |
| |
| put_thread(create_internal(GetCurrentThreadId())); |
| } |
| |
| /** |
| * Described in header. |
| */ |
| void threads_deinit() |
| { |
| private_thread_t *this; |
| |
| this = threads->remove(threads, (void*)(uintptr_t)GetCurrentThreadId()); |
| destroy(this); |
| |
| threads_lock->destroy(threads_lock); |
| threads->destroy(threads); |
| } |