Ask a questionAsk a question
 

AnswerConcurrency::task_group leaks memory

  • Sunday, November 01, 2009 7:13 AMAlex Farber Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Using task_group in MFC project, I get huge memory leaks report. Just creating task_group instance is enough to reproduce this. Are these leaks real or memory is released after MFC memory leaks report? How can I prevent this report to be shown, and continue to track another memory leaks, not related to task_group?
    Leaks reported also when using other resources, like reader_writer_lock.

Answers

  • Wednesday, November 04, 2009 7:12 PMAtilla GunalMSFTUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    Hi Alex,

    Let me give some brief information on the Concurrency Runtime to explain and how to overcome the leaks you are seeing.


    Underlying the user facing layer of Concurrency Runtime there are two major components; resource manager and task scheduler (see http://msdn.microsoft.com/en-us/library/dd504870(VS.100).aspx for details). Most of PPL and agent library constructs (i.e. task_group, reader_writer_lock) need an underlying task scheduler and all task schedulers need a resource manager. If the thread that uses PPL or Agent library does not have an associated scheduler, and there is no default scheduler for the process, then a task scheduler will be created implicitly (and the created scheduler will be made default). The lifetime of the implicitly created scheduler is tied to the associated thread. The destruction of the scheduler is done when the thread goes away. Therefore if the implicit scheduler is tied to the main thread it will always be shown as leaked (note that if the implicit scheduler is created on another thread then exiting the thread will start the scheduler shutdown process if there are no more references to that scheduler).

     

    As an example the following will report leaks:

     

    int _tmain(int argc, _TCHAR* argv[])

    {

     

        int tmpDbgFlag;

        tmpDbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);

        tmpDbgFlag |= _CRTDBG_LEAK_CHECK_DF;

        _CrtSetDbgFlag(tmpDbgFlag);

     

        reader_writer_lock rw;

        rw.lock();

        rw.unlock();

     

        return 0;

    }

     

    On the other hand users can explicitly create their own scheduler and control the lifetime of it. See below how to create an explicit scheduler and use events to wait for its shutdown.

     

    int _tmain(int argc, _TCHAR* argv[])

    {

        HANDLE hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

        CurrentScheduler::Create( SchedulerPolicy() );

        CurrentScheduler::RegisterShutdownEvent( hEvent );

     

        int tmpDbgFlag;

        tmpDbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);

        tmpDbgFlag |= _CRTDBG_LEAK_CHECK_DF;

        _CrtSetDbgFlag(tmpDbgFlag);

     

        reader_writer_lock rw;

     

        rw.lock();

     

        rw.unlock();

     

        CurrentScheduler::Detach();

        WaitForSingleObject( hEvent, INFINITE );

        CloseHandle( hEvent );

        Sleep(500);

     

        return 0;

    }

     

    Here we still wait for 500 ms, that is mainly for the resource manager to have its shutdown completed.

     

    Atilla

    • Marked As Answer byAlex Farber Sunday, November 08, 2009 7:11 PM
    •  
  • Wednesday, November 04, 2009 7:32 PMGenevieve FernandesMSFTUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    Expanding on what Atilla said, destruction of a scheduler is initiated when all threads associated with it exit. When you use any of the PPL or Agents and Messaging constructs on a thread, it is very likely that your thread will be associated with the Concurrency Runtime's default scheduler. So if you declared a task group on a thread other than the main thread and waited for that thread to exit, in addition to a sleep period for shutdown to complete, you would not see any leaked memory.

    Now I agree that this is a workaround, however, the alternative to not caching stuff would be to recreate resources (memory threads) each time you use a parallel construct. The caching is intentional in our design in order to boost application performance.
    • Marked As Answer byAlex Farber Sunday, November 08, 2009 7:11 PM
    •  
  • Thursday, November 05, 2009 6:41 PMAtilla GunalMSFTUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    Each worker thread needs to be associated with the same scheduler the main thread is waiting on. As an example:


    DWORD WINAPI ThreadProc( void* pSchedulerAddress )

    {

        // Associate this worker thread with the scheduler that we are waiting on main thread

        Scheduler* pScheduler = (Scheduler*)pSchedulerAddress;

        pScheduler->Attach();


        reader_writer_lock rw;

        rw.lock();

        rw.unlock();

     

        // When worker is done, it has to clear its association with the scheduler

        CurrentScheduler::Detach();

     

        return 0;

    }


    int _tmain(int argc, _TCHAR* argv[])

    {

        HANDLE hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

        CurrentScheduler::Create( SchedulerPolicy() );

        CurrentScheduler::RegisterShutdownEvent( hEvent );

     

        // Get the scheduler and pass it to the worker

        Scheduler *pScheduler = CurrentScheduler::Get();

        HANDLE hThread = CreateThread( NULL, 0, ThreadProc, pScheduler, 0, NULL );

     

        int tmpDbgFlag;

        tmpDbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);

        tmpDbgFlag |= _CRTDBG_LEAK_CHECK_DF;

        _CrtSetDbgFlag(tmpDbgFlag);

     

        reader_writer_lock rw;

        rw.lock();

        rw.unlock();

     

        WaitForSingleObject( hThread, INFINITE );

        CloseHandle( hThread );

     

        CurrentScheduler::Detach();

        WaitForSingleObject( hEvent, INFINITE );

        Sleep(500);

     

        return 0;

     }

    This is the most general way of handling the shutdown. By the way as Genevieve mentioned, the usage could be simpler only if the worker threads use Concurrency Runtime constructs. Even if you have multiple worker threads, only one of them will create the default scheduler, the others will increment reference count of it. When all workers complete, they will decrement the reference count of the default scheduler. When reference count is zero, shutdown of the scheduler and resource manager will be initiated. You should not be seeing leaks with the example below as well:

     

    DWORD WINAPI ThreadProc( void* pSchedulerAddress )

    {

        reader_writer_lock rw;

        rw.lock();

        rw.unlock();

     

        return 0;

    }

     

    int _tmain(int argc, _TCHAR* argv[])

    {

        HANDLE hThread = CreateThread( NULL, 0, ThreadProc, NULL, 0, NULL );

     

        int tmpDbgFlag;

        tmpDbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);

        tmpDbgFlag |= _CRTDBG_LEAK_CHECK_DF;

        _CrtSetDbgFlag(tmpDbgFlag);

     

        WaitForSingleObject( hThread, INFINITE );

        CloseHandle( hThread );

     

        Sleep(500);

     

        return 0;

    }


    Atilla

    • Marked As Answer byAlex Farber Sunday, November 08, 2009 7:11 PM
    •  

