Subject: lib/30734: pthread_once() is not cancellation-safe.
To: None <lib-bug-people@netbsd.org, gnats-admin@netbsd.org,>
From: None <tshiozak@netbsd.org>
List: netbsd-bugs
Date: 07/12/2005 06:11:00
>Number:         30734
>Category:       lib
>Synopsis:       pthread_once() is not cancellation-safe.
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    lib-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Tue Jul 12 06:11:00 +0000 2005
>Originator:     Takuya SHIOZAKI
>Release:        trunk
>Organization:
foo
>Environment:
NetBSD aoi.prod.astec.co.jp 3.99.3 NetBSD 3.99.3 (AOI) #0: Fri Apr 15 22:38:49 JST 2005  tshiozak@aoi.prod.astec.co.jp:/usr/home/current-build/src/sys/arch/i386/compile/AOI i386

>Description:
POSIX says about cancellation handling on pthread_once():

>The function pthread_once() is not a cancellation point.  However, if
>init_routine() is a cancellation point and is cancelled, the effect on
>once_control is as if pthread_once() was never called.

but, our pthread_once() do not release the mutex and it may occur deadlock.
>How-To-Repeat:
A test program is here:

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


void
init(void)
{
        printf("init: %p\n", (void *)pthread_self());
        while (1) {
                printf("tick.\n");
                sleep(1);
                pthread_testcancel();
        }
}

pthread_once_t once = PTHREAD_ONCE_INIT;

void *
foo(void *p)
{
        printf("foo: %p\n", (void *)pthread_self());
        pthread_once(&once, &init);
        printf("foo done: %p\n", (void *)pthread_self());
        return NULL;
}

int
main()
{
        pthread_t t;

        printf("1\n");
        pthread_create(&t, NULL, &foo, NULL);
        printf("2\n");
        sleep(1);
        printf("3\n");
        pthread_cancel(t);
        printf("4\n");
        pthread_join(t, NULL);
        printf("5\n");
        pthread_once(&once, &init); /* deadlock at this point. */
        printf("6\n");

        return 0;
}

>Fix:
Index: pthread_mutex.c
===================================================================
RCS file: /cvsroot/src/lib/libpthread/pthread_mutex.c,v
retrieving revision 1.18
diff -u -r1.18 pthread_mutex.c
--- pthread_mutex.c     14 Mar 2004 01:19:42 -0000      1.18
+++ pthread_mutex.c     12 Jul 2005 05:59:55 -0000
@@ -450,17 +450,25 @@
 }
 
 
+static void
+once_cleanup(void *closure)
+{
+
+       pthread_mutex_unlock((pthread_mutex_t *)closure);
+}
+
 int
 pthread_once(pthread_once_t *once_control, void (*routine)(void))
 {
 
        if (once_control->pto_done == 0) {
                pthread_mutex_lock(&once_control->pto_mutex);
+               pthread_cleanup_push(&once_cleanup, &once_control->pto_mutex);
                if (once_control->pto_done == 0) {
                        routine();
                        once_control->pto_done = 1;
                }
-               pthread_mutex_unlock(&once_control->pto_mutex);
+               pthread_cleanup_pop(1);
        }
 
        return 0;