1

I have a socket server, everytime a new connection is made, a XClient class is instantiated and I am inserting it into a map. I am watching the memory usage through task manager. everytime a new connection is made, lets assume, the memory usage of my program increases by 800kb for example. Inside that class, there is a connected variable, which will tell me wheter this client is active or not. I created a thread to run endlessly and iterate through all the elements of my map and I'm checking if the connected variable is true or false. if it is false, I am (at least I think I am...) releasing the memory used by the previously instantiated XClient class. BUT, the memory usage is being decreased only half of the 800kb (for example, no precise values). So, when a client connects: +800kb. when client disconnects: -400kb. I think I have a memory leak? If I have 100 clients connected, that 400kb that is not being released would turn into 4000kb of non-used(?) memory, and that would be a problem.

So, here is my code. The thread to iterate through all elements:

DWORD Update(XSockets *sockets)
{
while(true)
{
    for(sockets->it = sockets->clients.begin(); sockets->it != sockets->clients.end(); sockets->it++)
    {
        int key = (*sockets->it).first;
        if(sockets->clients[key]->connected == false) // remove the client, releasing memory
        {
            delete sockets->clients[key];
        }
    }
    Sleep(100);
}
return true;
}

The code that is adding new XClients instances to my map:

bool XSockets::AcceptConnections()
{
struct sockaddr_in from;

while(true)
{
    try
    {
        int fromLen = sizeof(from);
        SOCKET client = accept(this->loginSocket,(struct sockaddr*)&from,&fromLen);
        if(client != INVALID_SOCKET)
        {
            srand(time(NULL));
            int clientKey = rand();
            XClient* clientClass = new XClient(inet_ntoa(from.sin_addr),clientKey,client);
            this->clients.insert(make_pair(clientKey,clientClass));
        }
        Sleep(100);
    }
    catch(...)
    {
        printf("error accepting incoming connection!\r\n");
        break;
    }
}

closesocket(this->loginSocket);
WSACleanup();

return true;
}

And the declarations:

    map<int,XClient*> clients;
map<int,XClient*>::iterator it;
  • Check `XClient::~XClient()` (the destructor) to make sure it properly releases everything. – Ben Voigt May 20 '12 at 02:41
  • I added a printf in the destructor, but it seems like the destructor is not being called... – Leandro Battochio May 20 '12 at 02:43
  • 1
    `printf` is not a reliable way to debug. Try a breakpoint. – Anthony May 20 '12 at 02:45
  • I also notice that your code doesn't remove the entry from the map after deleting it. So you'll read the `connected` member variable of a deleted object, and call `delete` on the same pointer multiple times, with all kinds of bad behavior. – Ben Voigt May 20 '12 at 02:46
  • my bad, printf is working in the destructor fine. so, how should I remove the entry in the map correctly? – Leandro Battochio May 20 '12 at 02:48

3 Answers3

1

You've got several problems, but the chief one is that you appear to be sharing a map between threads without any synchronization at all. That can lead to all kinds of trouble.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
1

Are you using c++11 or Boost? To avoid memory leak nightmares like this, you could create a map of shared pointers. This way, you can let the structure clean itself up.

This is how I would do it:

#include <memory>
#include <map>
#include <algorithm>
#include <functional>
#include <mutex>

typedef std::shared_ptr<XClient> XClientPtr;
std::map<int, XClientPtr> client;
std::mutex the_lock;

bool XSockets::AcceptConnections()
{
/* snip */

    auto clientClass = std::make_shared<XClient>(/*... params ...*/);
    the_lock.lock();
    clients[clientKey] = clientClass;
    the_lock.unlock();
/* snip */
}

bool client_is_connected(const std::pair<int, XClientPtr> &p) {
    return p.second->connected;
}

DWORD Update(XSockets *sockets) {
    while(true) { /* You should probably have some kind of
                     exit condition here. Like a global "running" bool
                     so that the thread will eventually stop. */
        the_lock.lock();

        auto it = sockets->clients.begin(), end = sockets->clients.end();
        for(; it != end; ) {
            if (!it->second->connected)
                //Clients will be destructed here if their refcount goes to 0
                sockets->clients.erase(it++); 
            else
                ++it;
        }
        the_lock.unlock();
        Sleep(100);
    }
    return 1;
}

Note: Above code is untested. I haven't even tried to compile it.

