Reputation: 480
While profiling the memory usage with valgrind/massif, I found it does not seem to see the memory deallocation done from another thread, reporting it as a leak. Here is an program that demonstrates that:
#include <stdio.h>
#include <iostream>
#include <pthread.h>
#include <stdlib.h>
using namespace std;
struct my_struct
{
pthread_mutex_t m_mutex;
void* m_mem;
};
extern "C"
{
void* my_allocation(void* p)
{
my_struct *ptr = (my_struct*)p;
int error;
if((error = pthread_mutex_lock(&ptr->m_mutex))) {
cerr << "mutex lock failed in thread: " << error << endl;
}
else {
ptr->m_mem = malloc(100);
if((error = pthread_mutex_unlock(&ptr->m_mutex)))
cerr << "mutex unlock failed in thread: " << error << endl;
}
return NULL;
}
void* my_deallocation(void* p)
{
my_struct *ptr = (my_struct*)p;
int error;
if((error = pthread_mutex_lock(&ptr->m_mutex))) {
cerr << "mutex lock failed in thread: " << error << endl;
}
else {
free(ptr->m_mem);
ptr->m_mem = NULL;
if((error = pthread_mutex_unlock(&ptr->m_mutex)))
cerr << "mutex unlock failed in thread: " << error << endl;
}
return NULL;
}
}
int main(int argc, char **argv)
{
pthread_t alloc_thread;
pthread_t dealloc_thread;
my_struct value;
value.m_mem = NULL;
int error;
if((error = pthread_mutex_init(&value.m_mutex, NULL))) {
cerr << "Failed to initialize mutex: " << error << endl;
return 1;
}
for(int i=0; i<300; ++i) {
if((error = pthread_create(&alloc_thread, NULL, my_allocation, &value))) {
cerr << "Failed to start allocation thread: " << error << endl;
return 1;
}
if((error = pthread_join(alloc_thread, NULL))) {
cerr << "Failed to join allocation thread: " << error << endl;
return 1;
}
if((error = pthread_mutex_lock(&value.m_mutex))) {
cerr << "mutex lock failed: " << error << endl;
}
else {
if(!value.m_mem) {
cerr << "Allocation thread failed to allocate the memory" << endl;
return 1;
}
if((error = pthread_mutex_unlock(&value.m_mutex)))
cerr << "mutex unlock failed: " << error << endl;
}
if((error = pthread_create(&dealloc_thread, NULL, my_deallocation, &value))) {
cerr << "Failed to start deallocation thread: " << error << endl;
return 1;
}
if((error = pthread_join(dealloc_thread, NULL))) {
cerr << "Failed to join deallocation thread: " << error << endl;
return 1;
}
if((error = pthread_mutex_lock(&value.m_mutex))) {
cerr << "mutex lock failed: " << error << endl;
}
else {
if(value.m_mem) {
cerr << "Deallocation thread failed to deallocate the memory" << endl;
return 1;
}
if((error = pthread_mutex_unlock(&value.m_mutex)))
cerr << "mutex unlock failed: " << error << endl;
}
}
if((error = pthread_mutex_destroy(&value.m_mutex))) {
cerr << "Failed to destroy mutex: " << error << endl;
return 1;
}
cout << "Done" << endl;
return 0;
}
After running this program under valgrind/massif, I am getting the report that seems to indicate the allocated memory as leaked.
Here is a fragment of massif report:
--------------------------------------------------------------------------------
Command: valgrind-threads
Massif arguments: --heap=yes --pages-as-heap=yes --massif-out-file=/tmp/valgrind-threads.massif.31220.log
ms_print arguments: /tmp/valgrind-threads.massif.31220.log
--------------------------------------------------------------------------------
100.00% (197,083,136B) (page allocation syscalls) mmap/mremap/brk, --alloc-fns, etc.
->72.36% (142,610,432B) 0x7773FCA: mmap (in /usr/lib64/libc-2.17.so)
| ->68.10% (134,217,728B) 0x76FA2D0: new_heap (in /usr/lib64/libc-2.17.so)
| | ->68.10% (134,217,728B) 0x76FA8D3: arena_get2.isra.3 (in /usr/lib64/libc-2.17.so)
| | ->68.10% (134,217,728B) 0x770077D: malloc (in /usr/lib64/libc-2.17.so)
| | ->68.10% (134,217,728B) 0x40C194: my_allocation (valgrind-threads.cpp:24)
| | ->68.10% (134,217,728B) 0x7466EA4: start_thread (in /usr/lib64/libpthread-2.17.so)
| | ->68.10% (134,217,728B) 0x7779B0C: clone (in /usr/lib64/libc-2.17.so)
| |
| ->04.26% (8,392,704B) 0x74677FC: pthread_create@@GLIBC_2.2.5 (in /usr/lib64/libpthread-2.17.so)
| | ->04.26% (8,392,704B) 0x40C384: main (valgrind-threads.cpp:64)
| |
| ->00.00% (0B) in 1+ places, all below ms_print's threshold (01.00%)
Valgrind version is 3.19
.
Building and running that with gcc 4.9.4
on Red Hat Enterprise Linux Server release 7.9 (Maipo)
.
Am I reading the report incorrectly, or it is a valgrind/massif limitation, or I am doing something wrong in the code?
Upvotes: 0
Views: 541
Reputation: 480
As it was found in another question I posted about TLS deallocations not tracked by valgrind/massif:
If we use --pages-as-heap
, valgrind/massif will report the GLIBC memory pages allocated per thread malloc cache, which happens at least at the first allocation on thread (sometimes the reallocations may happen when heap needs to grow).
This gives an impression the allocated memory has leaked.
If run without --pages-as-heap
option, the deallocations are accounted for correctly.
Upvotes: 1