Skip to content

Memory leak #185

Description

@dipterix

Found previous closed issues (#81 #175).

I think the tinythread is leaking as well. I don't have TBB installed, but running parallelFor leaks ~10MB per 800 threads (16KB/thread). Here are some information that might be useful:

1. Print out when threads are joined/detached

In function ttParallelFor, before threads[i]->join();, I added the following code to print out thread handle

std::cout << "mNotAThread: " << threads[i]->native_handle() << " " << threads[i]->native_handle() << "\n";

In tinythread.h, added printing information:

  // Create the thread
#if defined(_TTHREAD_WIN32_)
  mHandle = (HANDLE) _beginthreadex(0, 0, wrapper_function, (void *) ti, 0, &mWin32ThreadID);
#elif defined(_TTHREAD_POSIX_)
  if(pthread_create(&mHandle, NULL, wrapper_function, (void *) ti) != 0) {
    mHandle = 0;
  } else {
    std::cout << "Threading " << mHandle << "\n";
  }

#endif

Here's the result:

Threading 0x16bf6f000
Threading 0x16bffb000
Threading 0x16c087000
Threading 0x16c113000
Threading 0x16c19f000
Threading 0x16c22b000
Threading 0x16c2b7000
Threading 0x16c343000
mNotAThread: 0x16bf6f000 1
mNotAThread: 0x16bffb000 1
mNotAThread: 0x16c087000 1
mNotAThread: 0x16c113000 1
mNotAThread: 0x16c19f000 1
mNotAThread: 0x16c22b000 1
mNotAThread: 0x16c2b7000 1
mNotAThread: 0x16c343000 1
[1]  205.41996 -302.72300  -30.53115 -408.31099 -227.63973  292.03780
[7]   48.60666  108.68428

Since the printing lines execute before join(), and I could not find join() calls elsewhere. I guess this means the threads are not joined at all.

Even though join() function is indeed called explicitly. The mNotAThread is set to true before join(). The result is: joinable() returns false and the thread was not joined nor detached, hence memory leak.

2. When is mNotAThread set to true?

There are 3 places:

  • When pthread_create errored out
  • When the thread is marked as detached
  • In thread::wrapper_function, after calling ti->mFunction(ti->mArg);

The first two are pretty legit: if the thread does not exist, then there is no join/detach; if the thread is detached, then the resource will be collected back automatically when the thread ends. Neither will cause memory leak.

Therefore I think the culprit is the third one: when the thread starts to run mFunction, the mNotAThread is immediately set to true, and there is no join in between.

3. Trying to fix

Simply removing the two lines in thread::wrapper_function does not work. The program terminates immediately.

  lock_guard<mutex> guard(ti->mThread->mDataMutex);
  ti->mThread->mNotAThread = true;

This is because with mNotAThread set to false, inline thread::~thread() will get called and std::terminate() will shutdown the program. The code had been fine because std::terminate() never got a chance to be called (joinable() is always false).

To fix this issue, I replaced destructor with

inline thread::~thread()
{
  join();
  detach();
}

Since join does not change the flag, I choose to detach the thread (not sure if this is ok, never programed pthread before...).

I tried only call join() instead of both, surprisingly the memory leak persists. Not sure if it was caused by myself...

4. Potential issues

potential issues:

  • The thread will not call std::terminate(). So if the thread hangs, I don't know what behavior would be. However, the current code won't execute std::terminate() neither so I guess it's fine?
  • I'm using OSX, haven't tested on Linux nor Windows yet. Maybe on Windows, the behaviors are different... (such as having issues joining threads, or thread detaches/terminates before the joining)

I'm worried about stray threads... What should I do...

5. Other minor changes

To avoid taking long time to acquire the locker, I changed detach() and joinable() to:

inline bool thread::joinable() const
{
  // mDataMutex.lock();
  bool result = !mNotAThread;
  // mDataMutex.unlock();
  return result;
}

inline void thread::detach()
{
  bool isDetachable = false;
  mDataMutex.lock();
  if(!mNotAThread)
  {
    mNotAThread = true;
    isDetachable = true;
  }
  mDataMutex.unlock();

  if(isDetachable)
  {
#if defined(_TTHREAD_WIN32_)
    CloseHandle(mHandle);
#elif defined(_TTHREAD_POSIX_)
    pthread_detach(mHandle);
#endif
  }
}

Not sure if this would introduce side effects. Not sure if the following report makes sense.... Please help : )

==24919== HEAP SUMMARY:
==24919==     in use at exit: 52,156,933 bytes in 9,980 blocks
==24919==   total heap usage: 29,002 allocs, 19,022 frees, 88,950,783 bytes allocated
==24919== 
==24919== LEAK SUMMARY:
==24919==    definitely lost: 0 bytes in 0 blocks
==24919==    indirectly lost: 0 bytes in 0 blocks
==24919==      possibly lost: 0 bytes in 0 blocks
==24919==    still reachable: 52,156,933 bytes in 9,980 blocks
==24919==                       of which reachable via heuristic:
==24919==                         newarray           : 4,264 bytes in 1 blocks
==24919==         suppressed: 0 bytes in 0 blocks
==24919== Reachable blocks (those to which a pointer was found) are not shown.
==24919== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==24919== 
==24919== For lists of detected and suppressed errors, rerun with: -s
==24919== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions