This is the mail archive of the libc-alpha@sourceware.cygnus.com mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Re: Help: Unwinding the C++ stack...throw, longjmp & threads


Ulrich Drepper wrote:
> 
> "George T. Talbot" <george@moberg.com> writes:
> 
> > I asked the author of Programming POSIX Threads (David Butenhof) who
> > said that the standard doesn't really define that case, but that
> > programs which use return to avoid pthread_cleanup_pop() aren't
> > portable.
> 
> I don't care whether it's unportable, I need this for the implementation.

I don't understand what you need.  Do you need the return to actually
call some function like _pthread_cleanup_pop()?

> > By the way, won't the current implementation have a problem with this
> > case?
> 
> No, since I call the underlying function.

I'm not sure exactly what you're saying.

glibc 2.1.1 has the following in <pthreads.h>:

#define
pthread_cleanup_push(routine,arg)                                     \
  { struct _pthread_cleanup_buffer
_buffer;                                   \
    _pthread_cleanup_push (&_buffer, (routine), (arg));
extern void _pthread_cleanup_push __P ((struct _pthread_cleanup_buffer
*__buffer,
                                        void (*__routine) (void *),
                                        void *__arg));
/* Remove a cleanup handler installed by the matching
pthread_cleanup_push.
   If EXECUTE is non-zero, the handler function is called. */
#define
pthread_cleanup_pop(execute)                                          \
    _pthread_cleanup_pop (&_buffer, (execute)); }

So, the expansion in the user code looks like this:

	pthread_cleanup_push(blah, arg);
	...
	do_work();
	...
	if (something)
		return;
	...
	pthread_cleanup_pop(execute);

		expands to

	{ struct _pthread_cleanup_buffer _buffer;
	  _pthread_cleanup_push(&_buffer, (blah), (arg));
	...
	do_work();
	...
	if (something)
		return;
	...
	_pthread_cleanup_pop(&_buffer, (execute)); }

If something is TRUE, and the function returns at the return statement,
then _pthread_cleanup_pop(&_buffer, (execute)) will not get called, and
the library will leave the cleanup handler registered when the scope is
exited.  That doesn't seem right.

Here's a test program.

#include <pthread.h>
#include <stdio.h>
void    cleanup(void *arg)
{
    printf("cleanup runs.\n"); fflush(stdout);
}
void    test(int something, int execute)
{
    printf("\n\nsomething=%d, execute=%d: ", something, execute);
    fflush(stdout);
    pthread_cleanup_push(cleanup, 0);
    if (something)
        return;
    pthread_cleanup_pop(execute);
}
int main(int argc, char *argv[])
{
    test(0, 0);
    test(0, 1);
    test(1, 0);
    test(1, 1);
}

Here's the output:

$ ./a.out
something=0, execute=0:
something=0, execute=1: cleanup runs.
something=1, execute=0:
something=1, execute=1:

In the fourth case, the cleanup has not run.  This is glibc 2.1.1.  I
suspect, also, that when something is TRUE, that the cleanup handler is
still registered with the C library so that if the thread is cancelled,
the cleanup will run even though the scope is exited.  In other words,
if the thread is cancelled after the fourth call to test(1,1), then the
cleanup would run.

Is that correct behavior?  I would think that the cleanup should only
run if the thread is cancelled when the cleanup scope is still active.

Using exceptions works the same, except that there is no problem with
cleanup handlers remaining registered, as the library would no longer
keep track of cleanup handlers, and the normal stack unwind would take
care of cases like the one above.

> >       {                                               \
> >               void    (*__function) (void *)  = blah; |
> >               void    *__arg                  = arg;  | pthread_cleanup_push()
> >                                                       |
> >               __try                                   |
> >               {                                       /
> >                       ...
> >                       do_work();
> >                       ...
> >               }                                       \
> >               __catch (...)                           |
> >               {                                       | pthread_cleanup_pop()
> >                       __function(arg);                |
> >                       __throw;                        |
> >               }                                       |
> >               if (execute) __function(arg);           |
> >       }                                               /
> 
> What I hope to get are optimizations.  E.g., the function is constant.
> Therefore the compiler should put a function pointer in an appropriate
> place where it does not have to be loaded each time.  Since the access
> in the __catch part is generated by the compiler as well it can know
> where to get the value from.

I think that you are correct, and the compiler would in fact recognize
that __function and __arg are constant.

> > (I'm not sure whether the above should be __catch(...) or
> > __catch(POSIX_cancel c).  I could argue for either one.)
> 
> The latter.

OK.

> > I agree that this needs to be done.  I'm just saying that it's a problem
> > that I don't know that I could get right, as I don't have enough
> > experience and familiarity with the C library.  Is there an automated
> > way of doing this?
> 
> One could find this out partially using some scripts but they need in
> some cases help from people and also have to rerun after every change.

Since a compiler patch is required to add exceptions to C anyway, maybe
we could talk the GCC folks into optimizing their exception information
so that the space problem would just go away. ;^)

--
George T. Talbot
<george@moberg.com>

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]