Bug 37614

Summary: libc_r aborts when exiting thread is canceled
Product: Base System Reporter: Archie Cobbs <archie>
Component: binAssignee: Archie Cobbs <archie>
Status: Closed FIXED    
Severity: Affects Only Me    
Priority: Normal    
Version: 4.5-RELEASE   
Hardware: Any   
OS: Any   
Attachments:
Description Flags
file.diff none

Description Archie Cobbs 2002-04-30 23:20:01 UTC
There seems to be a bug or problem in our pthreads implementation.

Consider a thread that has exited and is invoking its cleanup routines.
Another thread cancels that thread. Then one of the cleanup routines
invokes a function which is a cancelation point.    

Here's what happens:

> Fatal error 'Thread 0x806fc00 has called pthread_exit() from a destructor. POS
IX 1003.1 1996 s16.2.5.2 does not allow this!' at line ? in file /usr/src/lib/li
bc_r/uthread/uthread_exit.c (errno = ?)
>
> Program received signal SIGABRT, Aborted.
> 0x281ee53c in kill () from /usr/lib/libc_r.so.4
> (gdb) where
> #0  0x281ee53c in kill () from /usr/lib/libc_r.so.4
> #1  0x2823878a in abort () from /usr/lib/libc_r.so.4
> #2  0x2820504a in _thread_exit () from /usr/lib/libc_r.so.4
> #3  0x282050e9 in pthread_exit () from /usr/lib/libc_r.so.4
> #4  0x282370d7 in pthread_testcancel () from /usr/lib/libc_r.so.4
> #5  0x282370f8 in _thread_enter_cancellation_point () from /usr/lib/libc_r.so.
4
> #6  0x28236bc4 in close () from /usr/lib/libc_r.so.4
> #7  0x28193416 in timeout_fp_close (cookie=0x80645b4)
>     at /usr/home/archie/libpdel/io/timeout_fp.c:148
> #8  0x2822622b in fclose () from /usr/lib/libc_r.so.4
> #9  0x2818e780 in foobar_connection_free (connp=0xbfadced4)
>     at /usr/home/archie/libpdel/http/http_connection.c:136
> #10 0x28185d07 in foobar_server_connection_cleanup (arg=0x8062584)
>     at /usr/home/archie/libpdel/http/http_server.c:650
> #11 0x2820611d in pthread_cleanup_pop () from /usr/lib/libc_r.so.4
> #12 0x2820511e in pthread_exit () from /usr/lib/libc_r.so.4
> #13 0x281c5f99 in _thread_start () from /usr/lib/libc_r.so.4
> #14 0x0 in ?? ()

It seems that pthread_testcancel() should do nothing if the
thread is already in the process of exiting. Below is a patch.

Fix: This is just a guess, but...
Comment 1 Archie Cobbs 2002-04-30 23:23:45 UTC
Oops, in that patch the "(_thread_run->flags & PTHREAD_EXITING) != 0"
should of course instead be "(_thread_run->flags & PTHREAD_EXITING) == 0".

-Archie

__________________________________________________________________________
Archie Cobbs     *     Packet Design     *     http://www.packetdesign.com
Comment 2 Archie Cobbs 2002-05-01 19:07:46 UTC
Test case below.

-Archie

__________________________________________________________________________
Archie Cobbs     *     Packet Design     *     http://www.packetdesign.com

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <sched.h>
#include <pthread.h>
#include <err.h>

static void
thread_cleanup(void *arg)
{
        sched_yield();
        printf("Thread: executing cleanup...\n");
        pthread_testcancel();
}

static void *
thread_main(void *arg)
{
        pthread_cleanup_push(thread_cleanup, NULL);
        printf("Thread: sleeping 1 second...\n");
        sleep(1);
        printf("Thread: sending SIGTERM...\n");
        kill(getpid(), SIGTERM);
        sched_yield();
        printf("Thread: exiting...\n");
        return (NULL);
}

int
main(int argc, char **argv)
{
        pthread_t tid;
        sigset_t sigs;
        int sig;

        /* Spawn thread */
        printf("Main: spawning thread...\n");
        if ((errno = pthread_create(&tid, NULL, thread_main, NULL)) != 0)
                err(1, "pthread_create");

        /* Wait for signal */
        sigemptyset(&sigs);
        sigaddset(&sigs, SIGINT);
        sigaddset(&sigs, SIGTERM);
        if (sigprocmask(SIG_BLOCK, &sigs, NULL) == -1)
                err(1, "sigprocmask");
        printf("Main: waiting for signal...\n");
        if (sigwait(&sigs, &sig) == -1)
                err(1, "sigwait");

        /* Cancel thread */
        printf("Main: canceling thread...\n");
        pthread_cancel(tid);

        /* Done */
        usleep(500);
        printf("Main: exiting...\n");
        return (0);
}
Comment 3 Archie Cobbs 2002-05-02 20:18:02 UTC
Oops, my patch is incomplete as pointed out by Daniel Eischen.
The combined patchfile (both patches are required) is below.

NOTE: these patches are for -stable; -current is very similar.

-Archie

__________________________________________________________________________
Archie Cobbs     *     Packet Design     *     http://www.packetdesign.com

Index: uthread_cancel.c
===================================================================
RCS file: /home/cvs/freebsd/src/lib/libc_r/uthread/uthread_cancel.c,v
retrieving revision 1.3.2.4
diff -u -r1.3.2.4 uthread_cancel.c
--- uthread_cancel.c	19 Aug 2001 11:45:58 -0000	1.3.2.4
+++ uthread_cancel.c	2 May 2002 19:18:37 -0000
@@ -15,7 +15,8 @@
 
 	if ((ret = _find_thread(pthread)) != 0) {
 		/* NOTHING */
-	} else if (pthread->state == PS_DEAD || pthread->state == PS_DEADLOCK) {
+	} else if (pthread->state == PS_DEAD || pthread->state == PS_DEADLOCK
+	    || (pthread->flags & PTHREAD_EXITING) != 0) {
 		ret = 0;
 	} else {
 		/* Protect the scheduling queues: */
@@ -186,7 +187,8 @@
 pthread_testcancel(void)
 {
 	if (((_thread_run->cancelflags & PTHREAD_CANCEL_DISABLE) == 0) &&
-	    ((_thread_run->cancelflags & PTHREAD_CANCELLING) != 0)) {
+	    ((_thread_run->cancelflags & PTHREAD_CANCELLING) != 0) &&
+	    ((_thread_run->flags & PTHREAD_EXITING) == 0)) {
 		/*
 		 * It is possible for this thread to be swapped out
 		 * while performing cancellation; do not allow it
Comment 4 Archie Cobbs freebsd_committer freebsd_triage 2002-05-05 05:36:34 UTC
Responsible Changed
From-To: freebsd-bugs->archie

Assign bug to me since I'm fixing it.
Comment 5 Archie Cobbs freebsd_committer freebsd_triage 2002-05-07 19:47:52 UTC
State Changed
From-To: open->closed

Fixed in -current and -stable.