702 lines
12 KiB
C
702 lines
12 KiB
C
/*
|
|
* 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);
|
|
}
|