From 765a834cd04473afeb4b86b445dee901e0d0c83c Mon Sep 17 00:00:00 2001
From: kwolekr <kwolekr@minetest.net>
Date: Fri, 16 Oct 2015 23:43:29 -0400
Subject: Refactor Thread class to improve readability and portability

- Fix some incompatibilities with obscure platforms (AIX and WinCE)
- Clean up Thread class interface
- Add m_ prefix to private member variables
- Simplify platform-dependent logic, reducing preprocessor
  conditional clauses and improving readibility
- Add Thread class documentation
---
 src/threading/thread.cpp | 390 +++++++++++++++++++++++++++++------------------
 src/threading/thread.h   | 161 ++++++++++++-------
 2 files changed, 348 insertions(+), 203 deletions(-)

(limited to 'src/threading')

diff --git a/src/threading/thread.cpp b/src/threading/thread.cpp
index 1f9b54795..a10082478 100644
--- a/src/threading/thread.cpp
+++ b/src/threading/thread.cpp
@@ -26,48 +26,48 @@ DEALINGS IN THE SOFTWARE.
 #include "threading/thread.h"
 #include "threading/mutex_auto_lock.h"
 #include "log.h"
+#include "porting.h"
 
-#if __cplusplus >= 201103L
+#define UNUSED(expr) do { (void)(expr); } while (0)
+
+#if USE_CPP11_THREADS
 	#include <chrono>
-#else
-	#define UNUSED(expr) do { (void)(expr); } while (0)
-# ifdef _WIN32
-#  ifndef _WIN32_WCE
-	#include <process.h>
-#  endif
-# else
-	#include <ctime>
-	#include <cassert>
-	#include <cstdlib>
+	#include <system_error>
+#elif USE_WIN_THREADS
+	#ifndef _WIN32_WCE
+		#include <process.h>
+	#endif
+#elif USE_POSIX_THREADS
+	#include <time.h>
+	#include <assert.h>
+	#include <stdlib.h>
+	#include <unistd.h>
 	#include <sys/time.h>
 
-	// For getNumberOfProcessors
-	#include <unistd.h>
-#  if defined(__FreeBSD__) || defined(__APPLE__)
-	#include <sys/types.h>
-	#include <sys/sysctl.h>
-#  elif defined(_GNU_SOURCE)
-	#include <sys/sysinfo.h>
-#  endif
-# endif
+	#if defined(__FreeBSD__) || defined(__APPLE__)
+		#include <sys/types.h>
+		#include <sys/sysctl.h>
+	#elif defined(_GNU_SOURCE)
+		#include <sys/sysinfo.h>
+	#endif
 #endif
 
 
-// For setName
+// for setName
 #if defined(linux) || defined(__linux)
 	#include <sys/prctl.h>
 #elif defined(__FreeBSD__) || defined(__OpenBSD__)
 	#include <pthread_np.h>
 #elif defined(_MSC_VER)
 	struct THREADNAME_INFO {
-		DWORD dwType; // Must be 0x1000
-		LPCSTR szName; // Pointer to name (in user addr space)
+		DWORD dwType;     // Must be 0x1000
+		LPCSTR szName;    // Pointer to name (in user addr space)
 		DWORD dwThreadID; // Thread ID (-1=caller thread)
-		DWORD dwFlags; // Reserved for future use, must be zero
+		DWORD dwFlags;    // Reserved for future use, must be zero
 	};
 #endif
 
-// For bindToProcessor
+// for bindToProcessor
 #if __FreeBSD_version >= 702106
 	typedef cpuset_t cpu_set_t;
 #elif defined(__linux) || defined(linux)
@@ -78,6 +78,7 @@ DEALINGS IN THE SOFTWARE.
 	#include <sys/procset.h>
 #elif defined(_AIX)
 	#include <sys/processor.h>
+	#include <sys/thread.h>
 #elif defined(__APPLE__)
 	#include <mach/mach_init.h>
 	#include <mach/thread_act.h>
