All,
I have a multi-threaded program that runs on 10.1-RELEASE-p5. It starts
out with a reasonable footprint but there is obviously a resource leak
as the program uses substantially more memory over time ...
PID USERNAME THR PRI NICE SIZE RES STATE C TIME WCPU COMMAND
51560 rdj 3 20 0 46676K 7500K select 1 0:00 0.00% dialyd
... 24h later ...
PID USERNAME THR PRI NICE SIZE RES STATE C TIME WCPU COMMAND
51560 rdj 3 20 0 131M 27064K select 3 1:45 0.00% dialyd
After a bit of debugging, I determined that it only happens when threads
are created and then later destroyed. Valgrind thinks that the resources
are being leaked from libthr itself ...
First I tried joining the threads, then I tried detaching them using
either pthread_detach or pthread_attr_setdetachstate. Whatever method I
use, I still see resources leaked. I wrote a simple test program to
demonstrate this ( attached ). Ten threads show 16,472 bytes leaked
where 250 threads show 275,672 bytes. Here is an example of the valgrind
output ...
==69449== 254,000 bytes in 250 blocks are still reachable in loss record
12 of 12
==69449== at 0x1007221: calloc (in
/usr/local/lib/valgrind/vgpreload_memcheck-amd64-freebsd.so)
==69449== by 0x16F8BD9: ??? (in /lib/libthr.so.3)
==69449== by 0x16F0E3D: pthread_create (in /lib/libthr.so.3)
==69449== by 0x4012B4: main (in /usr/home/mgrooms/threads/threads)
... and example of the summary shown for each run of the test program ...
valgrind --leak-check=full ./threads 10
==69310== Memcheck, a memory error detector
==69310== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==69310== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==69310== Command: ./threads 10
==69310=main: waiting for condition to be signaled ...
thread 5: signaling condition
main: condition signaled, exiting
==69310===69310== HEAP SUMMARY:
==69310== in use at exit: 16,472 bytes in 30 blocks
==69310== total heap usage: 44 allocs, 14 frees, 17,368 bytes allocated
==69310===69310== LEAK SUMMARY:
==69310== definitely lost: 0 bytes in 0 blocks
==69310== indirectly lost: 0 bytes in 0 blocks
==69310== possibly lost: 0 bytes in 0 blocks
==69310== still reachable: 16,472 bytes in 30 blocks
==69310== suppressed: 0 bytes in 0 blocks
==69310== Reachable blocks (those to which a pointer was found) are not
shown.
==69310== To see them, rerun with: --leak-check=full --show-reachable=yes
==69310===69310== For counts of detected and suppressed errors, rerun with: -v
==69310== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
valgrind --leak-check=full ./threads 100
==69311== Memcheck, a memory error detector
==69311== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==69311== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==69311== Command: ./threads 100
==69311=main: waiting for condition to be signaled ...
thread 99: signaling condition
main: condition signaled, exiting
==69311===69311== HEAP SUMMARY:
==69311== in use at exit: 113,672 bytes in 210 blocks
==69311== total heap usage: 314 allocs, 104 frees, 121,048 bytes allocated
==69311===69311== LEAK SUMMARY:
==69311== definitely lost: 0 bytes in 0 blocks
==69311== indirectly lost: 0 bytes in 0 blocks
==69311== possibly lost: 0 bytes in 0 blocks
==69311== still reachable: 113,672 bytes in 210 blocks
==69311== suppressed: 0 bytes in 0 blocks
==69311== Reachable blocks (those to which a pointer was found) are not
shown.
==69311== To see them, rerun with: --leak-check=full --show-reachable=yes
==69311===69311== For counts of detected and suppressed errors, rerun with: -v
==69311== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
valgrind --leak-check=full ./threads 250
==69315== Memcheck, a memory error detector
==69315== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==69315== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==69315== Command: ./threads 250
==69315=main: waiting for condition to be signaled ...
thread 241: signaling condition
main: condition signaled, exiting
==69315===69315== HEAP SUMMARY:
==69315== in use at exit: 275,672 bytes in 510 blocks
==69315== total heap usage: 764 allocs, 254 frees, 293,848 bytes allocated
==69315===69315== LEAK SUMMARY:
==69315== definitely lost: 0 bytes in 0 blocks
==69315== indirectly lost: 0 bytes in 0 blocks
==69315== possibly lost: 0 bytes in 0 blocks
==69315== still reachable: 275,672 bytes in 510 blocks
==69315== suppressed: 0 bytes in 0 blocks
==69315== Reachable blocks (those to which a pointer was found) are not
shown.
==69315== To see them, rerun with: --leak-check=full --show-reachable=yes
==69315===69315== For counts of detected and suppressed errors, rerun with: -v
==69315== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
This doesn't seem right to me. Anyone have any ideas?
Thanks,
-Matthew
-------------- next part --------------
//
// main.cpp: thread test
//
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <machine/atomic.h>
enum THREAD_TYPE
{
THREAD_TYPE_JOINED = 0,
THREAD_TYPE_DETACHED,
THREAD_TYPE_DETACHED_ATTRIB
};
int thread_index = 0;
pthread_mutex_t mutex;
pthread_cond_t condition;
void pthread_error( const char * name, int result )
{
switch( result )
{
case 0:
return;
case EAGAIN:
printf( "%s failed ( EAGAIN: Resource temporarily unavailable )\n",
name );
break;
case EPERM:
printf( "%s failed ( EPERM: Operation not permitted )\n", name );
break;
case EINVAL:
printf( "%s failed ( EINVAL: Invalid argument )\n", name );
break;
case ENOMEM:
printf( "%s failed ( ENOMEM: Cannot allocate memory )\n", name );
break;
case EDEADLK:
printf( "%s failed ( EDEADLK: Resource deadlock avoided )\n", name
);
break;
default:
printf( "%s failed( unknown error )\n", name );
}
exit( 1 );
}
void * thread_proc( void * arg )
{
//
// increment the thread index under mutex protection
//
int result = pthread_mutex_lock( &mutex );
if( result != 0 )
pthread_error( "thread: pthread_mutex_lock", result );
int local_index = thread_index++;
printf( "thread %i: created\n", local_index );
result = pthread_mutex_unlock( &mutex );
if( result != 0 )
pthread_error( "thread: pthread_mutex_unlock", result );
//
// sleep for a bit
//
sleep( 3 );
//
// decrement the thread index under mutex protection
// and signal the condition if the thread index has
// reached zero
//
result = pthread_mutex_lock( &mutex );
if( result != 0 )
pthread_error( "thread: pthread_mutex_lock", result );
printf( "thread %i: exiting\n", local_index );
if( --thread_index == 0 )
{
printf( "thread %i: signaling condition\n", local_index );
result = pthread_cond_signal( &condition );
if( result == 0 )
pthread_error( "thread: pthread_cond_signal", result );
}
result = pthread_mutex_unlock( &mutex );
if( result != 0 )
pthread_error( "thread: pthread_mutex_unlock", result );
return NULL;
}
int main( int argc, char * argv[] )
{
//
// type of thread cleanup
//
// THREAD_TYPE_JOINED: thread will be joined with pthread_join
// THREAD_TYPE_DETACHED: thread will be detached using pthread_detach
// THREAD_TYPE_DETACHED_ATTRIB: thread will be detached using
pthread_attr_setdetachstate
//
THREAD_TYPE thread_type = THREAD_TYPE_DETACHED;
//
// read thread count
//
int thread_count = 10;
if( argc == 2 )
thread_count = atoi( argv[1] );
if( thread_count < 1 )
{
printf( "usage: threads [threadcount]\n" );
exit( 2 );
}
//
// create our pthread mutex
//
int result = pthread_mutex_init( &mutex, NULL );
if( result != 0 )
pthread_error( "main: pthread_mutex_init", result );
//
// create our pthread condition
//
result = pthread_cond_init( &condition, NULL );
if( result != 0 )
pthread_error( "main: pthread_cond_init", result );
//
// alocate storage for our pthread handles
//
pthread_t * pthreads = new pthread_t[ thread_count ];
if( pthreads == NULL )
{
printf( "failed to allocate thread handle storage\n" );
return -1;
}
//
// loop for number of threads requested
//
for( int index = 0; index < thread_count; index++ )
{
//
// initialize default thread attributes
//
pthread_attr_t attr;
result = pthread_attr_init( &attr );
//
// set detach thread attribute if neccessary
//
if( thread_type == THREAD_TYPE_DETACHED_ATTRIB )
result = pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED );
//
// create a new thread
//
result = pthread_create( &pthreads[ index ], &attr, &thread_proc,
NULL );
if( result != 0 )
pthread_error( "main: pthread_create", result );
//
// clean up thread attributes
//
pthread_attr_destroy( &attr );
//
// explicitly detach the thread if neccessary
//
if( thread_type == THREAD_TYPE_DETACHED )
{
result = pthread_detach( pthreads[ index ] );
if( result != 0 )
pthread_error( "main: pthread_detach", result );
}
}
//
// wait for our condition to be signaled
//
printf( "main: waiting for condition to be signaled ...\n" );
result = pthread_mutex_lock( &mutex );
if( result != 0 )
pthread_error( "main: pthread_mutex_lock", result );
result = pthread_cond_wait( &condition, &mutex );
if( result != 0 )
pthread_error( "main: pthread_cond_wait", result );
printf( "main: condition signaled, exiting\n" );
result = pthread_mutex_unlock( &mutex );
if( result != 0 )
pthread_error( "main: pthread_mutex_unlock", result );
//
// join all threads if neccessary
//
if( thread_type == THREAD_TYPE_JOINED )
{
for( int index = 0; index < thread_count; index++ )
{
result = pthread_join( pthreads[ index ], NULL );
if( result != 0 )
pthread_error( "main: pthread_join", result );
}
printf( "all threads joined\n" );
}
//
// clean up
//
delete [] pthreads;
pthread_cond_destroy( &condition );
pthread_mutex_destroy( &mutex );
return 0;
}