Thread::Thread(const std::string &name) :
m_name(name),
m_retval(NULL),
+ m_joinable(false),
m_request_stop(false),
m_running(false)
{
#ifdef _AIX
m_kernel_thread_id = -1;
#endif
-
-#if USE_CPP11_THREADS
- m_thread_obj = NULL;
-#endif
}
bool Thread::start()
{
- MutexAutoLock lock(m_continue_mutex);
+ MutexAutoLock lock(m_mutex);
if (m_running)
return false;
- cleanup();
+ m_request_stop = false;
#if USE_CPP11_THREADS
while (!m_running)
sleep_ms(1);
+ m_joinable = true;
+
return true;
}
}
-void Thread::wait()
+bool Thread::wait()
{
- if (!m_running)
- return;
+ MutexAutoLock lock(m_mutex);
+
+ if (!m_joinable)
+ return false;
#if USE_CPP11_THREADS
m_thread_obj->join();
+ delete m_thread_obj;
+ m_thread_obj = NULL;
+
#elif USE_WIN_THREADS
int ret = WaitForSingleObject(m_thread_handle, INFINITE);
assert(ret == WAIT_OBJECT_0);
UNUSED(ret);
+ CloseHandle(m_thread_handle);
+ m_thread_handle = NULL;
+ m_thread_id = -1;
+
#elif USE_POSIX_THREADS
int ret = pthread_join(m_thread_handle, NULL);
#endif
assert(m_running == false);
-
- return;
+ m_joinable = false;
+ return true;
}
return false;
}
+ m_running = false;
+
#ifdef _WIN32
TerminateThread(m_thread_handle, 0);
+ CloseHandle(m_thread_handle);
#else
-
// We need to pthread_kill instead on Android since NDKv5's pthread
// implementation is incomplete.
# ifdef __ANDROID__
# else
pthread_cancel(m_thread_handle);
# endif
-
wait();
#endif
- cleanup();
-
- return true;
-}
-
-
-void Thread::cleanup()
-{
-#if USE_CPP11_THREADS
-
- delete m_thread_obj;
- m_thread_obj = NULL;
-
-#elif USE_WIN_THREADS
-
- CloseHandle(m_thread_handle);
- m_thread_handle = NULL;
- m_thread_id = -1;
-
-#elif USE_POSIX_THREADS
-
- // Can't do any cleanup for pthreads
-
-#endif
-
- m_name = "";
m_retval = NULL;
- m_running = false;
+ m_joinable = false;
m_request_stop = false;
+
+ return true;
}
#if USE_CPP11_THREADS || USE_POSIX_THREADS
- void *(Thread::threadProc)(void *param)
+void *Thread::threadProc(void *param)
#elif defined(_WIN32_WCE)
- DWORD (Thread::threadProc)(LPVOID param)
+DWORD Thread::threadProc(LPVOID param)
#elif defined(_WIN32)
- DWORD WINAPI (Thread::threadProc)(LPVOID param)
+DWORD WINAPI Thread::threadProc(LPVOID param)
#endif
{
Thread *thr = (Thread *)param;
public:
TestThreading() { TestManager::registerTestModule(this); }
const char *getName() { return "TestThreading"; }
- void runTests(IGameDef *);
+ void runTests(IGameDef *gamedef);
+
+ void testStartStopWait();
+ void testThreadKill();
void testAtomicSemaphoreThread();
};
static TestThreading g_test_instance;
-void TestThreading::runTests(IGameDef *)
+void TestThreading::runTests(IGameDef *gamedef)
{
+ TEST(testStartStopWait);
+ TEST(testThreadKill);
TEST(testAtomicSemaphoreThread);
}
+class SimpleTestThread : public Thread {
+public:
+ SimpleTestThread(unsigned int interval) :
+ Thread("SimpleTest"),
+ m_interval(interval)
+ {
+ }
+
+private:
+ void *run()
+ {
+ void *retval = this;
+
+ if (isCurrentThread() == false)
+ retval = (void *)0xBAD;
+
+ while (!stopRequested())
+ sleep_ms(m_interval);
+
+ return retval;
+ }
+
+ unsigned int m_interval;
+};
+
+void TestThreading::testStartStopWait()
+{
+ void *thread_retval;
+ SimpleTestThread *thread = new SimpleTestThread(25);
+
+ // Try this a couple times, since a Thread should be reusable after waiting
+ for (size_t i = 0; i != 5; i++) {
+ // Can't wait() on a joined, stopped thread
+ UASSERT(thread->wait() == false);
+
+ // start() should work the first time, but not the second.
+ UASSERT(thread->start() == true);
+ UASSERT(thread->start() == false);
+
+ UASSERT(thread->isRunning() == true);
+ UASSERT(thread->isCurrentThread() == false);
+
+ // Let it loop a few times...
+ sleep_ms(70);
+
+ // It's still running, so the return value shouldn't be available to us.
+ UASSERT(thread->getReturnValue(&thread_retval) == false);
+
+ // stop() should always succeed
+ UASSERT(thread->stop() == true);
+
+ // wait() only needs to wait the first time - the other two are no-ops.
+ UASSERT(thread->wait() == true);
+ UASSERT(thread->wait() == false);
+ UASSERT(thread->wait() == false);
+
+ // Now that the thread is stopped, we should be able to get the
+ // return value, and it should be the object itself.
+ thread_retval = NULL;
+ UASSERT(thread->getReturnValue(&thread_retval) == true);
+ UASSERT(thread_retval == thread);
+ }
+
+ delete thread;
+}
+
-class AtomicTestThread : public Thread
+void TestThreading::testThreadKill()
{
+ SimpleTestThread *thread = new SimpleTestThread(300);
+
+ UASSERT(thread->start() == true);
+
+ // kill()ing is quite violent, so let's make sure our victim is sleeping
+ // before we do this... so we don't corrupt the rest of the program's state
+ sleep_ms(100);
+ UASSERT(thread->kill() == true);
+
+ // The state of the thread object should be reset if all went well
+ UASSERT(thread->isRunning() == false);
+ UASSERT(thread->start() == true);
+ UASSERT(thread->stop() == true);
+ UASSERT(thread->wait() == true);
+
+ // kill() after already waiting should fail.
+ UASSERT(thread->kill() == false);
+
+ delete thread;
+}
+
+
+class AtomicTestThread : public Thread {
public:
AtomicTestThread(Atomic<u32> &v, Semaphore &trigger) :
Thread("AtomicTest"),
val(v),
trigger(trigger)
- {}
+ {
+ }
+
private:
void *run()
{
++val;
return NULL;
}
+
Atomic<u32> &val;
Semaphore &trigger;
};