@@ -85,188 +86,246 @@ DEALINGS IN THE SOFTWARE.
 
 
 Thread::Thread(const std::string &name) :
-	name(name),
-	retval(NULL),
-	request_stop(false),
-	running(false)
-#if __cplusplus >= 201103L
-	, thread(NULL)
-#elif !defined(_WIN32)
-	, started(false)
+	m_name(name),
+	m_retval(NULL),
+	m_request_stop(false),
+	m_running(false)
+{
+#ifdef _AIX
+	m_kernel_thread_id = -1;
 #endif
-{}
 
+#if USE_CPP11_THREADS
+	m_thread_obj = NULL;
+#endif
+}
 
-void Thread::wait()
+
+Thread::~Thread()
 {
-#if __cplusplus >= 201103L
-	if (!thread || !thread->joinable())
-		return;
-	thread->join();
-#elif defined(_WIN32)
-	if (!running)
-		return;
-	WaitForSingleObject(thread, INFINITE);
-#else // pthread
-	void *status;
-	if (!started)
-		return;
-	int ret = pthread_join(thread, &status);
-	assert(!ret);
-	UNUSED(ret);
-	started = false;
-#endif
+	kill();
 }
 
 
 bool Thread::start()
 {
-	if (running)
+	MutexAutoLock lock(m_continue_mutex);
+
+	if (m_running)
 		return false;
-	request_stop = false;
 
-#if __cplusplus >= 201103L
-	MutexAutoLock l(continue_mutex);
-	thread = new std::thread(theThread, this);
-#elif defined(_WIN32)
-	MutexAutoLock l(continue_mutex);
-# ifdef _WIN32_WCE
-	thread = CreateThread(NULL, 0, theThread, this, 0, &thread_id);
-# else
-	thread = (HANDLE)_beginthreadex(NULL, 0, theThread, this, 0, &thread_id);
-# endif
-	if (!thread)
+	cleanup();
+
+#if USE_CPP11_THREADS
+
+	try {
+		m_thread_obj    = new std::thread(threadProc, this);
+		m_thread_id     = m_thread->get_id();
+		m_thread_handle = m_thread->native_handle();
+	} except (const std::system_error &e) {
 		return false;
-#else
-	int status;
+	}
 
-	MutexAutoLock l(continue_mutex);
+#elif USE_WIN_THREADS
 
-	status = pthread_create(&thread, NULL, theThread, this);
+	m_thread_handle = CreateThread(NULL, 0, threadProc, this, 0, &m_thread_id);
+	if (!m_thread_handle)
+		return false;
+
+#elif USE_POSIX_THREADS
 
+	int status = pthread_create(&m_thread_handle, NULL, threadProc, this);
 	if (status)
 		return false;
-#endif
 
-#if __cplusplus < 201103L
-	// Wait until running
-	while (!running) {
-# ifdef _WIN32
-		Sleep(1);
-	}
-# else
-		struct timespec req, rem;
-		req.tv_sec = 0;
-		req.tv_nsec = 1000000;
-		nanosleep(&req, &rem);
-	}
-	started = true;
-# endif
+	m_thread_id = m_thread_handle;
+
 #endif
+
+	while (!m_running)
+		sleep_ms(1);
+
+	return true;
+}
+
+
+bool Thread::stop()
+{
+	m_request_stop = true;
 	return true;
 }
 
 
