Skip to content

Commit 964be64

Browse files
committedOct 24, 2015
Fix some threading things and add additional thread unittests
- Fix thread name reset on start() - Fully reset thread state on kill() - Add unittests to check for correct object states under various circumstances
1 parent 59fa117 commit 964be64

File tree

4 files changed

+137
-55
lines changed

4 files changed

+137
-55
lines changed
 

‎src/threading/thread.cpp

+28-43
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,13 @@ DEALINGS IN THE SOFTWARE.
8888
Thread::Thread(const std::string &name) :
8989
m_name(name),
9090
m_retval(NULL),
91+
m_joinable(false),
9192
m_request_stop(false),
9293
m_running(false)
9394
{
9495
#ifdef _AIX
9596
m_kernel_thread_id = -1;
9697
#endif
97-
98-
#if USE_CPP11_THREADS
99-
m_thread_obj = NULL;
100-
#endif
10198
}
10299

103100

@@ -109,12 +106,12 @@ Thread::~Thread()
109106

110107
bool Thread::start()
111108
{
112-
MutexAutoLock lock(m_continue_mutex);
109+
MutexAutoLock lock(m_mutex);
113110

114111
if (m_running)
115112
return false;
116113

117-
cleanup();
114+
m_request_stop = false;
118115

119116
#if USE_CPP11_THREADS
120117

@@ -145,6 +142,8 @@ bool Thread::start()
145142
while (!m_running)
146143
sleep_ms(1);
147144

145+
m_joinable = true;
146+
148147
return true;
149148
}
150149

@@ -156,21 +155,30 @@ bool Thread::stop()
156155
}
157156

158157

159-
void Thread::wait()
158+
bool Thread::wait()
160159
{
161-
if (!m_running)
162-
return;
160+
MutexAutoLock lock(m_mutex);
161+
162+
if (!m_joinable)
163+
return false;
163164

164165
#if USE_CPP11_THREADS
165166

166167
m_thread_obj->join();
167168

169+
delete m_thread_obj;
170+
m_thread_obj = NULL;
171+
168172
#elif USE_WIN_THREADS
169173

170174
int ret = WaitForSingleObject(m_thread_handle, INFINITE);
171175
assert(ret == WAIT_OBJECT_0);
172176
UNUSED(ret);
173177

178+
CloseHandle(m_thread_handle);
179+
m_thread_handle = NULL;
180+
m_thread_id = -1;
181+
174182
#elif USE_POSIX_THREADS
175183

176184
int ret = pthread_join(m_thread_handle, NULL);
@@ -180,8 +188,8 @@ void Thread::wait()
180188
#endif
181189

182190
assert(m_running == false);
183-
184-
return;
191+
m_joinable = false;
192+
return true;
185193
}
186194

187195

@@ -192,50 +200,27 @@ bool Thread::kill()
192200
return false;
193201
}
194202

203+
m_running = false;
204+
195205
#ifdef _WIN32
196206
TerminateThread(m_thread_handle, 0);
207+
CloseHandle(m_thread_handle);
197208
#else
198-
199209
// We need to pthread_kill instead on Android since NDKv5's pthread
200210
// implementation is incomplete.
201211
# ifdef __ANDROID__
202212
pthread_kill(m_thread_handle, SIGKILL);
203213
# else
204214
pthread_cancel(m_thread_handle);
205215
# endif
206-
207216
wait();
208217
#endif
209218

210-
cleanup();
211-
212-
return true;
213-
}
214-
215-
216-
void Thread::cleanup()
217-
{
218-
#if USE_CPP11_THREADS
219-
220-
delete m_thread_obj;
221-
m_thread_obj = NULL;
222-
223-
#elif USE_WIN_THREADS
224-
225-
CloseHandle(m_thread_handle);
226-
m_thread_handle = NULL;
227-
m_thread_id = -1;
228-
229-
#elif USE_POSIX_THREADS
230-
231-
// Can't do any cleanup for pthreads
232-
233-
#endif
234-
235-
m_name = "";
236219
m_retval = NULL;
237-
m_running = false;
220+
m_joinable = false;
238221
m_request_stop = false;
222+
223+
return true;
239224
}
240225

241226

@@ -256,11 +241,11 @@ bool Thread::isCurrentThread()
256241

257242

258243
#if USE_CPP11_THREADS || USE_POSIX_THREADS
259-
void *(Thread::threadProc)(void *param)
244+
void *Thread::threadProc(void *param)
260245
#elif defined(_WIN32_WCE)
261-
DWORD (Thread::threadProc)(LPVOID param)
246+
DWORD Thread::threadProc(LPVOID param)
262247
#elif defined(_WIN32)
263-
DWORD WINAPI (Thread::threadProc)(LPVOID param)
248+
DWORD WINAPI Thread::threadProc(LPVOID param)
264249
#endif
265250
{
266251
Thread *thr = (Thread *)param;

‎src/threading/thread.h

+5-5
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,10 @@ class Thread {
8181
/*
8282
* Waits for thread to finish.
8383
* Note: This does not stop a thread, you have to do this on your own.
84-
* Returns immediately if the thread is not started.
84+
* Returns false immediately if the thread is not started or has been waited
85+
* on before.
8586
*/
86-
void wait();
87+
bool wait();
8788

8889
/*
8990
* Returns true if the calling thread is this Thread object.
@@ -140,15 +141,14 @@ class Thread {
140141

141142
private:
142143
void *m_retval;
144+
bool m_joinable;
143145
Atomic<bool> m_request_stop;
144146
Atomic<bool> m_running;
145-
Mutex m_continue_mutex;
147+
Mutex m_mutex;
146148

147149
threadid_t m_thread_id;
148150
threadhandle_t m_thread_handle;
149151

150-
void cleanup();
151-
152152
static ThreadStartFunc threadProc;
153153

154154
#ifdef _AIX

‎src/threads.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
5858
// ThreadStartFunc
5959
//
6060
#if USE_CPP11_THREADS || USE_POSIX_THREADS
61-
typedef void *(ThreadStartFunc)(void *param);
61+
typedef void *ThreadStartFunc(void *param);
6262
#elif defined(_WIN32_WCE)
63-
typedef DWORD (ThreadStartFunc)(LPVOID param);
63+
typedef DWORD ThreadStartFunc(LPVOID param);
6464
#elif defined(_WIN32)
65-
typedef DWORD WINAPI (ThreadStartFunc)(LPVOID param);
65+
typedef DWORD WINAPI ThreadStartFunc(LPVOID param);
6666
#endif
6767

6868

‎src/unittest/test_threading.cpp

+101-4
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,122 @@ class TestThreading : public TestBase {
2828
public:
2929
TestThreading() { TestManager::registerTestModule(this); }
3030
const char *getName() { return "TestThreading"; }
31-
void runTests(IGameDef *);
31+
void runTests(IGameDef *gamedef);
32+
33+
void testStartStopWait();
34+
void testThreadKill();
3235
void testAtomicSemaphoreThread();
3336
};
3437

3538
static TestThreading g_test_instance;
3639

37-
void TestThreading::runTests(IGameDef *)
40+
void TestThreading::runTests(IGameDef *gamedef)
3841
{
42+
TEST(testStartStopWait);
43+
TEST(testThreadKill);
3944
TEST(testAtomicSemaphoreThread);
4045
}
4146

47+
class SimpleTestThread : public Thread {
48+
public:
49+
SimpleTestThread(unsigned int interval) :
50+
Thread("SimpleTest"),
51+
m_interval(interval)
52+
{
53+
}
54+
55+
private:
56+
void *run()
57+
{
58+
void *retval = this;
59+
60+
if (isCurrentThread() == false)
61+
retval = (void *)0xBAD;
62+
63+
while (!stopRequested())
64+
sleep_ms(m_interval);
65+
66+
return retval;
67+
}
68+
69+
unsigned int m_interval;
70+
};
71+
72+
void TestThreading::testStartStopWait()
73+
{
74+
void *thread_retval;
75+
SimpleTestThread *thread = new SimpleTestThread(25);
76+
77+
// Try this a couple times, since a Thread should be reusable after waiting
78+
for (size_t i = 0; i != 5; i++) {
79+
// Can't wait() on a joined, stopped thread
80+
UASSERT(thread->wait() == false);
81+
82+
// start() should work the first time, but not the second.
83+
UASSERT(thread->start() == true);
84+
UASSERT(thread->start() == false);
85+
86+
UASSERT(thread->isRunning() == true);
87+
UASSERT(thread->isCurrentThread() == false);
88+
89+
// Let it loop a few times...
90+
sleep_ms(70);
91+
92+
// It's still running, so the return value shouldn't be available to us.
93+
UASSERT(thread->getReturnValue(&thread_retval) == false);
94+
95+
// stop() should always succeed
96+
UASSERT(thread->stop() == true);
97+
98+
// wait() only needs to wait the first time - the other two are no-ops.
99+
UASSERT(thread->wait() == true);
100+
UASSERT(thread->wait() == false);
101+
UASSERT(thread->wait() == false);
102+
103+
// Now that the thread is stopped, we should be able to get the
104+
// return value, and it should be the object itself.
105+
thread_retval = NULL;
106+
UASSERT(thread->getReturnValue(&thread_retval) == true);
107+
UASSERT(thread_retval == thread);
108+
}
109+
110+
delete thread;
111+
}
112+
42113

43-
class AtomicTestThread : public Thread
114+
void TestThreading::testThreadKill()
44115
{
116+
SimpleTestThread *thread = new SimpleTestThread(300);
117+
118+
UASSERT(thread->start() == true);
119+
120+
// kill()ing is quite violent, so let's make sure our victim is sleeping
121+
// before we do this... so we don't corrupt the rest of the program's state
122+
sleep_ms(100);
123+
UASSERT(thread->kill() == true);
124+
125+
// The state of the thread object should be reset if all went well
126+
UASSERT(thread->isRunning() == false);
127+
UASSERT(thread->start() == true);
128+
UASSERT(thread->stop() == true);
129+
UASSERT(thread->wait() == true);
130+
131+
// kill() after already waiting should fail.
132+
UASSERT(thread->kill() == false);
133+
134+
delete thread;
135+
}
136+
137+
138+
class AtomicTestThread : public Thread {
45139
public:
46140
AtomicTestThread(Atomic<u32> &v, Semaphore &trigger) :
47141
Thread("AtomicTest"),
48142
val(v),
49143
trigger(trigger)
50-
{}
144+
{
145+
}
146+
51147
private:
52148
void *run()
53149
{
@@ -56,6 +152,7 @@ class AtomicTestThread : public Thread
56152
++val;
57153
return NULL;
58154
}
155+
59156
Atomic<u32> &val;
60157
Semaphore &trigger;
61158
};

0 commit comments

Comments
 (0)
Please sign in to comment.