Bug 4123 - pthread cleanup handler not called
Summary: pthread cleanup handler not called
Status: RESOLVED WORKSFORME
Alias: None
Product: glibc
Classification: Unclassified
Component: ports (show other bugs)
Version: unspecified
: P2 normal
Target Milestone: ---
Assignee: Roland McGrath
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-03-02 15:18 UTC by Miroslav Kes
Modified: 2018-04-20 13:56 UTC (History)
5 users (show)

See Also:
Host: i686-pc-linux-gnu
Target: i686-pc-linux-gnu, arm-xscale-be
Build: i686-pc-linux-gnu
Last reconfirmed:
fweimer: security-


Attachments
Tar file containing test program to produce the behavior with pthread_exit. (6.70 KB, application/gzip)
2007-09-08 17:37 UTC, K.R. Foley
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Miroslav Kes 2007-03-02 15:18:26 UTC
I use native or crosscompiler for arm-xscale-be gcc 4.1.1 and glibc 2.5. gcc
configuration:

Using built-in specs.
Target: i686-pc-linux-gnu
Configured with: /var/tmp/portage/gcc-4.1.1-r3/work/gcc-4.1.1/configure
--prefix=/usr --bindir=/usr/i686-pc-linux-gnu/gcc-bin/4.1.1
--includedir=/usr/lib/gcc/i686-pc-linux-gnu/4.1.1/include
--datadir=/usr/share/gcc-data/i686-pc-linux-gnu/4.1.1
--mandir=/usr/share/gcc-data/i686-pc-linux-gnu/4.1.1/man
--infodir=/usr/share/gcc-data/i686-pc-linux-gnu/4.1.1/info
--with-gxx-include-dir=/usr/lib/gcc/i686-pc-linux-gnu/4.1.1/include/g++-v4
--host=i686-pc-linux-gnu --build=i686-pc-linux-gnu --disable-altivec
--enable-nls --without-included-gettext --with-system-zlib --disable-checking
--disable-werror --enable-secureplt --disable-libunwind-exceptions
--disable-multilib --disable-libmudflap --disable-libssp --disable-libgcj
--enable-languages=c,c++,fortran --enable-shared --enable-threads=posix
--enable-__cxa_atexit --enable-clocale=gnu
Thread model: posix
gcc version 4.1.1 (Gentoo 4.1.1-r3)

or (the cross compiler)

Using built-in specs.
Target: armv5teb-softfloat-linux-gnueabi
Configured with: ../gcc-4.1.1/configure --prefix=/tmp/clfs/cross-tools
--host=i686-cross-linux-gnu --target=armv5teb-softfloat-linux-gnueabi
--disable-multilib --with-sysroot=/tmp/clfs --disable-nls --enable-shared
--enable-languages=c,c++ --enable-__cxa_atexit --enable-c99 --enable-long-long
--enable-threads=posix
Thread model: posix
gcc version 4.1.1

glibc on the x86 is:

GNU C Library stable release version 2.5, by Roland McGrath et al.
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 4.1.1 (Gentoo 4.1.1-r3).
Compiled on a Linux 2.6.17 system on 2007-02-09.
Available extensions:
        C stubs add-on version 2.1.2
        crypt add-on version 2.1 by Michael Glad and others
        Gentoo patchset 1.3.1
        GNU Libidn by Simon Josefsson
        GNU libio by Per Bothner
        NIS(YP)/NIS+ NSS modules 0.19 by Thorsten Kukuk
        Native POSIX Threads Library by Ulrich Drepper et al
        Support for some architectures added on, not maintained in glibc core.
        BIND-8.2.3-T5B
Thread-local storage support included.
For bug reporting instructions, please see:
<http://www.gnu.org/software/libc/bugs.html>.

Problem:
Pthread cleanup handlers do not get called on thread cancelation if the program
is compiled using g++. The same code, if compiled using gcc or using g++ with
the -fno-exceptions option, calls cleanup handlers OK. 
Consequences in C++ application if the cleanup handler is (for example)
supposed to unlock a mutex are easy to guess.

Reproducible: Always

Steps to Reproduce:
Compile the code bellow using g++ and using gcc. Run both executables and
compare results.
Actual Results:  
The executable compile using gcc prints out this:

Starting child thread.
Canceling child thread.
Cleanup handler called.
Child thread terminated.

while the executable compiled using g++ prints just this:

Starting child thread.
Canceling child thread.
Child thread terminated.

Expected Results:  
The outputs should be the same regardless of compiler used.

--------------