+void Thread::wait()
+{
+	if (!m_running)
+		return;
+
+#if USE_CPP11_THREADS
+
+	m_thread_obj->join();
+
+#elif USE_WIN_THREADS
+
+	int ret == WaitForSingleObject(m_thread_handle, INFINITE);
+	assert(ret == WAIT_OBJECT_0);
+	UNUSED(ret);
+
+#elif USE_POSIX_THREADS
+
+	int ret = pthread_join(m_thread_handle, NULL);
+	assert(ret == 0);
+	UNUSED(ret);
+
+#endif
+
+	assert(m_running == false);
+
+	return;
+}
+
+
 bool Thread::kill()
 {
-#ifdef _WIN32
-	if (!running)
-		return false;
-	TerminateThread(getThreadHandle(), 0);
-	CloseHandle(getThreadHandle());
-#else
-	if (!running) {
+	if (!m_running) {
 		wait();
 		return false;
 	}
+
+#ifdef _WIN32
+	TerminateThread(m_thread_handle, 0);
+#else
+
+	// We need to pthread_kill instead on Android since NDKv5's pthread
+	// implementation is incomplete.
 # ifdef __ANDROID__
-	pthread_kill(getThreadHandle(), SIGKILL);
+	pthread_kill(m_thread_handle, SIGKILL);
 # else
-	pthread_cancel(getThreadHandle());
+	pthread_cancel(m_thread_handle);
 # endif
+
 	wait();
 #endif
-#if __cplusplus >= 201103L
-	delete thread;
-#endif
-	running = false;
+
+	cleanup();
+
 	return true;
 }
 
 
-bool Thread::isSameThread()
+void Thread::cleanup()
 {
-#if __cplusplus >= 201103L
-	return thread->get_id() == std::this_thread::get_id();
-#elif defined(_WIN32)
-	return GetCurrentThreadId() == thread_id;
-#else
-	return pthread_equal(pthread_self(), thread);
+#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_request_stop = false;
 }
 
 
-#if __cplusplus >= 201103L
-void Thread::theThread(Thread *th)
+bool Thread::getReturnValue(void **ret)
+{
+	if (m_running)
+		return false;
+
+	*ret = m_retval;
+	return true;
+}
+
+
+bool Thread::isCurrentThread()
+{
+	return thr_is_current_thread(m_thread_id);
+}
+
+
+#if USE_CPP11_THREADS || USE_POSIX_THREADS
+	void *(Thread::threadProc)(void *param)
 #elif defined(_WIN32_WCE)
-DWORD WINAPI Thread::theThread(void *param)
+	DWORD (Thread::threadProc)(LPVOID param)
 #elif defined(_WIN32)
-UINT __stdcall Thread::theThread(void *param)
-#else
-void *Thread::theThread(void *param)
+	DWORD WINAPI (Thread::threadProc)(LPVOID param)
 #endif
 {
-#if __cplusplus < 201103L
-	Thread *th = static_cast<Thread *>(param);
+	Thread *thr = (Thread *)param;
+
+#ifdef _AIX
+	m_kernel_thread_id = thread_self();
 #endif
-	th->running = true;
 
-	th->setName();
-	g_logger.registerThread(th->name);
+	thr->setName(thr->m_name);
+
+	g_logger.registerThread(thr->m_name);
+	thr->m_running = true;
 
-	th->retval = th->run();
+	thr->m_retval = thr->run();
 
+	thr->m_running = false;
 	g_logger.deregisterThread();
 
-	th->running = false;
-#if __cplusplus < 201103L
-# ifdef _WIN32
-	CloseHandle(th->thread);
-# endif
 	return NULL;
-#endif
 }
 
 
 void Thread::setName(const std::string &name)
 {
 #if defined(linux) || defined(__linux)
-	/* It would be cleaner to do this with pthread_setname_np,
-	 * which was added to glibc in version 2.12, but some major
-	 * distributions are still runing 2.11 and previous versions.
-	 */
+
+	// It would be cleaner to do this with pthread_setname_np,
+	// which was added to glibc in version 2.12, but some major
+	// distributions are still runing 2.11 and previous versions.
 	prctl(PR_SET_NAME, name.c_str());
+
 #elif defined(__FreeBSD__) || defined(__OpenBSD__)
+
 	pthread_set_name_np(pthread_self(), name.c_str());
+
 #elif defined(__NetBSD__)
+
 	pthread_setname_np(pthread_self(), name.c_str());
+
 #elif defined(__APPLE__)
+
 	pthread_setname_np(name.c_str());
+
 #elif defined(_MSC_VER)
+
 	// Windows itself doesn't support thread names,
 	// but the MSVC debugger does...
 	THREADNAME_INFO info;
+
 	info.dwType = 0x1000;
 	info.szName = name.c_str();
 	info.dwThreadID = -1;
 	info.dwFlags = 0;
+
 	__try {
-		RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR *)&info);
+		RaiseException(0x406D1388, 0,
+			sizeof(info) / sizeof(DWORD), (ULONG_PTR *)&info);
 	} __except (EXCEPTION_CONTINUE_EXECUTION) {
 	}
