Bug 13613

Summary: Cancellation is broken in single-threaded processes
Product: glibc Reporter: Rich Felker <bugdal>
Component: nptlAssignee: Siddhesh Poyarekar <siddhesh>
Status: RESOLVED FIXED    
Severity: normal CC: siddhesh
Priority: P2 Flags: fweimer: security-
Version: unspecified   
Target Milestone: ---   
Host: Target:
Build: Last reconfirmed:

Description Rich Felker 2012-01-22 23:54:42 UTC
Cancellation is not acted upon unless the process has more than one thread. Consider the program:

#include <pthread.h>
#include <unistd.h>
int main()
{
    char c;
    pthread_cancel(pthread_self());
    read(0, &c, 1);
    return 1;
}

Being a cancellation point, read should cause the pending cancellation request to be acted upon, and the program should terminate with status 0 as a consequence of the last thread exiting from cancellation. However, under glibc/NPTL, the program waits for input then terminates with status 1.

I suspect the cancellation wrapper code is incorrectly checking SINGLE_THREAD_P and (wrongly) assuming that it implies cancellation is impossible.
Comment 1 Rich Felker 2012-03-17 20:36:37 UTC
Ping. Anyone looked at this?
Comment 2 Siddhesh Poyarekar 2012-05-08 16:45:49 UTC
Async cancellation points get activated only when the number of threads are greater than 1, which is why you're seeing the behaviour you see. What is your use case anyway? I don't see why something like this should be needed at all. Of course, I couldn't find anything in the posix definition of pthread_cancel to not allow something like this:

http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_cancel.html
Comment 3 Rich Felker 2012-05-08 21:58:29 UTC
I don't have a specific usage case in mind, and I don't suspect anyone would intentionally try to cancel the main thread in a single-threaded program knowing it's the main thread. Where the issue is likely to matter is in code that doesn't know it's dealing with the main thread of a single-threaded program, e.g. in a library.

The type of application it's likely to affect is an application that does `N` jobs in parallel by spawning `N-1` threads and using the main thread as a worker thread. As an example, I have a parallel DNS resolver tool that does this. If it turns out that `N` is 1, no threads get created, and the process runs single-threaded. My DNS resolver does not have the threads using pthread_cancel on themselves, but I could imagine moderately-contrived cases where you might want to do this. Self-cancellation is a nice way to setup future code to abort if it does anything that would block, while avoiding exiting immediately. (And if you've written your program this way, you can still call functions that are cancellation points if you temporarily block cancellation around the call.)

In any case, glibc's behavior is non-conformant. POSIX does not make any exceptions to the rules about how cancellation works for the special case of single-threaded programs. (In fact the only special case I can find that POSIX makes for single-threaded programs is that you're allowed to call async-signal-unsafe functions after fork() in a single-threaded program.)
Comment 4 Jakub Jelinek 2012-05-09 10:44:47 UTC
If anything, pthread_cancel should for self-cancellation (with a comment)
      THREAD_SETMEM (THREAD_SELF, header.multiple_threads, 1);
#ifndef TLS_MULTIPLE_THREADS_IN_TCB
      __pthread_multiple_threads = *__libc_multiple_threads_ptr = 1;
#endif
Then this will work just fine, without any overhead in the common path, except a few insns in the already slow pthread_cancel.
Comment 5 Rich Felker 2012-05-09 14:25:09 UTC
I agree that's a simple and acceptable fix.
Comment 6 Siddhesh Poyarekar 2012-05-09 14:56:31 UTC
I have submitted a patch for review:

http://sourceware.org/ml/libc-alpha/2012-05/msg00458.html
Comment 7 Siddhesh Poyarekar 2012-05-15 04:14:51 UTC
Fix committed to master. Thanks for reporting this.