Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Memory problems with a multi-threaded Win32 service that uses STL on VS2010

I have a multi-threaded Win32 service written in C++ (VS2010) that makes extensive use of the standard template library. The business logic of the program operates properly, but when looking at the task manager (or resource manager) the program leaks memory like a sieve.

I have a test set that averages about 16 simultaneous requests/second. When the program is first started up it consumes somewhere in the neighborhood of 1.5Mb of ram. After a full test run (which take 12-15 minutes) the memory consumption ends up somewhere near 12Mb. Normally, this would not be a problem for a program that runs once and then terminates, but this program is intended to run continuously. Very bad, indeed.

To try and narrow down the problem, I created a very small test application that spins off worker threads at a rate of once every 250ms. The worker thread creates a map and populates it with pseudo-random data, empties the map, and then exits. This program, too, leaks memory in like fashion, so I'm thinking that the problem is with the STL not releasing the memory as expected.

I have tried VLD to search for leaks and it has found a couple which I have remedied, but still the problem remains. I have tried integrating Hoard, but that has actually made the problem worse (i'm probably not integrating it properly, but i can't see how).

So I would like to pose the following question: is it possible to create a program that uses the STL in a multi-threaded environment that will not leak memory? Over the course of the last week I have made no less than 200 changes to this program. I have plotted the results of the changes and they all have the same basic profile. I don't want to have to remove all of the STL goodness that has made developing this application so much easier. I would earnestly appreciate any suggestions on how I can get this app working without leaking memory like it's going out of style.

Thanks again for any help!

P.S. I'm posting a copy of the memory test for inspection/personal edification.

#include <string>
#include <iostream>
#include <Windows.h>
#include <map>

using namespace std;

#define MAX_THD_COUNT 1000

DWORD WINAPI ClientThread(LPVOID param)
{
    unsigned int thdCount = (unsigned int)param;

    map<int, string> m;

    for (unsigned int x = 0; x < 1000; ++x)
    {
        string s;

        for (unsigned int y = 0; y < (x % (thdCount + 1)); ++y)
        {
            string z = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            unsigned int zs = z.size();

            s += z[(y % zs)];
        }

        m[x] = s;
    }

    m.erase(m.begin(), m.end());

    ExitThread(0);

    return 0;
}

int main(int argc, char ** argv)
{
    // wait for start
    string inputWait;
    cout << "type g and press enter to go: ";
    cin >> inputWait;

    // spawn many memory-consuming threads
    for (unsigned int thdCount = 0; thdCount < MAX_THD_COUNT; ++thdCount)
    {
        CreateThread(NULL, 0, ClientThread, (LPVOID)thdCount, NULL, NULL);

        cout
            << (int)(MAX_THD_COUNT - thdCount)
            << endl;

        Sleep(250);
    }

    // wait for end
    cout << "type e and press enter to end: ";
    cin >> inputWait;

    return 0;
}
like image 657
John A. Gonzalez Avatar asked Nov 12 '22 16:11

John A. Gonzalez


1 Answers

Use _beginthreadex() when using the std library (includes the C runtime as far as MS is concerned). Also, you're going to experience a certain amount of fragmentation in the std runtime sub-allocator, especially in code designed to continually favor larger and larger requests like this.

The MS runtime library has some functions that allow you to debug memory requests and determine if there is a solid leak once you have a sound algorithm and are confident you don't see anything glaringly obvious. See the debug routines for more information.

Finally, I made the following modifications to the test jig you wrote:

  1. Setup the proper _Crt report mode for spamming the debug window with any memory leaks after shutdown.
  2. Modified the thread-startup loop to keep the maximum number of threads running constantly at MAXIMUM_WAIT_OBJECTS (WIN32-defined currently as 64 handles)
  3. Threw in a purposeful leaked char array allocation to show the CRT will, in fact, catch it when dumping at program termination.
  4. Eliminated console keyboard interaction. Just run it.

Hopefully this will make sense when you see the output log. Note: you must compile in Debug mode for this to make any proper dump for you.

#include <windows.h>
#include <dbghelp.h>
#include <process.h>
#include <string>
#include <iostream>
#include <map>
#include <vector>

using namespace std;

#define MAX_THD_COUNT 250
#define MAX_THD_LOOPS 250

unsigned int _stdcall ClientThread(void *param)
{
    unsigned int thdCount = (unsigned int)param;
    map<int, string> m;

    for (unsigned int x = 0; x < MAX_THD_LOOPS; ++x)
    {
        string s;
        for (unsigned int y = 0; y < (x % (thdCount + 1)); ++y)
        {
            string z = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            size_t zs = z.size();
            s += z[(y % zs)];
        }
        m[x].assign(s);
    }
    return 0;
}

int main(int argc, char ** argv)
{
    // setup reporting mode for the debug heap. when the program
    //  finishes watch the debug output window for any potential
    //  leaked objects. We're leaking one on purpose to show this
    //  will catch the leaks.
    int flg = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
    flg |= _CRTDBG_LEAK_CHECK_DF;
    _CrtSetDbgFlag(flg);

    static char msg[] = "Leaked memory.";
    new std::string(msg);

    // will hold our vector of thread handles. we keep this fully populated
    //  with running threads until we finish the startup list, then wait for
    //  the last set of threads to expire.
    std::vector<HANDLE> thrds;
    for (unsigned int thdCount = 0; thdCount < MAX_THD_COUNT; ++thdCount)
    {
        cout << (int)(MAX_THD_COUNT - thdCount) << endl;
        thrds.push_back((HANDLE)_beginthreadex(NULL, 0, ClientThread, (void*)thdCount, 0, NULL));
        if (thrds.size() == MAXIMUM_WAIT_OBJECTS)
        {
            // wait for any single thread to terminate. we'll start another one after,
            //  cleaning up as we detected terminated threads
            DWORD dwRes = WaitForMultipleObjects(thrds.size(), &thrds[0], FALSE, INFINITE);
            if (dwRes >= WAIT_OBJECT_0 && dwRes < (WAIT_OBJECT_0 + thrds.size()))
            {
                DWORD idx = (dwRes - WAIT_OBJECT_0);
                CloseHandle(thrds[idx]);
                thrds.erase(thrds.begin()+idx, thrds.begin()+idx+1);
            }
        }
    }

    // there will be threads left over. need to wait on those too.
    if (thrds.size() > 0)
    {
        WaitForMultipleObjects(thrds.size(), &thrds[0], TRUE, INFINITE);
        for (std::vector<HANDLE>::iterator it=thrds.begin(); it != thrds.end(); ++it)
            CloseHandle(*it);
    }

    return 0;
}

Output Debug Window

Note: there are two leaks reported. One is the std::string allocation, the other is the buffer within the std::string that held our message copy.

Detected memory leaks!
Dumping objects ->
{80} normal block at 0x008B1CE8, 8 bytes long.
 Data: <09      > 30 39 8B 00 00 00 00 00 
{79} normal block at 0x008B3930, 32 bytes long.
 Data: <    Leaked memor> E8 1C 8B 00 4C 65 61 6B 65 64 20 6D 65 6D 6F 72 
Object dump complete.
like image 167
WhozCraig Avatar answered Nov 15 '22 05:11

WhozCraig