Klaim J. Lamotte [Boost][log] VS2012 21 messages Klaim - Joël Lamotte Tue, Sep 4, 2012 at 12:22 AM To: Andrey Semashev Hi Andrey, Today I installed the final version of Visual Studio 2012. I also updated my boost.log version to the last trunk. I updated boost to 1.51.0. I mailed you before about a problem related to std::locale not providing correct data with VS2012 RC. I was trying to use file rotation based on date, if you remember. I re-tested the code showing the problem and now it don't appear, I don't get any exception and the log file have a correct date as file name. So it's fixed. However, since I first tried boost log, I'm having memory leaks that appear only when logging from multiple threads. I first noticed this at the time I mailed you, but as you concluded that the STL had problems with streams, and as I have seen but report names for VS2012RC that suggests that standard streams cannot be used concurrently (see http://blogs.msdn.com/b/vcblog/archive/2012/06/15/10320846.aspx), I was waiting to get the fixed Visual Studio version to check again these leaks. Maybe the leaks had the same souce than the standard streams bug. Now that I upgraded, I see that the leaks are still here. So I did some tests. I first wrote a simple test (at the end of this email) that use the trivial logging, no concurrency: no leak. Then I made this test parrallel : leak. (see the macro to switch from one to the other) Then I rewrote the parallel version in a way allowing me to compile a VS2010 version, still using trivial logging: no leak. (I used boost thread and mutex instead of futures and async as they are not available in VS2010) I took the test I put online before (the one with one library using boost log and other binaries using this library) and checked it: leak (a lot actually, but because it logs a lot too and from a lot of threads). I remarked before that these leaks were from boost log because the leaking data seems mostly string content, the messages I logged. As said before, I first thought it was related to the stream bug but I can't check because the bug report isn't public. So far I think it might be either: 1. boost log trivial implementation that setup something that leaks 2. boost log that leaks the messages when passing them between threads 3. the standard library having a bug again. It's hard to check if it's the last solution, and I tried to make simple parallel stream usage tests without being able to reproduce the problem, so I think it might really be a boost log problem that would appear only when using a C++11 standard library implementation and compiler. Obviously I don't have enough data to check what exactly go wrong. Are you aware about this problem with VS2012 (the final one)? Also I wanted to ask if the bleeding edge branch is usable already or if it is not stable enough yet? Here is the simple test to use in VS2012 (console applcation) to reveal the problem: ---------------------------------- #define TEST_ASYNC // comment this to make it linear and not leak #include #include #include #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #define _CRTDBG_MAP_ALLOC #include #include void work() { for( int i = 0; i < 42; ++i ) { BOOST_LOG_TRIVIAL( info ) << "THIS IS A MESSAGE - I = " << i; std::this_thread::sleep_for( std::chrono::milliseconds(10) ); } } int main(int argc, char *argv[]) { // 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 ); //int* k = new int[42]; // to check that memory leak detection does work BOOST_LOG_TRIVIAL(info) << "Hello, World!"; #ifdef TEST_ASYNC std::vector< std::future > work_in_progress(42); for( auto& ftr : work_in_progress ) { ftr = std::async( work ); } for( auto& ftr : work_in_progress ) { ftr.get(); } #else for( int i = 0; i < 42; ++i ) { work(); } #endif BOOST_LOG_TRIVIAL(info) << "END"; return EXIT_SUCCESS; } ------------------------------------------------------------------------ Hope it helps. Joel Lamotte Klaim - Joël Lamotte Tue, Sep 4, 2012 at 2:34 PM To: Andrey Semashev Note that it could also be a false-positive, due to the basic visual studio debugger (see last section there: http://msdn.microsoft.com/en-us/library/x98tx3cf(v=vs.110).aspx ) I'm wondering if it is because I get some similar problems with tbb (so it could also be the standard library...) However, tbb is certainly allocating some things in a special way, while boost log, as far as I know, use standard allocation all the way, right? I'll try to use Visual Leak Detector later, I need to drop temporarily this problem. Joel Lamotte Klaim - Joël Lamotte Tue, Sep 4, 2012 at 3:06 PM To: Andrey Semashev In the end I couldn't resist trying with Visual Leak Detector (with VS2012) and modified the test I posted to make it work with it. What I found is that VLD does find the leaks too, but provide far more interesting infos. I executed the following program several times (in Debug mode) and you can read the ouput form VS and VLD in the files I'm joining to this mail. I tried to find the leak but I think you will be better placed to find it. It seems related to some deleter objects though. Also, I checked with non-threaded use of the same program (see the macros) and it does not leak, as said before. Hope it helps. --------------------------------------------------------------------- #define TEST_ASYNC // comment this to make it linear and not leak #include #include #include #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 # include #else # include "vld.h" #endif void work() { for( int i = 0; i < 42; ++i ) { BOOST_LOG_TRIVIAL( info ) << "THIS IS A MESSAGE - I = " << i; std::this_thread::sleep_for( std::chrono::milliseconds(10) ); } } 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 BOOST_LOG_TRIVIAL(info) << "Hello, World!"; #ifdef TEST_ASYNC std::vector< std::future > work_in_progress(42); for( auto& ftr : work_in_progress ) { ftr = std::async( work ); } for( auto& ftr : work_in_progress ) { ftr.get(); } #else for( int i = 0; i < 42; ++i ) { work(); } #endif BOOST_LOG_TRIVIAL(info) << "END"; return EXIT_SUCCESS; } Test_LogLeaksVS2012.zip 202K Andrey Semashev Tue, Sep 4, 2012 at 4:11 PM To: Klaim - Joël Lamotte On Tuesday 04 September 2012 15:06:37 you wrote: > In the end I couldn't resist trying with Visual Leak Detector (with VS2012) > and modified the test I posted to make it work with it. > > What I found is that VLD does find the leaks too, but provide far more > interesting infos. > I executed the following program several times (in Debug mode) and you can > read the ouput form VS and VLD in the files I'm joining to this mail. > > I tried to find the leak but I think you will be better placed to find it. > It seems related to some deleter objects though. > Also, I checked with non-threaded use of the same program (see the macros) > and it does not leak, as said before. > > Hope it helps. First, thank you for your feedback. Second, I currently don't have a setup with VS 2012 to test so my suggestions are mostly theoretical. I've taken a look at a few VLD reports you attached and most (if not all) stack traces indicate that the memory is allocated for thread-specific data, either by Boost.Thread or by Boost.Log. You can verify this by trying this 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()); } Try calling my_thread_foo in std::async or sequentially. If it leaks in std::async case then there's the problem. If this theory is confirmed then the problem is that when std::future completes the thread-specific cleanup functions are not called. This may be caused by linking Boost.Thread statically (AFAIR, Boost.Thread uses some MSVC- specific technique in this case to hook into thread termination to invoke the cleanup; this may no longer be valid for VS2012). When linked dynamically, Boost.Thread uses another approach that relies on core Windows behavior and should not depend on a particular MSVC version. You can try is it solves the problem. If that doesn't help, can you try your initial test with boost::future and plain boost::thread instead of std::async? It is possible that std::async behaves differently to what I expect (i.e. it does not necessarilly start and terminate a thread). Klaim - Joël Lamotte Tue, Sep 4, 2012 at 4:49 PM To: Andrey Semashev On Tue, Sep 4, 2012 at 4:11 PM, Andrey Semashev wrote: First, thank you for your feedback. No problem, I like to hunt memory leaks. Second, I currently don't have a setup with VS 2012 to test so my suggestions are mostly theoretical. I've taken a look at a few VLD reports you attached and most (if not all) stack traces indicate that the memory is allocated for thread-specific data, either by Boost.Thread or by Boost.Log. You can verify this by trying this 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()); } Try calling my_thread_foo in std::async or sequentially. If it leaks in std::async case then there's the problem. I modified the test code (see the end of this email) and it apparently does leak only with async. I joined the vs logs to this email. I also modified the async code to only do 2 calls to async, to get less dumps. If this theory is confirmed then the problem is that when std::future completes the thread-specific cleanup functions are not called. This may be caused by linking Boost.Thread statically (AFAIR, Boost.Thread uses some MSVC- specific technique in this case to hook into thread termination to invoke the cleanup; this may no longer be valid for VS2012). When linked dynamically, Boost.Thread uses another approach that relies on core Windows behavior and should not depend on a particular MSVC version. You can try is it solves the problem. Ok then I need to provide you some more info in my tests: 1. I always only use static version of boost libaries 2. I've seen this leak both with std::async and using tbb. In my real application, I use tbb mainly, async once. 3. In my real application, only one dll does use boost static libraries, including boost log, thread, chrono etc I have a test app that use exclusively TBB, I can check immediately see what if I use boost log in it. If that doesn't help, can you try your initial test with boost::future and plain boost::thread instead of std::async? It is possible that std::async behaves differently to what I expect (i.e. it does not necessarilly start and terminate a thread). Ok I'll try that too to isolate the problem. ---------------------------------------------- #define TEST_ASYNC // comment this to make it linear and not leak #include #include #include #include #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 # include #else # include "vld.h" #endif struct my_data { char data[1024]; }; boost::thread_specific_ptr< my_data > ts_ptr; void my_thread_foo() { ts_ptr.reset(new my_data()); } void work() { for( int i = 0; i < 42; ++i ) { //BOOST_LOG_TRIVIAL( info ) << "THIS IS A MESSAGE - I = " << i; my_thread_foo(); std::this_thread::sleep_for( std::chrono::milliseconds(10) ); } } 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 BOOST_LOG_TRIVIAL(info) << "Hello, World!"; #ifdef TEST_ASYNC std::vector< std::future > work_in_progress(2); for( auto& ftr : work_in_progress ) { ftr = std::async( work ); } for( auto& ftr : work_in_progress ) { ftr.get(); } #else for( int i = 0; i < 42; ++i ) { work(); } #endif BOOST_LOG_TRIVIAL(info) << "END"; return EXIT_SUCCESS; } ---------------------------------------------------- Joel Lamotte Test_LogLeaksVS2012_2.zip 8K Andrey Semashev Tue, Sep 4, 2012 at 5:05 PM To: Klaim - Joël Lamotte On Tuesday 04 September 2012 16:49:14 you wrote: > On Tue, Sep 4, 2012 at 4:11 PM, Andrey Semashev > > wrote: > > Try calling my_thread_foo in std::async or sequentially. If it leaks in > > std::async case then there's the problem. > > I modified the test code (see the end of this email) and it apparently does > leak only with async. > I joined the vs logs to this email. > I also modified the async code to only do 2 calls to async, to get less > dumps. Ok, so the problem indeed seems to be caused by not calling TLS cleanup. > > If this theory is confirmed then the problem is that when std::future > > completes the thread-specific cleanup functions are not called. This may > > be > > caused by linking Boost.Thread statically (AFAIR, Boost.Thread uses some > > MSVC- > > specific technique in this case to hook into thread termination to invoke > > the > > cleanup; this may no longer be valid for VS2012). When linked dynamically, > > Boost.Thread uses another approach that relies on core Windows behavior > > and > > should not depend on a particular MSVC version. You can try is it solves > > the > > problem. > > Ok then I need to provide you some more info in my tests: > 1. I always only use static version of boost libaries > 2. I've seen this leak both with std::async and using tbb. In my real > application, I use tbb mainly, async once. > 3. In my real application, only one dll does use boost static libraries, > including boost log, thread, chrono etc > > I have a test app that use exclusively TBB, I can check immediately see > what if I use boost log in it. I'd rather rule out TBB for now to minimize the scope of the problem. Can you try linking to Boost libraries dynamically and see if it helps? You can use the very same test code you posted. Klaim - Joël Lamotte Tue, Sep 4, 2012 at 5:15 PM To: Andrey Semashev On Tue, Sep 4, 2012 at 5:05 PM, Andrey Semashev wrote: I'd rather rule out TBB for now to minimize the scope of the problem. Can you try linking to Boost libraries dynamically and see if it helps? You can use the very same test code you posted. Sorry I didn't see your email and i already test with tbb. I'll try the dynamic libraries just after I rewrite the test with only use boost thread. The test I use to check tbb is a very simplified organizatoin of how I schedule my tasks in the real application. I'm adding the code at the end of this email. I replaced my logs using std::cout by using BOOST_LOG_TRIVIAL. The result isn't leaking at all. I couldn't manage to provoke a leak, so I did try to add a little thread executed through std::async. In this case, boost log leaks. Now I'll try the boost::thread only test, then the dynamic library test. Joel Lamotte -------------------------------------- #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 this to leak #ifdef WITH_STD_ASYNC # include #endif #include #include #include #include #ifdef WITH_BOOST_LOG # include # define MY_LOG BOOST_LOG_TRIVIAL(info) #else # include # define MY_LOG std::cout << '\n' #endif #if LEAK_DETECTOR == BASIC_VS_LEAK_DETECTOR # define _CRTDBG_MAP_ALLOC # include # include #else # include "vld.h" #endif namespace test { 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 <increment_ref_count(); this->recycle_as_safe_continuation(); } return nullptr; } private: static std::atomic s_idx; std::atomic m_count; const int idx; }; std::atomic 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; 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 finished = false; std::async( [&]{ while( !finished ) { MY_LOG << "POP"; 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; } Klaim - Joël Lamotte Tue, Sep 4, 2012 at 5:37 PM To: Andrey Semashev I just tried with only boost thread and future, I don't get any leak. (still static libs though). I wonder if not using async but still using the standard library would work. I'll tell you soon for the other tests. -------------------------------------------------------------------- #define TEST_ASYNC // comment this to make it linear and not leak #define WITH_BOOST_THREADS #ifdef WITH_BOOST_THREADS # include # include # include # define THIS_THREAD boost::this_thread # define THREAD_LIB boost::thread # define CHRONO_LIB boost::chrono # define MY_FUTURE boost::future #else # include # include # include # define THIS_THREAD std::this_thread # define THREAD_LIB std::thread # define CHRONO_LIB std::chrono # define MY_FUTURE std::future #endif #include #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 # include #else # include "vld.h" #endif struct my_data { char data[1024]; }; boost::thread_specific_ptr< my_data > ts_ptr; void my_thread_foo() { ts_ptr.reset(new my_data()); } void work() { for( int i = 0; i < 42; ++i ) { BOOST_LOG_TRIVIAL( info ) << "THIS IS A MESSAGE - I = " << i; my_thread_foo(); THIS_THREAD::sleep_for( CHRONO_LIB::milliseconds(10) ); } } 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 BOOST_LOG_TRIVIAL(info) << "Hello, World!"; #ifdef TEST_ASYNC # ifndef WITH_BOOST_THREADS std::vector< std::future > work_in_progress(42); for( auto& ftr : work_in_progress ) { ftr = std::async( work ); } for( auto& ftr : work_in_progress ) { ftr.get(); } # else std::vector< boost::unique_future > work_in_progress(42); for( auto& ftr : work_in_progress ) { boost::packaged_task task(work); 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 BOOST_LOG_TRIVIAL(info) << "END"; return EXIT_SUCCESS; } Klaim - Joël Lamotte Tue, Sep 4, 2012 at 5:57 PM To: Andrey Semashev I don't get any leak either using std::thread without std::async... Looks like it's VS2012's std::async that does something that boost::thread_specific_ptr don't like. In this code there is all the test cases. Only if WITH_STD_ASYNC is defined and WITH_BOOST_THREADS is not defined there are leaks (from both boost log and the thread_specific_ptr test). For the dynamic libraries test, it will take some time because I need to recompile boost and I'm not sure yet how to proceed. --------------------------------------------------------------------- #define TEST_ASYNC // comment this to make it linear and not leak //#define WITH_BOOST_THREADS //#define WITH_STD_ASYNC //uncomment this to leak #include #ifdef WITH_BOOST_THREADS [Quoted text hidden] int work() { for( int i = 0; i < 42; ++i ) { BOOST_LOG_TRIVIAL( info ) << "THIS IS A MESSAGE - I = " << i; 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 BOOST_LOG_TRIVIAL(info) << "Hello, World!"; #ifdef TEST_ASYNC # ifndef WITH_BOOST_THREADS std::vector< std::future > work_in_progress(42); # 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 > work_in_progress(42); for( auto& ftr : work_in_progress ) { boost::packaged_task 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 BOOST_LOG_TRIVIAL(info) << "END"; return EXIT_SUCCESS; } Klaim - Joël Lamotte Tue, Sep 4, 2012 at 7:28 PM To: Andrey Semashev I managed to build all boost libs with shared library. I got exactly the same results: It's only if std::async that is used that I got a leak. All other variations of the last test code I sent you does work but with std::async. Shouldn't we notify boost thread author about this? See if it's on his side or if it's async implementation that does something wrong? Joel Lamotte Andrey Semashev Tue, Sep 4, 2012 at 8:16 PM To: Klaim - Joël Lamotte On Tuesday 04 September 2012 19:28:33 you wrote: > I managed to build all boost libs with shared library. > > I got exactly the same results: > It's only if std::async that is used that I got a leak. All other > variations of the last test code I sent you does work but with std::async. Thank you for your thorough testing. Yes, it seems it is caused by std::async or PPL implementation. Apparently, futures are built on top of PPL and somehow it breaks TLS in Boost.Thread. I don't know much about PPL but I suspect it doesn't create a thread for every task it runs, that would explain why the cleanup is not run. OTOH, this means that Boost.Thread can't get the right pointer to TLS when called from a PPL task, which results in reinitializing the TLS and leaking the previous TLS state. This needs more research. First, we can verify that TLS cleanup is run _at_all_ at the end of the application. You can add an output to the console to my_data destructor to see that. If it is run at least once then my theory about a pool of threads in PPL is right and we should look for why Boost.Thread doesn't find TLS state from the task. BTW, we can also check if this is true by printing thread_specific_ptr value before initializing it in my_thread_foo. > Shouldn't we notify boost thread author about this? See if it's on his side > or if it's async implementation that does something wrong? Yes, I think posting on the Boost Dev mailing list and creating a ticket is a good idea. At least someone could have faced this problem already. Klaim - Joël Lamotte Wed, Sep 5, 2012 at 8:56 AM To: Andrey Semashev On Tue, Sep 4, 2012 at 8:16 PM, Andrey Semashev wrote: First, we can verify that TLS cleanup is run _at_all_ at the end of the application. You can add an output to the console to my_data destructor to see that. If it is run at least once then my theory about a pool of threads in PPL is right and we should look for why Boost.Thread doesn't find TLS state from the task. BTW, we can also check if this is true by printing thread_specific_ptr value before initializing it in my_thread_foo. I did these modifications, you can see the result in the joined log when I execute it. The destructors are called. However I'm not sure how to interpret the smart pointer value, so I'll let you do it. > Shouldn't we notify boost thread author about this? See if it's on his side > or if it's async implementation that does something wrong? Yes, I think posting on the Boost Dev mailing list and creating a ticket is a good idea. At least someone could have faced this problem already. Okay I'll do it in the coming hours. Joel Lamotte output_3_1.txt 46K Klaim - Joël Lamotte Wed, Sep 5, 2012 at 8:56 AM To: Andrey Semashev I forgot to add the code from my last modification: #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 #ifdef WITH_BOOST_THREADS # include # include # define THIS_THREAD boost::this_thread # define THREAD_LIB boost::thread # define CHRONO_LIB boost::chrono # define MY_FUTURE boost::future #else # include # include # include # 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 # define MY_LOG BOOST_LOG_TRIVIAL(info) #else # include # 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 # include #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 BOOST_LOG_TRIVIAL(info) << "Hello, World!"; #ifdef TEST_ASYNC # ifndef WITH_BOOST_THREADS std::vector< std::future > work_in_progress(2); [Quoted text hidden] MY_LOG << "END"; return EXIT_SUCCESS; } Klaim - Joël Lamotte Wed, Sep 5, 2012 at 9:15 AM To: Andrey Semashev Wrong code, this is the fixed version I'm sending with my mail to boost dev : #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 [Quoted text hidden] MY_LOG << "Hello, World!"; [Quoted text hidden] Andrey Semashev Wed, Sep 5, 2012 at 9:15 AM To: Klaim - Joël Lamotte On Wed, Sep 5, 2012 at 10:56 AM, Klaim - Joël Lamotte wrote: > > > On Tue, Sep 4, 2012 at 8:16 PM, Andrey Semashev > wrote: >> >> First, we can verify that TLS cleanup is run _at_all_ at the end of the >> application. You can add an output to the console to my_data destructor to >> see >> that. If it is run at least once then my theory about a pool of threads in >> PPL >> is right and we should look for why Boost.Thread doesn't find TLS state >> from >> the task. BTW, we can also check if this is true by printing >> thread_specific_ptr value before initializing it in my_thread_foo. >> > > I did these modifications, you can see the result in the joined log when I > execute it. > The destructors are called. Hmm, so it seems the thread_specific_ptr works as expected, my_data is deleted. Only the last 2 destructors are missing (by the number of threads, as I can see), and these 2 my_data instances are present in the VLD report. I think this is normal because PPL threads may not terminate until after VLD produces its report. Are you sure you have a memory leak (i.e. the memory consumption is steadily climbing) when the test with only thread_specific_ptr is run? > However I'm not sure how to interpret the smart pointer value, so I'll let > you do it. I wanted to see the value of the pointer _within_ the my_thread_foo function, before resetting it. But since the destructors are called, it is obvious that the pointer behaves correctly (i.e. it is initialized at that point). Klaim - Joël Lamotte Wed, Sep 5, 2012 at 9:38 AM To: Andrey Semashev On Wed, Sep 5, 2012 at 9:15 AM, Andrey Semashev wrote: Hmm, so it seems the thread_specific_ptr works as expected, my_data is deleted. Only the last 2 destructors are missing (by the number of threads, as I can see), and these 2 my_data instances are present in the VLD report. I think this is normal because PPL threads may not terminate until after VLD produces its report. Are you sure you have a memory leak (i.e. the memory consumption is steadily climbing) when the test with only thread_specific_ptr is run? No I don't see such grow but it's hard to tell, I don't have other instruments. I only see what VLD (and VS2012 basic leak detector) report. The problem is that it's noise hiding future potential memory leaks so it still have to be fixed in some way. By the way using tbb works without leaking when I use boost.log with it. It's implementation is supposed to be the same than PPL. The main difference between both is that using std::async() I have no control over the task scheduler, while in my tbb test I'm explicitely initializing and terminating the tbb task scheduler. So you might be right that the thread is still running, managed by the runtime. I just tried in my tbb test to remove the explicit control of lifetime of the task scheduler. It gives a leak. Actually I knew that and that's why I was explicitely controlling it's lifetime. So it looks like it's the problem... then I don't see how to fix it other than using ppl explicitely to end the lifetime of the task scheduler. I add the code of my tbb test to this email and the boost dev one. (the one that shouldn't leak but leak because task scheduler isn't terminated) Joel Lamotte -------------------------------- #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 #endif #include #include #include #include #include #ifdef WITH_BOOST_LOG # include # define MY_LOG BOOST_LOG_TRIVIAL(info) #else # include # define MY_LOG std::cout << '\n' #endif #if LEAK_DETECTOR == BASIC_VS_LEAK_DETECTOR # define _CRTDBG_MAP_ALLOC # include # include #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 < 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; } Andrey Semashev Wed, Sep 5, 2012 at 9:51 AM To: Klaim - Joël Lamotte On Wed, Sep 5, 2012 at 11:38 AM, Klaim - Joël Lamotte wrote: > > > On Wed, Sep 5, 2012 at 9:15 AM, Andrey Semashev > wrote: >> >> Hmm, so it seems the thread_specific_ptr works as expected, my_data is >> deleted. Only the last 2 destructors are missing (by the number of >> threads, as I can see), and these 2 my_data instances are present in >> the VLD report. I think this is normal because PPL threads may not >> terminate until after VLD produces its report. Are you sure you have a >> memory leak (i.e. the memory consumption is steadily climbing) when >> the test with only thread_specific_ptr is run? > > > No I don't see such grow but it's hard to tell, I don't have other > instruments. > I only see what VLD (and VS2012 basic leak detector) report. You can run the test for a longer time (say, 5-10 minutes) and watch it in the task manager or system performance monitor. If memory consumption grows while the test runs then we have a problem. Otherwise I would consider it as a false positive. > The problem is that it's noise hiding future potential memory leaks so it > still have to be fixed in some way. > > By the way using tbb works without leaking when I use boost.log with it. > It's implementation is supposed to be the same than PPL. > The main difference between both is that using std::async() I have no > control over the task scheduler, while in my tbb test I'm explicitely > initializing and terminating the tbb task scheduler. > > So you might be right that the thread is still running, managed by the > runtime. > > I just tried in my tbb test to remove the explicit control of lifetime of > the task scheduler. It gives a leak. > Actually I knew that and that's why I was explicitely controlling it's > lifetime. > > So it looks like it's the problem... then I don't see how to fix it other > than using ppl explicitely to end the lifetime of the task scheduler. > > I add the code of my tbb test to this email and the boost dev one. (the one > that shouldn't leak but leak because task scheduler isn't terminated) Frankly, I don't think there's anything that can be done in Boost.Thread to avoid these reports in VLD, if the problem is in PPL threads not terminated. If there is a way to explicitly terminate those threads from your code, this would be the solution. Klaim - Joël Lamotte Wed, Sep 5, 2012 at 9:54 AM To: Andrey Semashev I will check if it's leak on running and then explicit ppl termination. If it's a false positive, should I post the mail on boost dev anyway? If yes, is it ok for you if I add an archive with this discussion? Joel Lamotte Klaim - Joël Lamotte Wed, Sep 5, 2012 at 10:04 AM To: Andrey Semashev It don't seem to leak on runtime. Andrey Semashev Wed, Sep 5, 2012 at 10:04 AM To: Klaim - Joël Lamotte On Wed, Sep 5, 2012 at 11:54 AM, Klaim - Joël Lamotte wrote: > I will check if it's leak on running and then explicit ppl termination. > > > If it's a false positive, should I post the mail on boost dev anyway? Since it's not exactly a Boost issue, I don't think there will be much use of it. But if you need further advice, why not. OTOH, if you have Microsoft support subscription you can ask for their support with either PPL or VLD. > If yes, is it ok for you if I add an archive with this discussion? Sure. Klaim - Joël Lamotte Wed, Sep 5, 2012 at 10:15 AM To: Andrey Semashev On Wed, Sep 5, 2012 at 10:04 AM, Andrey Semashev wrote: Since it's not exactly a Boost issue, I don't think there will be much use of it. But if you need further advice, why not. OTOH, if you have Microsoft support subscription you can ask for their support with either PPL or VLD. Ok I'll post on both then. I just checked this page: http://msdn.microsoft.com/en-us/library/gg663535.aspx It explains how to manipulate the task manager. I will not do it in the end as it's not in my interest at all, I will just use tbb only and explicitely release resources. Thanks for your help. Joel Lamotte