Anthony
  • 12,177
  • 9
  • 69
  • 105
  • Somehow, I can't include the mutex header: no such file or directory – Leandro Battochio May 20 '12 at 03:15
  • Probably not part of your compiler's standard library. But you get the point. You're using `winapi` by the looks of it. Try the techniques defined in [InitializeCriticalSection](http://msdn.microsoft.com/en-us/library/windows/desktop/ms683472(v=vs.85).aspx) and related documentation. – Anthony May 20 '12 at 03:17
  • I cant include all the headers you provided: algorithm,functional and memory. If I add the functional header, my VS 2010 shows a lot of errors... – Leandro Battochio May 20 '12 at 03:26
  • What's up with your VS2010? It should include all of those headers. In fact, those headers are old by `C++` standards. Is your environment broken? Are you using native C++ or Managed? – Anthony May 21 '12 at 02:35
  • I am using native C++, no relation with .net, is there any settings in my project I should change or you may want to see? – Leandro Battochio May 21 '12 at 05:07
  • OK. In VS2010, the thread parts of the C++11 standard are not implemented. Use `winapi` threads and synchronisation primitives instead. Every other header file from my sample above should be available. The directory `%VS2010_INSTALL_DIR%\VC\include` should have the files `algorithm`, `memory` and `functional`. – Anthony May 21 '12 at 05:57
  • indeed they have, but if I include the functional header, I get several errors – Leandro Battochio May 21 '12 at 12:10
  • You are now in the world of template error messages. Can you hit compile? The compiler log window should give you an instantiation trace which will tell you which lines of your the code are causing the errors. It's likely that I have mistyped something in the example above. – Anthony May 22 '12 at 22:06
  • even if I dont use your codes, the simply fact of including the functional header gives me errors. – Leandro Battochio May 23 '12 at 15:36
  • I fixed the code, the error was some conflict between bind from winsock and bind from std, since I was "using namespace std". Now, I only have one error, output text: http://pastebin.com/NEAVURVi – Leandro Battochio May 23 '12 at 15:44
  • 1
    Well done! Lesson learned? Don't use `namespace std` in a using directive. What's on that line, XClient.h:29? – Anthony May 23 '12 at 22:05
  • Oh, wait a minute. Can't use `remove_if` on a map, because it reorders the collection. That isn't allowed on a `std::map`. Updating my answer. – Anthony May 24 '12 at 21:40
  • Im having this error now: The thread 'Win32 Thread' (0x1544) has exited with code 1 (0x1). HEAP[LoginSvr.exe]: Invalid address specified to RtlValidateHeap( 01D50000, 023B0075 ) Windows has triggered a breakpoint in LoginSvr.exe. – Leandro Battochio May 25 '12 at 03:24
0

See What happens to an STL iterator after erasing it in VS, UNIX/Linux?. In your case, you are not deleting everything, so you will want to not use a for loop.

sockets->it = sockets->clients.begin();
while (sockets->it != sockets->clients.end())
{
    int key = (*sockets->it).first;
    if(sockets->clients[key]->connected == false) // remove the client, releasing memory
    {
        delete sockets->clients[key];
        sockets->clients.erase(sockets->it++);
    }
    else
    {
        sockets->it++;
    }
}
Community
  • 1
  • 1
Jeffery Thomas
  • 42,202
  • 8
  • 92
  • 117
  • 1
    Couldn't you just use `it->second` instead of `clients[key]`? – Ben Voigt May 20 '12 at 03:28
  • Yes, but I wanted to make as few changes as possible to his code. – Jeffery Thomas May 20 '12 at 03:40
  • BTW `sockets->clients.erase(sockets->it++);` leaves `it` invalid. You should use `sockets->it = sockets->clients.erase(sockets->it);` instead – Ben Voigt Apr 16 '15 at 03:41
  • @BenVoigt Before C++11, the signature was [void erase(iterator pos)](http://en.cppreference.com/w/cpp/container/map/erase), so `sockets->it = sockets->clients.erase(sockets->it)` would fail to compile on older compilers. In addition, "References and iterators to the erased elements are invalidated. Other references and iterators are not affected.", so `sockets->it++` leaves `it` in a valid [state](http://stackoverflow.com/a/263958/1298400). – Jeffery Thomas Apr 16 '15 at 10:22