Bug 1195

Summary: behavior of pthread_cleanup_pop does not behave as described
Product: uClibc Reporter: Mark Gondree <gondree>
Component: ThreadsAssignee: unassigned
Status: NEW ---    
Severity: normal CC: gondree, uclibc-cvs
Priority: P5    
Version: 0.9.31   
Target Milestone: 0.9.32   
Hardware: Other   
OS: Other   
Host: Target:
Build:

Description Mark Gondree 2010-02-27 03:03:28 UTC
According to its description, pthread_cleanup_pop:
  "shall remove the routine at the top of the calling thread's cancellation 
  cleanup stack and optionally invoke it (if execute is non-zero)."

However, this implementation executes and then removes it from the cleanup stack (order is reversed). This alternate behavior has the potential to cause some code to deadlock. Example is below.

If you agree this is problematic, the solution is quite simple:
in, libpthread/linuxthread/cancel.c

void _pthread_cleanup_pop(struct _pthread_cleanup_buffer * buffer, int execute) {
  pthread_descr self = thread_self();
  if (execute) buffer->__routine(buffer->__arg);
  THREAD_SETMEM(self, p_cleanup, buffer->__prev);
}

s/b
  THREAD_SETMEM(self, p_cleanup, buffer->__prev);
  if (execute) buffer->__routine(buffer->__arg);

Similar changes may be required in _pthread_cleanup_pop_restore

=======
Notional example of deadlocking code:
    If a thread is cancelled at point A, cancellation-handler 
    ch1() is executed for a second time (this time with the 
    mutex m acquired) which causes a deadlock.

void foo(void) {
    ...
    pthread_mutex_lock(&m);
    pthread_cleanup_push(&ch2, NULL);
    ...
    while(condition) {
        ....
        pthread_mutex_unlock(&m);
        pthread_cleanup_push(&ch1, NULL);
        ....
        pthread_cleanup_pop(1);
    }
    pthread_cleanup_pop(1);
}

void *ch1(void *p) {
    pthread_mutex_lock(&m);
    ....
    (A)
    ....
}

void *ch2(void *p) {
    pthread_mutex_unlock(&m);
    ....
}