/*
 * Cleanup handler bug test program.
 * Build using  g++ -o cleanuphandlertest -l pthread cleanuphandlertest.cpp
 * or using gcc -o cleanuphandlertest -l pthread cleanuphandlertest.c
 *
 * If the cleanup handler doesn't get called try the -fno-exceptions option. 
 */

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>


// Assertion which must always be true. 
//  If !(condition) then a fatal error will result.
//
#define ASSERT( condition, errCode )  ( ( void ) \
                      ( (condition) ? ( ( void ) 0 ) : \
                       Assert( __FILE__, __LINE__, errCode ) ) )


void Assert( const char *file, int line, int err )
{
        fprintf( stderr, "Error in file %s, line %d: error code %d\n", file,
line, err );
}


void ThreadCleanupHandler( void* arg )
{
        printf( "Cleanup handler called.\n" );
}


void* ThreadBody( void* arg )
{
        pthread_cleanup_push( ThreadCleanupHandler, arg );

        while ( 1 )
        {               
                pthread_testcancel();
                sleep( 1 );
        }

        pthread_cleanup_pop( 0 );
}


int main( int argc, char **argv ) 
{
        int status;
        pthread_attr_t threadAttributes;
        pthread_t threadId;

        status = pthread_attr_init( &threadAttributes );
        ASSERT( status == 0, status );

        // Start the queue scanning thread
        printf( "Starting child thread.\n" );

        status = pthread_create( &threadId, &threadAttributes, ThreadBody, NULL );
        ASSERT( status == 0, status );

        status = pthread_attr_destroy( &threadAttributes );
        ASSERT( status == 0, status );

        sleep( 3 );

        printf( "Canceling child thread.\n" );

        status = pthread_cancel( threadId );
        ASSERT( status == 0, status );

        // wait until the thread really terminates
        status = pthread_join( threadId, NULL );
        ASSERT( status == 0, status );

        printf( "Child thread terminated.\n" );

        return 0;
}

--------------

After a bit more investigation ... If the compiler is stopped after the 
preprocessor (i.e. g++ -E -o preprocess.cpp cleanuphandlertest.cpp), the code
that installs/deinstalls the cleanup hander looks like:

void* ThreadBody( void* arg )
{
 do { __pthread_cleanup_class __clframe (ThreadCleanupHandler, arg);

 while ( 1 )
 {
  pthread_testcancel();
  sleep( 1 );
 }

 __clframe.__setdoit (0); } while (0);
}


where the __pthread_cleanup_class is defined as:

class __pthread_cleanup_class
{
  void (*__cancel_routine) (void *);
  void *__cancel_arg;
  int __do_it;
  int __cancel_type;

 public:
  __pthread_cleanup_class (void (*__fct) (void *), void *__arg)
    : __cancel_routine (__fct), __cancel_arg (__arg), __do_it (1) { }
  ~__pthread_cleanup_class () { if (__do_it) __cancel_routine (__cancel_arg); }
  void __setdoit (int __newval) { __do_it = __newval; }
  void __defer () { pthread_setcanceltype (PTHREAD_CANCEL_DEFERRED,
        &__cancel_type); }
  void __restore () const { pthread_setcanceltype (__cancel_type, 0); }
};

The actual execution of the cleanup handler when the thread is canceled should
happen in the destructor of the __clframe instance.
 
So it looks like if the cleanup handler setup/invocation is implemented using
the __pthread_cleanup_class the destructor of an instance created as local
variable doesn't get called on thread cancellation.
Comment 1 K.R. Foley 2007-09-06 11:09:45 UTC
What is the status of this? We are seeing this also on opensuse 10.2 X86 with
gcc 4.1.2 and glibc 2.5-25.
Comment 2 K.R. Foley 2007-09-08 17:37:52 UTC
Created attachment 1993 [details]
Tar file containing test program to produce the behavior with pthread_exit.

In addition, the cleanup handlers are sometimes not called from pthread_exit as
well. I tried to reproduce this with the simple test program previously
provided but could not. So I added what was a standalone openmotif messagebox
app into the program in order to reproduce it. I changed the main function in
messagebox.cpp to main_display and called it from the ThreadBody function. Then
pthread_exit is called from MessageBox::ButtonCallback which is called upon
pressing the OK button. pthread_exit should call the cleanup functions that
were put on the cleanup stack in ThreadBody. To produce this behavior build the
three source files using: g++ -o cleanuptest2 -lpthread -lXm -lXt
cleanuptest2.cpp messagebox.cpp. To produce the correct behavior just add
-fno-exceptions as in the original test.
Comment 3 Andreas Schwab 2010-09-03 15:16:39 UTC
Works fine with glibc 2.12 and gcc 4.4.