Boost logo

Boost :

Subject: [boost] [thread] thread_specific_ptr leaks on VS2012 when using std::async()
From: Klaim - Joël Lamotte (mjklaim_at_[hidden])
Date: 2012-09-05 04:33:26


Hi,

The title says it all.
I think I found a memory leak with the help of boost.log author Andrei
Semashev.
I was using Boost.Log in Visual Studio 2012 (released one) and tried to use
Visual Leak Detector because Visual Studio basic leak detector was
reporting leaks.
I isolated the leaks to usage of log and switched to VLD to check if the
basic detector wasn't giving me false positive.

I contacted Andrei to see if he could check it but he don't have VS2012
available so I setup some tests to isolate the problem.
In the end of our exchange we concluded it was a false positive (it only
leak on end of program).
However it is still an annoying noise in the leak report logs, so people
not understanding the source of this problem might be interested in this
email.

I attach the whole email exchange to this email, tell me if leak logs are
necessary too.

The last version of my test is at the end of the email (first program).
Basically, the leak appear only when using std::async() in combination of
boost::thread_specific_ptr :
 - using exclusively boost threading libraries (both with dynamic and
static linking) result in no leak
 - using exclusively std threading libraries but not std::async() result in
no leak
 - using std::async() result in a leak

The second test program shows that using Intel Threading Building Blocks, I
get leaks only if :
 - I use std::async() in combination with boost::thread_specific_ptr, as
before (I mean I use tbb without a leak, then I add a std::async() call to
the same task function and there is a leak)
 - I don't use std::async() but I don't manage the lifetime of tbb task
scheduler

We think the threads spawned by both tbb (that is supposed to be a superset
of ppl) and std::async() (that is supposed to use ppl/concrt task
scheduler) are not ended yet
when the program reach the end of main (which seems logical as the task
scheduler is then managed as a static global),
making boost::thread_specific_ptr not release some data on thread
destruction, as it is notified too late and any
leak detection tool willl generate noise due to this.

A way to fix this is to explicitely end the task scheduler, being tbb or
ppl/concrt.
I will use tbb task scheduler explicitely initialized/terminated in my
project so it will solve the problem on my side.

Joel Lamotte
------------------------------------
// this program was used to see the memory leak

#define TEST_ASYNC // comment this to make it linear and not leak
//#define WITH_BOOST_THREADS
#define WITH_STD_ASYNC // uncomment this to leak
//#define WITH_BOOST_LOG // boost log also leak if std::async is used

#include <boost/thread.hpp>
#ifdef WITH_BOOST_THREADS
# include <boost/thread/future.hpp>
# include <boost/chrono.hpp>
# define THIS_THREAD boost::this_thread
# define THREAD_LIB boost::thread
# define CHRONO_LIB boost::chrono
# define MY_FUTURE boost::future
#else
# include <future>
# include <thread>
# include <chrono>
# define THIS_THREAD std::this_thread
# define THREAD_LIB std::thread
# define CHRONO_LIB std::chrono
# define MY_FUTURE std::future
#endif

#ifdef WITH_BOOST_LOG
# include <boost/log/trivial.hpp>
# define MY_LOG BOOST_LOG_TRIVIAL(info)
#else
# include <iostream>
# define MY_LOG std::cout << '\n'
#endif

#define BASIC_VS_LEAK_DETECTOR 0
#define VISUAL_LEAK_DETECTOR 1

#define LEAK_DETECTOR VISUAL_LEAK_DETECTOR

#if LEAK_DETECTOR == BASIC_VS_LEAK_DETECTOR

# define _CRTDBG_MAP_ALLOC
# include <stdlib.h>
# include <crtdbg.h>

#else

# include "vld.h"

#endif

struct my_data
{
char data[1024];

my_data()
{
MY_LOG << "CONSTRUCTOR my_data - " << THIS_THREAD::get_id();
}
~my_data()
{
MY_LOG << "DESTRUCTOR my_data - " << THIS_THREAD::get_id();
}
};

boost::thread_specific_ptr< my_data > ts_ptr;

void my_thread_foo()
{
ts_ptr.reset(new my_data());
}

int work()
{
for( int i = 0; i < 42; ++i )
{
MY_LOG << "THIS IS A MESSAGE - I = " << i;
MY_LOG << "ts_ptr = " << ts_ptr.get();
my_thread_foo();
THIS_THREAD::sleep_for( CHRONO_LIB::milliseconds(10) );
}
return 0;
}

