`pthread_cancel` undefined behavior

use std::time::Duration;
use std::os::unix::thread::JoinHandleExt;

fn main() {
    let handle = std::thread::spawn(|| {
        std::thread::sleep(Duration::from_secs(2));
        println!("thread");
    });
    unsafe { libc::pthread_cancel(handle.as_pthread_t()); }
    for i in 0..10000 { println!("{:?}", i); }
}

(Playground)

When running on playground, this loop runs to completion.

On my laptop, I get Aborted (core dumped) after a few iterations.

Does pthread_cancel have such undefined behavior?

(If you can come up with an alternative way to abruptly kill a thread I'd also be happy with it)

Thanks in advance

Abrubtly killing a thread is always undefined behaviour. So many things can go wrong, one example: What if the thread is somewhere in the middle of a memory allocation and killing it there leaves your heap in an invalid state? And in any case your destructors would not get dropped, so if you use anything that allocates memory, that's a memory leak right there.

It is ancient wisdom that you will never be able to sensibly forcefully kill a system thread. Try to design your system such that it is not necessary. You may want to use processes instead of threads, or you may just need to make a loop in the thread checking a queue for a message that it should stop. Or you may just need to forcefully restart the whole program if you need to cancel whatever operation is occuring.

1 Like

ANY function/method in Rust that is "unsafe" requires that you, the programmer, 100% ensures that any necessary pre-conditions are met before calling the function; otherwise, it is 100% undefined behavior.

When you write, unsafe { .... } you are asserting to the compiler that you have done what is necessary to ensure that calling the unsafe functions that you call in that unsafe block have all of their pre-conditions met. If you haven't done that, you are lying to the compiler, and if you lie to the compiler, that is undefined behavior.

You will note that the documentation for pthread::cancel (from the Linux man pages):

A thread's cancelability state, determined by pthread_setcancelstate(3), can be enabled (the default for new threads) or
disabled. If a thread has disabled cancellation, then a cancellation request remains queued until the thread enables can‐
cellation. If a thread has enabled cancellation, then its cancelability type determines when cancellation occurs.

What this says, is that by default new threads have cancellation enabled. Any time a thread is doing something where it shouldn't be canceled, that thread must disable cancellation and then re-enable it when that block is done. If your child thread isn't doing that, it will be undefined behavior.

However, it is important to understand that it is effectively impossible to actually implement this contract correctly, as the following post elucidates: https://lwn.net/Articles/683118/

So, it really boils down to what @radix said.

2 Likes