+
 #elif defined(_WIN32) || defined(__GNU__)
+
 	// These platforms are known to not support thread names.
 	// Silently ignore the request.
+
 #else
 	#warning "Unrecognized platform, thread names will not be available."
 #endif
@@ -276,59 +335,96 @@ void Thread::setName(const std::string &name)
 unsigned int Thread::getNumberOfProcessors()
 {
 #if __cplusplus >= 201103L
+
 	return std::thread::hardware_concurrency();
+
 #elif defined(_SC_NPROCESSORS_ONLN)
+
 	return sysconf(_SC_NPROCESSORS_ONLN);
-#elif defined(__FreeBSD__) || defined(__APPLE__)
-	unsigned int len, count;
-	len = sizeof(count);
-	return sysctlbyname("hw.ncpu", &count, &len, NULL, 0);
+
+#elif defined(__FreeBSD__) || defined(__NetBSD__) || \
+	defined(__DragonFly__) || defined(__APPLE__)
+
+	unsigned int num_cpus = 1;
+	size_t len = sizeof(num_cpus);
+
+	int mib[2];
+	mib[0] = CTL_HW;
+	mib[1] = HW_NCPU;
+
+	sysctl(mib, 2, &num_cpus, &len, NULL, 0);
+	return num_cpus;
+
 #elif defined(_GNU_SOURCE)
+
 	return get_nprocs();
+
 #elif defined(_WIN32)
+
 	SYSTEM_INFO sysinfo;
 	GetSystemInfo(&sysinfo);
 	return sysinfo.dwNumberOfProcessors;
+
 #elif defined(PTW32_VERSION) || defined(__hpux)
+
 	return pthread_num_processors_np();
+
 #else
+
 	return 1;
+
 #endif
 }
 
 
-bool Thread::bindToProcessor(unsigned int num)
+bool Thread::bindToProcessor(unsigned int proc_number)
 {
 #if defined(__ANDROID__)
+
 	return false;
+
 #elif defined(_WIN32)
-	return SetThreadAffinityMask(getThreadHandle(), 1 << num);
+
+	return SetThreadAffinityMask(m_thread_handle, 1 << proc_number);
+
 #elif __FreeBSD_version >= 702106 || defined(__linux) || defined(linux)
+
 	cpu_set_t cpuset;
+
 	CPU_ZERO(&cpuset);
-	CPU_SET(num, &cpuset);
-	return pthread_setaffinity_np(getThreadHandle(), sizeof(cpuset),
-		&cpuset) == 0;
+	CPU_SET(proc_number, &cpuset);
+
+	return pthread_setaffinity_np(m_thread_handle, sizeof(cpuset), &cpuset) == 0;
+
 #elif defined(__sun) || defined(sun)
-	return processor_bind(P_LWPID, MAKE_LWPID_PTHREAD(getThreadHandle()),
-			num, NULL) == 0
+
+	return processor_bind(P_LWPID, P_MYID, proc_number, NULL) == 0
+
 #elif defined(_AIX)
-	return bindprocessor(BINDTHREAD, (tid_t) getThreadHandle(), pnumber) == 0;
+
+	return bindprocessor(BINDTHREAD, m_kernel_thread_id, proc_number) == 0;
+
 #elif defined(__hpux) || defined(hpux)
+
 	pthread_spu_t answer;
 
 	return pthread_processor_bind_np(PTHREAD_BIND_ADVISORY_NP,
-			&answer, num, getThreadHandle()) == 0;
+			&answer, proc_number, m_thread_handle) == 0;
+
 #elif defined(__APPLE__)
+
 	struct thread_affinity_policy tapol;
 
