diff options
Diffstat (limited to 'src/lib/arch/win32/ArchMultithreadWindows.cpp')
| -rw-r--r-- | src/lib/arch/win32/ArchMultithreadWindows.cpp | 705 |
1 files changed, 705 insertions, 0 deletions
diff --git a/src/lib/arch/win32/ArchMultithreadWindows.cpp b/src/lib/arch/win32/ArchMultithreadWindows.cpp new file mode 100644 index 0000000..d3fd059 --- /dev/null +++ b/src/lib/arch/win32/ArchMultithreadWindows.cpp @@ -0,0 +1,705 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#if defined(_MSC_VER) && !defined(_MT) +# error multithreading compile option is required +#endif + +#include "arch/win32/ArchMultithreadWindows.h" +#include "arch/Arch.h" +#include "arch/XArch.h" + +#include <process.h> + +// +// note -- implementation of condition variable taken from: +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html +// titled "Strategies for Implementing POSIX Condition Variables +// on Win32." it also provides an implementation that doesn't +// suffer from the incorrectness problem described in our +// corresponding header but it is slower, still unfair, and +// can cause busy waiting. +// + +// +// ArchThreadImpl +// + +class ArchThreadImpl { +public: + ArchThreadImpl(); + ~ArchThreadImpl(); + +public: + int m_refCount; + HANDLE m_thread; + DWORD m_id; + IArchMultithread::ThreadFunc m_func; + void* m_userData; + HANDLE m_cancel; + bool m_cancelling; + HANDLE m_exit; + void* m_result; + void* m_networkData; +}; + +ArchThreadImpl::ArchThreadImpl() : + m_refCount(1), + m_thread(NULL), + m_id(0), + m_func(NULL), + m_userData(NULL), + m_cancelling(false), + m_result(NULL), + m_networkData(NULL) +{ + m_exit = CreateEvent(NULL, TRUE, FALSE, NULL); + m_cancel = CreateEvent(NULL, TRUE, FALSE, NULL); +} + +ArchThreadImpl::~ArchThreadImpl() +{ + CloseHandle(m_exit); + CloseHandle(m_cancel); +} + + +// +// ArchMultithreadWindows +// + +ArchMultithreadWindows* ArchMultithreadWindows::s_instance = NULL; + +ArchMultithreadWindows::ArchMultithreadWindows() +{ + assert(s_instance == NULL); + s_instance = this; + + // no signal handlers + for (size_t i = 0; i < kNUM_SIGNALS; ++i) { + m_signalFunc[i] = NULL; + m_signalUserData[i] = NULL; + } + + // create mutex for thread list + m_threadMutex = newMutex(); + + // create thread for calling (main) thread and add it to our + // list. no need to lock the mutex since we're the only thread. + m_mainThread = new ArchThreadImpl; + m_mainThread->m_thread = NULL; + m_mainThread->m_id = GetCurrentThreadId(); + insert(m_mainThread); +} + +ArchMultithreadWindows::~ArchMultithreadWindows() +{ + s_instance = NULL; + + // clean up thread list + for (ThreadList::iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + delete *index; + } + + // done with mutex + delete m_threadMutex; +} + +void +ArchMultithreadWindows::setNetworkDataForCurrentThread(void* data) +{ + lockMutex(m_threadMutex); + ArchThreadImpl* thread = findNoRef(GetCurrentThreadId()); + thread->m_networkData = data; + unlockMutex(m_threadMutex); +} + +void* +ArchMultithreadWindows::getNetworkDataForThread(ArchThread thread) +{ + lockMutex(m_threadMutex); + void* data = thread->m_networkData; + unlockMutex(m_threadMutex); + return data; +} + +HANDLE +ArchMultithreadWindows::getCancelEventForCurrentThread() +{ + lockMutex(m_threadMutex); + ArchThreadImpl* thread = findNoRef(GetCurrentThreadId()); + unlockMutex(m_threadMutex); + return thread->m_cancel; +} + +ArchMultithreadWindows* +ArchMultithreadWindows::getInstance() +{ + return s_instance; +} + +ArchCond +ArchMultithreadWindows::newCondVar() +{ + ArchCondImpl* cond = new ArchCondImpl; + cond->m_events[ArchCondImpl::kSignal] = CreateEvent(NULL, + FALSE, FALSE, NULL); + cond->m_events[ArchCondImpl::kBroadcast] = CreateEvent(NULL, + TRUE, FALSE, NULL); + cond->m_waitCountMutex = newMutex(); + cond->m_waitCount = 0; + return cond; +} + +void +ArchMultithreadWindows::closeCondVar(ArchCond cond) +{ + CloseHandle(cond->m_events[ArchCondImpl::kSignal]); + CloseHandle(cond->m_events[ArchCondImpl::kBroadcast]); + closeMutex(cond->m_waitCountMutex); + delete cond; +} + +void +ArchMultithreadWindows::signalCondVar(ArchCond cond) +{ + // is anybody waiting? + lockMutex(cond->m_waitCountMutex); + const bool hasWaiter = (cond->m_waitCount > 0); + unlockMutex(cond->m_waitCountMutex); + + // wake one thread if anybody is waiting + if (hasWaiter) { + SetEvent(cond->m_events[ArchCondImpl::kSignal]); + } +} + +void +ArchMultithreadWindows::broadcastCondVar(ArchCond cond) +{ + // is anybody waiting? + lockMutex(cond->m_waitCountMutex); + const bool hasWaiter = (cond->m_waitCount > 0); + unlockMutex(cond->m_waitCountMutex); + + // wake all threads if anybody is waiting + if (hasWaiter) { + SetEvent(cond->m_events[ArchCondImpl::kBroadcast]); + } +} + +bool +ArchMultithreadWindows::waitCondVar(ArchCond cond, + ArchMutex mutex, double timeout) +{ + // prepare to wait + const DWORD winTimeout = (timeout < 0.0) ? INFINITE : + static_cast<DWORD>(1000.0 * timeout); + + // make a list of the condition variable events and the cancel event + // for the current thread. + HANDLE handles[4]; + handles[0] = cond->m_events[ArchCondImpl::kSignal]; + handles[1] = cond->m_events[ArchCondImpl::kBroadcast]; + handles[2] = getCancelEventForCurrentThread(); + + // update waiter count + lockMutex(cond->m_waitCountMutex); + ++cond->m_waitCount; + unlockMutex(cond->m_waitCountMutex); + + // release mutex. this should be atomic with the wait so that it's + // impossible for another thread to signal us between the unlock and + // the wait, which would lead to a lost signal on broadcasts. + // however, we're using a manual reset event for broadcasts which + // stays set until we reset it, so we don't lose the broadcast. + unlockMutex(mutex); + + // wait for a signal or broadcast + // TODO: this doesn't always signal when kill signals are sent + DWORD result = WaitForMultipleObjects(3, handles, FALSE, winTimeout); + + // cancel takes priority + if (result != WAIT_OBJECT_0 + 2 && + WaitForSingleObject(handles[2], 0) == WAIT_OBJECT_0) { + result = WAIT_OBJECT_0 + 2; + } + + // update the waiter count and check if we're the last waiter + lockMutex(cond->m_waitCountMutex); + --cond->m_waitCount; + const bool last = (result == WAIT_OBJECT_0 + 1 && cond->m_waitCount == 0); + unlockMutex(cond->m_waitCountMutex); + + // reset the broadcast event if we're the last waiter + if (last) { + ResetEvent(cond->m_events[ArchCondImpl::kBroadcast]); + } + + // reacquire the mutex + lockMutex(mutex); + + // cancel thread if necessary + if (result == WAIT_OBJECT_0 + 2) { + ARCH->testCancelThread(); + } + + // return success or failure + return (result == WAIT_OBJECT_0 + 0 || + result == WAIT_OBJECT_0 + 1); +} + +ArchMutex +ArchMultithreadWindows::newMutex() +{ + ArchMutexImpl* mutex = new ArchMutexImpl; + InitializeCriticalSection(&mutex->m_mutex); + return mutex; +} + +void +ArchMultithreadWindows::closeMutex(ArchMutex mutex) +{ + DeleteCriticalSection(&mutex->m_mutex); + delete mutex; +} + +void +ArchMultithreadWindows::lockMutex(ArchMutex mutex) +{ + EnterCriticalSection(&mutex->m_mutex); +} + +void +ArchMultithreadWindows::unlockMutex(ArchMutex mutex) +{ + LeaveCriticalSection(&mutex->m_mutex); +} + +ArchThread +ArchMultithreadWindows::newThread(ThreadFunc func, void* data) +{ + lockMutex(m_threadMutex); + + // create thread impl for new thread + ArchThreadImpl* thread = new ArchThreadImpl; + thread->m_func = func; + thread->m_userData = data; + + // create thread + unsigned int id = 0; + thread->m_thread = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, + threadFunc, (void*)thread, 0, &id)); + thread->m_id = static_cast<DWORD>(id); + + // check if thread was started + if (thread->m_thread == 0) { + // failed to start thread so clean up + delete thread; + thread = NULL; + } + else { + // add thread to list + insert(thread); + + // increment ref count to account for the thread itself + refThread(thread); + } + + // note that the child thread will wait until we release this mutex + unlockMutex(m_threadMutex); + + return thread; +} + +ArchThread +ArchMultithreadWindows::newCurrentThread() +{ + lockMutex(m_threadMutex); + ArchThreadImpl* thread = find(GetCurrentThreadId()); + unlockMutex(m_threadMutex); + assert(thread != NULL); + return thread; +} + +void +ArchMultithreadWindows::closeThread(ArchThread thread) +{ + assert(thread != NULL); + + // decrement ref count and clean up thread if no more references + if (--thread->m_refCount == 0) { + // close the handle (main thread has a NULL handle) + if (thread->m_thread != NULL) { + CloseHandle(thread->m_thread); + } + + // remove thread from list + lockMutex(m_threadMutex); + assert(findNoRefOrCreate(thread->m_id) == thread); + erase(thread); + unlockMutex(m_threadMutex); + + // done with thread + delete thread; + } +} + +ArchThread +ArchMultithreadWindows::copyThread(ArchThread thread) +{ + refThread(thread); + return thread; +} + +void +ArchMultithreadWindows::cancelThread(ArchThread thread) +{ + assert(thread != NULL); + + // set cancel flag + SetEvent(thread->m_cancel); +} + +void +ArchMultithreadWindows::setPriorityOfThread(ArchThread thread, int n) +{ + struct PriorityInfo { + public: + DWORD m_class; + int m_level; + }; + static const PriorityInfo s_pClass[] = { + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_IDLE }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_IDLE }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_TIME_CRITICAL} + }; +#if defined(_DEBUG) + // don't use really high priorities when debugging + static const size_t s_pMax = 13; +#else + static const size_t s_pMax = sizeof(s_pClass) / sizeof(s_pClass[0]) - 1; +#endif + static const size_t s_pBase = 8; // index of normal priority + + assert(thread != NULL); + + size_t index; + if (n > 0 && s_pBase < (size_t)n) { + // lowest priority + index = 0; + } + else { + index = (size_t)((int)s_pBase - n); + if (index > s_pMax) { + // highest priority + index = s_pMax; + } + } + SetPriorityClass(GetCurrentProcess(), s_pClass[index].m_class); + SetThreadPriority(thread->m_thread, s_pClass[index].m_level); +} + +void +ArchMultithreadWindows::testCancelThread() +{ + // find current thread + lockMutex(m_threadMutex); + ArchThreadImpl* thread = findNoRef(GetCurrentThreadId()); + unlockMutex(m_threadMutex); + + // test cancel on thread + testCancelThreadImpl(thread); +} + +bool +ArchMultithreadWindows::wait(ArchThread target, double timeout) +{ + assert(target != NULL); + + lockMutex(m_threadMutex); + + // find current thread + ArchThreadImpl* self = findNoRef(GetCurrentThreadId()); + + // ignore wait if trying to wait on ourself + if (target == self) { + unlockMutex(m_threadMutex); + return false; + } + + // ref the target so it can't go away while we're watching it + refThread(target); + + unlockMutex(m_threadMutex); + + // convert timeout + DWORD t; + if (timeout < 0.0) { + t = INFINITE; + } + else { + t = (DWORD)(1000.0 * timeout); + } + + // wait for this thread to be cancelled or woken up or for the + // target thread to terminate. + HANDLE handles[2]; + handles[0] = target->m_exit; + handles[1] = self->m_cancel; + DWORD result = WaitForMultipleObjects(2, handles, FALSE, t); + + // cancel takes priority + if (result != WAIT_OBJECT_0 + 1 && + WaitForSingleObject(handles[1], 0) == WAIT_OBJECT_0) { + result = WAIT_OBJECT_0 + 1; + } + + // release target + closeThread(target); + + // handle result + switch (result) { + case WAIT_OBJECT_0 + 0: + // target thread terminated + return true; + + case WAIT_OBJECT_0 + 1: + // this thread was cancelled. does not return. + testCancelThreadImpl(self); + + default: + // timeout or error + return false; + } +} + +bool +ArchMultithreadWindows::isSameThread(ArchThread thread1, ArchThread thread2) +{ + return (thread1 == thread2); +} + +bool +ArchMultithreadWindows::isExitedThread(ArchThread thread) +{ + // poll exit event + return (WaitForSingleObject(thread->m_exit, 0) == WAIT_OBJECT_0); +} + +void* +ArchMultithreadWindows::getResultOfThread(ArchThread thread) +{ + lockMutex(m_threadMutex); + void* result = thread->m_result; + unlockMutex(m_threadMutex); + return result; +} + +IArchMultithread::ThreadID +ArchMultithreadWindows::getIDOfThread(ArchThread thread) +{ + return static_cast<ThreadID>(thread->m_id); +} + +void +ArchMultithreadWindows::setSignalHandler( + ESignal signal, SignalFunc func, void* userData) +{ + lockMutex(m_threadMutex); + m_signalFunc[signal] = func; + m_signalUserData[signal] = userData; + unlockMutex(m_threadMutex); +} + +void +ArchMultithreadWindows::raiseSignal(ESignal signal) +{ + lockMutex(m_threadMutex); + if (m_signalFunc[signal] != NULL) { + m_signalFunc[signal](signal, m_signalUserData[signal]); + ARCH->unblockPollSocket(m_mainThread); + } + else if (signal == kINTERRUPT || signal == kTERMINATE) { + ARCH->cancelThread(m_mainThread); + } + unlockMutex(m_threadMutex); +} + +ArchThreadImpl* +ArchMultithreadWindows::find(DWORD id) +{ + ArchThreadImpl* impl = findNoRef(id); + if (impl != NULL) { + refThread(impl); + } + return impl; +} + +ArchThreadImpl* +ArchMultithreadWindows::findNoRef(DWORD id) +{ + ArchThreadImpl* impl = findNoRefOrCreate(id); + if (impl == NULL) { + // create thread for calling thread which isn't in our list and + // add it to the list. this won't normally happen but it can if + // the system calls us under a new thread, like it does when we + // run as a service. + impl = new ArchThreadImpl; + impl->m_thread = NULL; + impl->m_id = GetCurrentThreadId(); + insert(impl); + } + return impl; +} + +ArchThreadImpl* +ArchMultithreadWindows::findNoRefOrCreate(DWORD id) +{ + // linear search + for (ThreadList::const_iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if ((*index)->m_id == id) { + return *index; + } + } + return NULL; +} + +void +ArchMultithreadWindows::insert(ArchThreadImpl* thread) +{ + assert(thread != NULL); + + // thread shouldn't already be on the list + assert(findNoRefOrCreate(thread->m_id) == NULL); + + // append to list + m_threadList.push_back(thread); +} + +void +ArchMultithreadWindows::erase(ArchThreadImpl* thread) +{ + for (ThreadList::iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if (*index == thread) { + m_threadList.erase(index); + break; + } + } +} + +void +ArchMultithreadWindows::refThread(ArchThreadImpl* thread) +{ + assert(thread != NULL); + assert(findNoRefOrCreate(thread->m_id) != NULL); + ++thread->m_refCount; +} + +void +ArchMultithreadWindows::testCancelThreadImpl(ArchThreadImpl* thread) +{ + assert(thread != NULL); + + // poll cancel event. return if not set. + const DWORD result = WaitForSingleObject(thread->m_cancel, 0); + if (result != WAIT_OBJECT_0) { + return; + } + + // update cancel state + lockMutex(m_threadMutex); + bool cancel = !thread->m_cancelling; + thread->m_cancelling = true; + ResetEvent(thread->m_cancel); + unlockMutex(m_threadMutex); + + // unwind thread's stack if cancelling + if (cancel) { + throw XThreadCancel(); + } +} + +unsigned int __stdcall +ArchMultithreadWindows::threadFunc(void* vrep) +{ + // get the thread + ArchThreadImpl* thread = static_cast<ArchThreadImpl*>(vrep); + + // run thread + s_instance->doThreadFunc(thread); + + // terminate the thread + return 0; +} + +void +ArchMultithreadWindows::doThreadFunc(ArchThread thread) +{ + // wait for parent to initialize this object + lockMutex(m_threadMutex); + unlockMutex(m_threadMutex); + + void* result = NULL; + try { + // go + result = (*thread->m_func)(thread->m_userData); + } + + catch (XThreadCancel&) { + // client called cancel() + } + catch (...) { + // note -- don't catch (...) to avoid masking bugs + SetEvent(thread->m_exit); + closeThread(thread); + throw; + } + + // thread has exited + lockMutex(m_threadMutex); + thread->m_result = result; + unlockMutex(m_threadMutex); + SetEvent(thread->m_exit); + + // done with thread + closeThread(thread); +} |