All Replies

  • Monday, November 02, 2009 10:55 AMAlex Farber Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Actually, any use of Concurrency Runtime objects causes memory leak report. Without MFC, using _CrtSetDbgFlag with _CRT_LEAK_CHECK_DF allows to enable memory leaks dump, and this dump aslo includes Concurrency Runtime allocations.
    My conclusion is that using Concurrency Runtime prevents memory leaks tracking. I don't see how such library can be used in professional projects without solving this problem.
  • Tuesday, November 03, 2009 3:51 AMrickmolloyMSFT, OwnerUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Thanks Alex, we'll take a look at this.

    -Rick
    Rick Molloy Parallel Computing Platform : http://blogs.msdn.com/nativeconcurrency http://parallelroads.com/blog
  • Wednesday, November 04, 2009 7:12 PMAtilla GunalMSFTUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    Hi Alex,

    Let me give some brief information on the Concurrency Runtime to explain and how to overcome the leaks you are seeing.


    Underlying the user facing layer of Concurrency Runtime there are two major components; resource manager and task scheduler (see http://msdn.microsoft.com/en-us/library/dd504870(VS.100).aspx for details). Most of PPL and agent library constructs (i.e. task_group, reader_writer_lock) need an underlying task scheduler and all task schedulers need a resource manager. If the thread that uses PPL or Agent library does not have an associated scheduler, and there is no default scheduler for the process, then a task scheduler will be created implicitly (and the created scheduler will be made default). The lifetime of the implicitly created scheduler is tied to the associated thread. The destruction of the scheduler is done when the thread goes away. Therefore if the implicit scheduler is tied to the main thread it will always be shown as leaked (note that if the implicit scheduler is created on another thread then exiting the thread will start the scheduler shutdown process if there are no more references to that scheduler).

     

    As an example the following will report leaks:

     

    int _tmain(int argc, _TCHAR* argv[])

    {

     

        int tmpDbgFlag;

        tmpDbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);

        tmpDbgFlag |= _CRTDBG_LEAK_CHECK_DF;

        _CrtSetDbgFlag(tmpDbgFlag);

     

        reader_writer_lock rw;

        rw.lock();

        rw.unlock();

     

        return 0;

    }

     

    On the other hand users can explicitly create their own scheduler and control the lifetime of it. See below how to create an explicit scheduler and use events to wait for its shutdown.

     

    int _tmain(int argc, _TCHAR* argv[])

    {

        HANDLE hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

        CurrentScheduler::Create( SchedulerPolicy() );

        CurrentScheduler::RegisterShutdownEvent( hEvent );

     

        int tmpDbgFlag;

        tmpDbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);

        tmpDbgFlag |= _CRTDBG_LEAK_CHECK_DF;

        _CrtSetDbgFlag(tmpDbgFlag);

     

        reader_writer_lock rw;

     

        rw.lock();

     

        rw.unlock();

     

        CurrentScheduler::Detach();

        WaitForSingleObject( hEvent, INFINITE );

        CloseHandle( hEvent );

        Sleep(500);

     

        return 0;

    }

     

    Here we still wait for 500 ms, that is mainly for the resource manager to have its shutdown completed.

     

    Atilla

    • Marked As Answer byAlex Farber Sunday, November 08, 2009 7:11 PM
    •  
  • Wednesday, November 04, 2009 7:32 PMGenevieve FernandesMSFTUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    Expanding on what Atilla said, destruction of a scheduler is initiated when all threads associated with it exit. When you use any of the PPL or Agents and Messaging constructs on a thread, it is very likely that your thread will be associated with the Concurrency Runtime's default scheduler. So if you declared a task group on a thread other than the main thread and waited for that thread to exit, in addition to a sleep period for shutdown to complete, you would not see any leaked memory.

    Now I agree that this is a workaround, however, the alternative to not caching stuff would be to recreate resources (memory threads) each time you use a parallel construct. The caching is intentional in our design in order to boost application performance.
    • Marked As Answer byAlex Farber Sunday, November 08, 2009 7:11 PM
    •  
  • Thursday, November 05, 2009 2:53 PMAlex Farber Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Let's say that I subscribe to the CurrentScheduler ShutDown event in the beginning of main application thread, and wait for this event before main thread exits, as shown in Atilla's post. I use Concurrency Runtime objects in different threads (main + worker threads), and all worker threads finish before waiting for shutdown event. Does this mean, that all Concurrency Runtime resources are properly released before CRT prints memory leaks dump?
  • Thursday, November 05, 2009 6:41 PMAtilla GunalMSFTUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    Each worker thread needs to be associated with the same scheduler the main thread is waiting on. As an example:


    DWORD WINAPI ThreadProc( void* pSchedulerAddress )

    {

        // Associate this worker thread with the scheduler that we are waiting on main thread

        Scheduler* pScheduler = (Scheduler*)pSchedulerAddress;

        pScheduler->Attach();


        reader_writer_lock rw;

        rw.lock();

        rw.unlock();

     

        // When worker is done, it has to clear its association with the scheduler

        CurrentScheduler::Detach();

     

        return 0;

    }


    int _tmain(int argc, _TCHAR* argv[])

    {

        HANDLE hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

        CurrentScheduler::Create( SchedulerPolicy() );

        CurrentScheduler::RegisterShutdownEvent( hEvent );

     

        // Get the scheduler and pass it to the worker

        Scheduler *pScheduler = CurrentScheduler::Get();

        HANDLE hThread = CreateThread( NULL, 0, ThreadProc, pScheduler, 0, NULL );

     

        int tmpDbgFlag;

        tmpDbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);

        tmpDbgFlag |= _CRTDBG_LEAK_CHECK_DF;

        _CrtSetDbgFlag(tmpDbgFlag);

     

        reader_writer_lock rw;

        rw.lock();

        rw.unlock();

     

        WaitForSingleObject( hThread, INFINITE );

        CloseHandle( hThread );

     

        CurrentScheduler::Detach();

        WaitForSingleObject( hEvent, INFINITE );

        Sleep(500);

     

        return 0;

     }

    This is the most general way of handling the shutdown. By the way as Genevieve mentioned, the usage could be simpler only if the worker threads use Concurrency Runtime constructs. Even if you have multiple worker threads, only one of them will create the default scheduler, the others will increment reference count of it. When all workers complete, they will decrement the reference count of the default scheduler. When reference count is zero, shutdown of the scheduler and resource manager will be initiated. You should not be seeing leaks with the example below as well:

     

    DWORD WINAPI ThreadProc( void* pSchedulerAddress )

    {

        reader_writer_lock rw;

        rw.lock();

        rw.unlock();

     

        return 0;

    }

     

    int _tmain(int argc, _TCHAR* argv[])

    {

        HANDLE hThread = CreateThread( NULL, 0, ThreadProc, NULL, 0, NULL );

     

        int tmpDbgFlag;

        tmpDbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);

        tmpDbgFlag |= _CRTDBG_LEAK_CHECK_DF;

        _CrtSetDbgFlag(tmpDbgFlag);

     

        WaitForSingleObject( hThread, INFINITE );

        CloseHandle( hThread );

     

        Sleep(500);

     

        return 0;

    }


    Atilla

    • Marked As Answer byAlex Farber Sunday, November 08, 2009 7:11 PM
    •  
  • Friday, November 06, 2009 9:15 AMAlex Farber Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Thank you, now I have enough information to handle this.
    The last point is Sleep function in the end. It means, that Shutdown Event is set too early. Should this be fixed in the Concurrency library? Or, maybe, it is necessary to add another event, which is set, when shutdown really finished?
  • Friday, November 13, 2009 1:35 AMAtilla GunalMSFTUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    The shutdown event is for the scheduler. The resource manager will still be leaking at the time event fires. On the other hand, we are tracking this as a bug on concurrency runtime. Let me note that this is most likely not going to be fixed with Visual Studio 2010 but the next version.

    Thank you for the feedback,

    Atilla