-	thread_port_t threadport = pthread_mach_thread_np(getThreadHandle());
-	tapol.affinity_tag = num + 1;
+	thread_port_t threadport = pthread_mach_thread_np(m_thread_handle);
+	tapol.affinity_tag = proc_number + 1;
 	return thread_policy_set(threadport, THREAD_AFFINITY_POLICY,
 			(thread_policy_t)&tapol,
 			THREAD_AFFINITY_POLICY_COUNT) == KERN_SUCCESS;
+
 #else
+
 	return false;
+
 #endif
 }
 
@@ -336,19 +432,23 @@ bool Thread::bindToProcessor(unsigned int num)
 bool Thread::setPriority(int prio)
 {
 #if defined(_WIN32)
-	return SetThreadPriority(getThreadHandle(), prio);
+
+	return SetThreadPriority(m_thread_handle, prio);
+
 #else
+
 	struct sched_param sparam;
 	int policy;
 
-	if (pthread_getschedparam(getThreadHandle(), &policy, &sparam) != 0)
+	if (pthread_getschedparam(m_thread_handle, &policy, &sparam) != 0)
 		return false;
 
 	int min = sched_get_priority_min(policy);
 	int max = sched_get_priority_max(policy);
 
 	sparam.sched_priority = min + prio * (max - min) / THREAD_PRIORITY_HIGHEST;
-	return pthread_setschedparam(getThreadHandle(), policy, &sparam) == 0;
+	return pthread_setschedparam(m_thread_handle, policy, &sparam) == 0;
+
 #endif
 }
 
diff --git a/src/threading/thread.h b/src/threading/thread.h
index 275bc9b6d..3c8447b53 100644
--- a/src/threading/thread.h
+++ b/src/threading/thread.h
@@ -28,91 +28,136 @@ DEALINGS IN THE SOFTWARE.
 
 #include "threading/atomic.h"
 #include "threading/mutex.h"
+#include "threads.h"
 
 #include <string>
-#if __cplusplus >= 201103L
+#if USE_CPP11_THREADS
 	#include <thread>
 #endif
 
-#ifndef _WIN32
-enum {
-	THREAD_PRIORITY_LOWEST,
-	THREAD_PRIORITY_BELOW_NORMAL,
-	THREAD_PRIORITY_NORMAL,
-	THREAD_PRIORITY_ABOVE_NORMAL,
-	THREAD_PRIORITY_HIGHEST,
-};
+/*
+ * On platforms using pthreads, these five priority classes correlate to
+ * even divisions between the minimum and maximum reported thread priority.
+ */
+#if !defined(_WIN32)
+	#define THREAD_PRIORITY_LOWEST       0
+	#define THREAD_PRIORITY_BELOW_NORMAL 1
+	#define THREAD_PRIORITY_NORMAL       2
+	#define THREAD_PRIORITY_ABOVE_NORMAL 3
+	#define THREAD_PRIORITY_HIGHEST      4
 #endif
 
 
