Search code examples
multithreadinggccmemory-leakslibclibc++

C++ multi-thread memory leak issue


Once the following code is running, it will eat all my memory and cause OOM issue on my ARM 64-bit processor. it seems that some 'delete' operations do not work... it confused me. Could any experts help to analysis what is the root cause? Ttoolchain or libc?

I run the same code in another two platforms, one is x64, one is another ARM 32-bit chip; they all fine except my IPQ ARM chip.

environment:

  • libc version: musl libc 1.1.16
  • gcc version: 5.2.0
  • cpu chip: IPQ ARM 64bit
  • os: linux 4.1
#include <iostream>
#include <cstring>
#include <list>
#include <memory>
#include <pthread.h>


void *h(void *param)
{
    while(1)
    {
        char *p = new char[4096];
        delete[] p;
    }
    return NULL;
}


int main(int argc, char *argv)
{
    pthread_t th[50];
    int thread_cnt = 2;
    for(int i = 0; i < thread_cnt; i++) 
        pthread_create(th+i, NULL, h, NULL);
        
    for(int i = 0; i < thread_cnt; i++) 
        pthread_join(th+i, NULL);

    return 0;
}
  1. If I use a mutex in the header-file thread callback, the issue will disappear; there is no memory leak.
std::mutex thlock;
void *h(void *param)
{
    while(1)
    {
        std::lock_guard<std::mutex> lk(thlock);
        char *p = new char[4096];
        delete[] p;
    }
    return NULL;
}

  1. If I change the memory allocation size to 1024, then a weird thing happed. 2 threads do not leak memory, but 20 threads will cause memory leak. Whatever the allocation size, there's no issue with only 1 thread.
void *h(void *param)
{
    while(1)
    {
        //char *p = new char[4096];
        char *p = new char[1024];
        delete[] p;
    }
    return NULL;
}

  1. If I replace new/delete to std::malloc/std::free, the issue will disappear; no memory leak.
void *h(void *param)
{
    while(1)
    {
        //char *p = new char[4096];
        char *p = (char *)std::malloc(4096);
        std::free(p);
        //delete[] p;
    }
    return NULL;
}

  1. So I thought if I overload operator new/delete and my "new/delete" allocate memory by using std::malloc/free, maybe the issue could be solved. But the weird thing happened again; the memory leak still exists.
void* operator new(std::size_t sz) // no inline, required by [replacement.functions]/3
{
    std::printf("global op new called, size = %zu\n", sz);
    if (sz == 0)
        ++sz; // avoid std::malloc(0) which may return nullptr on success
 
    if (void *ptr = std::malloc(sz))
        return ptr;
 
    throw std::bad_alloc{}; // required by [new.delete.single]/3
}
void operator delete(void* ptr) noexcept
{
    std::puts("global op delete called");
    std::free(ptr);
}

void *h(void *param)
{
    while(1)
    {
        char *p = new char[4096];
        delete[] p;
    }
    return NULL;
}

  1. So I thought the memory leak may be caused by my operator new/delete rather than malloc/free, so I changed the code to look like what's shown below. I don't really call malloc/free but return a pre-allocated buffer. But the memory leak issue disappeared, so it seems the issue not only comes from new/delete...
char pp[4096];
void* operator new(std::size_t sz) // no inline, required by [replacement.functions]/3
{
    std::printf("global op new called, size = %zu\n", sz);
    if (sz == 0)
        ++sz; // avoid std::malloc(0) which may return nullptr on success
 
    return (void *)pp;
    //if (void *ptr = std::malloc(sz))
        //return ptr;
 
    throw std::bad_alloc{}; // required by [new.delete.single]/3
}
void operator delete(void* ptr) noexcept
{
    std::puts("global op delete called");
    //std::free(ptr);
}

So, what happened? Could any experts give me some idea? I guess it maybe caused musl libc thread lib; it seems that thread synchronization does not work fine... or is there a patch that already fixed this issue?


Solution

  • this issue is involved by musl libc, and has been fixed by the following patch.

    https://git.musl-libc.org/cgit/musl/commit/src/malloc?id=3e16313f8fe2ed143ae0267fd79d63014c24779f

    above patch involved a buffur overflow issue of realloc and has been fixed by below 2 patches:

    https://git.musl-libc.org/cgit/musl/commit/src/malloc?id=cb5babdc8d624a3e3e7bea0b4e28a677a2f2fc46 https://git.musl-libc.org/cgit/musl/commit/src/malloc?id=fca7428c096066482d8c3f52450810288e27515c