Skip to content

Commit 878b6e7

Browse files
authored
Merge pull request #187 from RcppCore/bugfix/tinythread-join
fix tinythread memory leak
2 parents 1a21781 + c663396 commit 878b6e7

File tree

2 files changed

+29
-9
lines changed

2 files changed

+29
-9
lines changed

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11

2+
## RcppParallel 5.1.6 (UNRELEASED)
3+
4+
* Fixed a memory leak that could occur when using TinyThread on POSIX systems.
5+
(#185; @dipertix and and @kevinushey)
6+
27
## RcppParallel 5.1.5
38

49
* Patches to ensure compatibility with the R 4.2.0 UCRT toolchain on Windows,

inst/include/tthread/tinythread.h

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ class thread {
492492
/// Default constructor.
493493
/// Construct a @c thread object without an associated thread of execution
494494
/// (i.e. non-joinable).
495-
thread() : mHandle(0), mNotAThread(true)
495+
thread() : mHandle(0), mJoinable(false)
496496
#if defined(_TTHREAD_WIN32_)
497497
, mWin32ThreadID(0)
498498
#endif
@@ -554,7 +554,7 @@ class thread {
554554
private:
555555
native_handle_type mHandle; ///< Thread handle.
556556
mutable mutex mDataMutex; ///< Serializer for access to the thread private data.
557-
bool mNotAThread; ///< True if this object is not a thread of execution.
557+
bool mJoinable; ///< Is the thread joinable?
558558
#if defined(_TTHREAD_WIN32_)
559559
unsigned int mWin32ThreadID; ///< Unique thread ID (filled out by _beginthreadex).
560560
#endif
@@ -871,7 +871,12 @@ inline void * thread::wrapper_function(void * aArg)
871871

872872
// The thread is no longer executing
873873
lock_guard<mutex> guard(ti->mThread->mDataMutex);
874-
ti->mThread->mNotAThread = true;
874+
875+
// On POSIX, we allow the thread to be joined even after execution has finished.
876+
// This is necessary to ensure that thread-local memory can be reclaimed.
877+
#if defined(_TTHREAD_WIN32_)
878+
ti->mThread->mJoinable = false;
879+
#endif
875880

876881
// The thread is responsible for freeing the startup information
877882
delete ti;
@@ -891,8 +896,8 @@ inline thread::thread(void (*aFunction)(void *), void * aArg)
891896
ti->mArg = aArg;
892897
ti->mThread = this;
893898

894-
// The thread is now alive
895-
mNotAThread = false;
899+
// Mark thread as joinable
900+
mJoinable = true;
896901

897902
// Create the thread
898903
#if defined(_TTHREAD_WIN32_)
@@ -905,9 +910,10 @@ inline thread::thread(void (*aFunction)(void *), void * aArg)
905910
// Did we fail to create the thread?
906911
if(!mHandle)
907912
{
908-
mNotAThread = true;
913+
mJoinable = false;
909914
delete ti;
910915
}
916+
911917
}
912918

913919
inline thread::~thread()
@@ -926,28 +932,37 @@ inline void thread::join()
926932
#elif defined(_TTHREAD_POSIX_)
927933
pthread_join(mHandle, NULL);
928934
#endif
935+
936+
// https://linux.die.net/man/3/pthread_join states:
937+
//
938+
// Joining with a thread that has previously been joined results in undefined behavior.
939+
//
940+
// We just allow a thread to be joined once.
941+
mJoinable = false;
929942
}
930943
}
931944

932945
inline bool thread::joinable() const
933946
{
934947
mDataMutex.lock();
935-
bool result = !mNotAThread;
948+
bool result = mJoinable;
936949
mDataMutex.unlock();
937950
return result;
938951
}
939952

940953
inline void thread::detach()
941954
{
955+
// TODO: Attempting to detach a non-joinable thread should throw.
956+
// https://en.cppreference.com/w/cpp/thread/thread/detach
942957
mDataMutex.lock();
943-
if(!mNotAThread)
958+
if(mJoinable)
944959
{
945960
#if defined(_TTHREAD_WIN32_)
946961
CloseHandle(mHandle);
947962
#elif defined(_TTHREAD_POSIX_)
948963
pthread_detach(mHandle);
949964
#endif
950-
mNotAThread = true;
965+
mJoinable = false;
951966
}
952967
mDataMutex.unlock();
953968
}

0 commit comments

Comments
 (0)