-class Thread
-{
+class Thread {
 public:
-	Thread(const std::string &name="Unnamed");
-	virtual ~Thread() { kill(); }
+	Thread(const std::string &name="");
+	virtual ~Thread();
 
+	/*
+	 * Begins execution of a new thread at the pure virtual method Thread::run().
+	 * Execution of the thread is guaranteed to have started after this function
+	 * returns.
+	 */
 	bool start();
-	inline void stop() { request_stop = true; }
-	bool kill();
 
-	inline bool isRunning() { return running; }
-	inline bool stopRequested() { return request_stop; }
-	void *getReturnValue() { return running ? NULL : retval; }
-	bool isSameThread();
+	/*
+	 * Requests that the thread exit gracefully.
+	 * Returns immediately; thread execution is guaranteed to be complete after
+	 * a subsequent call to Thread::wait.
+	 */
+	bool stop();
 
-	static unsigned int getNumberOfProcessors();
-	bool bindToProcessor(unsigned int);
-	bool setPriority(int);
+	/*
+	 * Immediately terminates the thread.
+	 * This should be used with extreme caution, as the thread will not have
+	 * any opportunity to release resources it may be holding (such as memory
+	 * or locks).
+	 */
+	bool kill();
 
 	/*
-	 * Wait for thread to finish.
-	 * Note: this does not stop a thread, you have to do this on your own.
+	 * Waits for thread to finish.
+	 * Note:  This does not stop a thread, you have to do this on your own.
 	 * Returns immediately if the thread is not started.
 	 */
 	void wait();
 
+	/*
+	 * Returns true if the calling thread is this Thread object.
+	 */
+	bool isCurrentThread();
+
+	inline bool isRunning() { return m_running; }
+	inline bool stopRequested() { return m_request_stop; }
+	inline threadid_t getThreadId() { return m_thread_id; }
+	inline threadhandle_t getThreadHandle() { return m_thread_handle; }
+
+	/*
+	 * Gets the thread return value.
+	 * Returns true if the thread has exited and the return value was available,
+	 * or false if the thread has yet to finish.
+	 */
+	bool getReturnValue(void **ret);
+
+	/*
+	 * Binds (if possible, otherwise sets the affinity of) the thread to the
+	 * specific processor specified by proc_number.
+	 */
+	bool bindToProcessor(unsigned int proc_number);
+
+	/*
+	 * Sets the thread priority to the specified priority.
+	 *
+	 * prio can be one of: THREAD_PRIORITY_LOWEST, THREAD_PRIORITY_BELOW_NORMAL,
+	 * THREAD_PRIORITY_NORMAL, THREAD_PRIORITY_ABOVE_NORMAL, THREAD_PRIORITY_HIGHEST.
+	 * On Windows, any of the other priorites as defined by SetThreadPriority
+	 * are supported as well.
+	 *
+	 * Note that it may be necessary to first set the threading policy or
+	 * scheduling algorithm to one that supports thread priorities if not
+	 * supported by default, otherwise this call will have no effect.
+	 */
+	bool setPriority(int prio);
+
+	/*
+	 * Sets the currently executing thread's name to where supported; useful
+	 * for debugging.
+	 */
 	static void setName(const std::string &name);
 
+	/*
+	 * Returns the number of processors/cores configured and active on this machine.
+	 */
+	static unsigned int getNumberOfProcessors();
+
 protected:
-	std::string name;
+	std::string m_name;
 
 	virtual void *run() = 0;
 
 private:
-	void setName() { setName(name); }
-
-	void *retval;
-	Atomic<bool> request_stop;
-	Atomic<bool> running;
-	Mutex continue_mutex;
-
-#if __cplusplus >= 201103L
-	static void theThread(Thread *th);
-
-	std::thread *thread;
-	std::thread::native_handle_type getThreadHandle() const
-		{ return thread->native_handle(); }
-#else
-# if defined(WIN32) || defined(_WIN32_WCE)
-#  ifdef _WIN32_WCE
-	DWORD thread_id;
-	static DWORD WINAPI theThread(void *param);
-#  else
-	UINT thread_id;
-	static UINT __stdcall theThread(void *param);
-#  endif
-
-	HANDLE thread;
-	HANDLE getThreadHandle() const { return thread; }
-# else // pthread
-	static void *theThread(void *param);
-
-	pthread_t thread;
-	pthread_t getThreadHandle() const { return thread; }
-
-	Atomic<bool> started;
-# endif
+	void *m_retval;
+	Atomic<bool> m_request_stop;
+	Atomic<bool> m_running;
+	Mutex m_continue_mutex;
+
+	threadid_t m_thread_id;
+	threadhandle_t m_thread_handle;
+
+	void cleanup();
+
+	static ThreadStartFunc threadProc;
+
+#ifdef _AIX
+	// For AIX, there does not exist any mapping from pthread_t to tid_t
+	// available to us, so we maintain one ourselves.  This is set on thread start.
+	tid_t m_kernel_thread_id;
+#endif
+
+#if USE_CPP11_THREADS
+	std::thread *m_thread_obj;
 #endif
+
 };
 
 #endif
-- 
cgit v1.2.3