int main(int argc, char *argv[])
{
#if LEAK_DETECTOR == BASIC_VS_LEAK_DETECTOR
// First simple memory leak detection for Visual Studio
// use _crtBreakAlloc to put a breakpoint on the provided memory leak id
allocation
//_crtBreakAlloc = 1149;
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

//int* k = new int[42]; // to check that memory leak detection does work

MY_LOG << "Hello, World!";

#ifdef TEST_ASYNC
# ifndef WITH_BOOST_THREADS
 std::vector< std::future<int> > work_in_progress(2);

# ifdef WITH_STD_ASYNC
for( auto& ftr : work_in_progress )
{
ftr = std::async( work );
}
# else
for( auto& ftr : work_in_progress )
{
std::packaged_task< int() > task(work);
ftr = task.get_future();
std::thread thread( std::move(task) );
thread.detach();
}
# endif

for( auto& ftr : work_in_progress )
{
ftr.get();
}
# else
std::vector< boost::unique_future<int> > work_in_progress(42);

for( auto& ftr : work_in_progress )
{
boost::packaged_task<int> task(work); // boost's packaged_task don't take
the full signature as parameter, it takes the return type only
ftr = task.get_future();
boost::thread thread( boost::move(task) );
thread.detach();
}

for( auto& ftr : work_in_progress )
{
ftr.get();
}
# endif
#else

for( int i = 0; i < 42; ++i )
{
work();
}

#endif

MY_LOG << "END";

return EXIT_SUCCESS;
}

--------------------------------------
// this program tests if the memory leaks appear with Threading Building
Blocks

#define BASIC_VS_LEAK_DETECTOR 0
#define VISUAL_LEAK_DETECTOR 1

#define LEAK_DETECTOR VISUAL_LEAK_DETECTOR

#define WITH_ROOT_TASK
//#define WITH_BOOST_LOG
//#define WITH_STD_ASYNC // uncomment to leak

#ifdef WITH_STD_ASYNC
# include <future>
#endif

#include <thread>
#include <atomic>
#include <boost/thread.hpp>

#include <tbb/tbb.h>
#include <tbb/task_scheduler_init.h>

#ifdef WITH_BOOST_LOG
# include <boost/log/trivial.hpp>
# define MY_LOG BOOST_LOG_TRIVIAL(info)
#else
# include <iostream>
# define MY_LOG std::cout << '\n'
#endif

#if LEAK_DETECTOR == BASIC_VS_LEAK_DETECTOR

# define _CRTDBG_MAP_ALLOC
# include <stdlib.h>
# include <crtdbg.h>

#else

# include "vld.h"

#endif

namespace test
{

struct my_data
{
char data[1024];
};

boost::thread_specific_ptr< my_data > ts_ptr;

void my_thread_foo()
{
ts_ptr.reset(new my_data());
}

class UpdateTask
: public tbb::task
{
 public:

UpdateTask()
: idx( s_idx++ )
{
MY_LOG << "[UpdateTask " << idx << " ] " << std::endl;
 }

~UpdateTask()
{
MY_LOG << "[/UpdateTask " << idx << " ]" << std::endl;
}

tbb::task* execute()
{
MY_LOG << "[" << std::this_thread::get_id() << "] Tick "<< m_count
<<std::endl;

my_thread_foo();
++m_count;

if( m_count < 100 )
{
// make sure this is re-executed
this->increment_ref_count();
this->recycle_as_safe_continuation();
}

return nullptr;
}

private:

static std::atomic<int> s_idx;
std::atomic<int> m_count;
const int idx;
};

std::atomic<int> UpdateTask::s_idx = 0;

class TaskScheduler // using TBB
{
public:

#ifdef WITH_ROOT_TASK
TaskScheduler()

: m_root_task( *new (tbb::task::allocate_root()) tbb::empty_task )
{
m_root_task.increment_ref_count();
 }

~TaskScheduler()
{
m_root_task.destroy(m_root_task);
}
#endif

void register_update_task()
{
#ifdef WITH_ROOT_TASK
m_root_task.increment_ref_count();
UpdateTask& updater = *new(m_root_task.allocate_child()) UpdateTask();
#else
UpdateTask& updater = *new(tbb::task::allocate_root()) UpdateTask();
#endif
m_task_list.push_back( updater );
}

void run_and_wait()
{
#ifdef WITH_ROOT_TASK
m_root_task.spawn( m_task_list );
m_root_task.wait_for_all();
#else
tbb::task::spawn_root_and_wait( m_task_list );
#endif
}

private:
tbb::task_list m_task_list;
// uncomment the following line to not leak tbb
//tbb::task_scheduler_init m_engine_instance; // We explicitly create and
destroy the TBB task scheduler to avoid memory leak warning noises.
#ifdef WITH_ROOT_TASK
tbb::task& m_root_task;
#endif
};

void tbb_test()
{
TaskScheduler task_scheduler;

for( int i = 0; i < 50; ++i )
{
task_scheduler.register_update_task();
}

task_scheduler.run_and_wait();

}

}

int main(int argc, char *argv[])
{
#if LEAK_DETECTOR == BASIC_VS_LEAK_DETECTOR
// First simple memory leak detection for Visual Studio
// use _crtBreakAlloc to put a breakpoint on the provided memory leak id
allocation
//_crtBreakAlloc = 148;
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

#ifdef WITH_STD_ASYNC
std::atomic<bool> finished = false;
std::async( [&]{
while( !finished )
{
MY_LOG << "POP";
test::my_thread_foo();
std::this_thread::sleep_for( std::chrono::milliseconds(500) );
}
} );

#endif

test::tbb_test();

#ifdef WITH_STD_ASYNC
finished = true;
#endif

MY_LOG << "\nEND";
std::this_thread::sleep_for( std::chrono::seconds(4) );

return 0;
}




Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk