pthread leaky with resources?

Matthew Grooms mgrooms at shrew.net
Thu Feb 19 22:14:22 UTC 2015


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;
}


More information about the freebsd-stable mailing list