Summary & Quick Reference

 

Chapter 32 — Section 32.7
Summary & Quick Reference
Chapter recap, complete API cheat sheet, golden rules, decision flowchart, and master interview Q&A

Chapter 32 Summary

32.1
pthread_cancel(tid) sends a cancellation request to the target thread and returns immediately. The actual response depends entirely on the target thread’s state and type settings.
32.2
Cancellation state (ENABLE/DISABLE) controls whether the thread accepts the request. Cancellation type (DEFERRED/ASYNCHRONOUS) controls when it acts. Defaults are ENABLE + DEFERRED. fork() inherits both; exec() resets both to defaults.
32.3
With DEFERRED type, a pending cancel fires only when the thread calls one of the SUSv3 cancellation point functions (sleep, read, write, pthread_cond_wait, etc.). Joining a canceled thread returns PTHREAD_CANCELED as the exit status.
32.4
Compute-bound loops have no natural cancellation points. pthread_testcancel() manually introduces one. Call it periodically inside tight loops at safe points (not while holding a lock).
32.5
Cleanup handlers (pthread_cleanup_push/pop) automatically run on cancellation or pthread_exit() to free resources and unlock mutexes. They execute in LIFO order. push/pop must be paired in the same lexical block (they are macros). Plain return does NOT invoke handlers.
32.6
ASYNCHRONOUS cancellation allows stopping at any instruction. It is dangerous because cleanup handlers cannot determine the state, and most library functions (malloc, mutex ops, stdio) are not async-cancel-safe. Only three functions are: pthread_cancel(), pthread_setcancelstate(), pthread_setcanceltype(). Use it only for pure computation with zero resource operations.

Cancellation Decision Flowchart

pthread_cancel(tid) called

CHECK
Cancel State?

DISABLED
Request stays
PENDING
(fires when re-enabled)

ENABLED

CHECK
Cancel Type?

DEFERRED
Fires at next
CANCELLATION
POINT
Thread
Terminates
→ Cleanup runs

ASYNC
Fires at
ANY instruction
⚠️ Dangerous
Thread
Terminates
→ Cleanup runs

Complete API Quick Reference

Function Signature Purpose Returns
pthread_cancel() int f(pthread_t thread) Send cancellation request to target thread 0 or errno
pthread_setcancelstate() int f(int state, int *old) Set ENABLE or DISABLE for this thread 0 or errno
pthread_setcanceltype() int f(int type, int *old) Set DEFERRED or ASYNCHRONOUS for this thread 0 or errno
pthread_testcancel() void f(void) Act as a cancellation point; terminate if pending cancel void (may not return)
pthread_cleanup_push() void f(void(*fn)(void*), void*) Push cleanup handler onto this thread’s stack void
pthread_cleanup_pop() void f(int execute) Pop handler; if execute≠0 also call it void
PTHREAD_CANCELED (void *)-1 Special constant returned by pthread_join for canceled threads constant

Constants Quick Reference

Cancellation State
Constant Meaning
PTHREAD_CANCEL_ENABLE Thread accepts cancel (DEFAULT)
PTHREAD_CANCEL_DISABLE Request stays pending

Cancellation Type
Constant Meaning
PTHREAD_CANCEL_DEFERRED Fire at cancel point (DEFAULT)
PTHREAD_CANCEL_ASYNCHRONOUS Fire at any instruction

Golden Rules for Thread Cancellation

1
Always call pthread_join() after pthread_cancel() (unless thread is detached). Check PTHREAD_CANCELED to confirm.
2
Always use non-NULL oldstate and oldtype pointers in setcancelstate/setcanceltype for portable code (SUSv3 requirement).
3
Always pair every pthread_cleanup_push() with a pthread_cleanup_pop() in the same lexical block — they are macros with brace expansion.
4
Never hold a mutex or other lock when calling pthread_testcancel() — place it between complete operations at safe state points.
5
Avoid PTHREAD_CANCEL_ASYNCHRONOUS in almost all cases. Use deferred cancellation + pthread_testcancel() even for compute loops — it’s safer and more maintainable.
6
If you need a code block where all steps must complete (transaction-like), use PTHREAD_CANCEL_DISABLE for that section, then restore the old state afterward.
7
Remember: plain return from the thread start function does NOT invoke cleanup handlers. Use pthread_cleanup_pop(1) or pthread_exit() if you need them to run on normal exit too.

Master Interview Questions — All Sections

Conceptual Understanding
Q1. Explain the complete flow: what happens between pthread_cancel() being called and the target thread actually terminating?
Answer: (1) Calling thread invokes pthread_cancel(tid) — returns immediately, non-blocking. (2) The OS marks a cancellation pending for the target thread. (3) If state is DISABLED: request stays pending indefinitely. (4) If state is ENABLED and type is DEFERRED: request fires at the next cancellation-point function call. (5) If state is ENABLED and type is ASYNC: request may fire at any instruction. (6) On cancellation: cleanup handlers run in LIFO order. (7) Thread terminates. (8) If not detached, becomes zombie until joined. (9) pthread_join() returns PTHREAD_CANCELED.
Q2. Why does deferred cancellation use a list of specific cancellation points rather than allowing cancel anywhere?
Answer: Cancellation at an arbitrary point leaves shared state inconsistent — a mutex might be half-locked, a malloc half-allocated, a file half-written. Cancellation points are chosen at functions that (1) can block for extended time (so cancellation is useful) and (2) are at “safe” positions where the system knows the program state is consistent. This makes cleanup handlers reliable and predictable.
Q3. What is the difference between pthread_exit() and cancellation in terms of cleanup handler invocation?
Answer: Both cancellation and pthread_exit() automatically invoke all pushed-but-not-yet-popped cleanup handlers in LIFO order. A plain return from the thread start function does NOT invoke them. So pthread_exit() is the preferred way to exit a thread when you have cleanup handlers installed.

API and Code Questions
Q4. Write the minimal code to safely cancel a thread and verify it was canceled.
pthread_cancel(tid);
pthread_join(tid, &retval);
if (retval == PTHREAD_CANCELED)
    printf("confirmed: thread was canceled\n");
Q5. How do you protect a critical section (must not be interrupted) from cancellation?
int old;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);
/* ... critical section ... */
pthread_setcancelstate(old, NULL); /* restore */
Q6. What is wrong with this cleanup handler setup?
pthread_cleanup_push(handler, NULL);
void *buf = malloc(1024);
/* ... */
pthread_cleanup_pop(0);
Answer: The handler receives NULL — the pointer to buf at the time of push. If the thread is canceled after malloc(), the handler has no way to free the allocated buffer since buf was not yet assigned when push was called. Fix: push the handler AFTER allocating buf, passing buf as the argument.

Design and Tricky Questions
Q7. A compute thread runs for 10 seconds and you need it to respond to cancel within 100ms. What is the best approach?
Answer: Use deferred cancellation (default) and call pthread_testcancel() inside the compute loop at a frequency that ensures it is called at least once every 100ms. For example, if one loop iteration takes ~10ns, call it every 10 million iterations. Do NOT use asynchronous cancellation unless the loop has zero resource operations.
Q8. If a thread forks while holding PTHREAD_CANCEL_DISABLE, what is the child’s initial cancel state?
Answer: The child inherits the calling thread’s cancel state. Since the parent thread had DISABLE set, the child’s main thread also starts with DISABLE. The child will not act on any cancellation requests until it explicitly re-enables cancellation.
Q9. Can the same cleanup handler be used for both cancellation and normal thread exit? How?
Answer: Yes — use pthread_cleanup_pop(1) at the end of the normal code path. Passing 1 removes the handler AND calls it, so cleanup runs in both the cancel case (auto-invoked) and the normal case (invoked by pop). This is the TLPI Listing 32-2 pattern.
Q10. What is a “zombie thread” and how does it relate to cancellation?
Answer: A zombie thread is a terminated thread (via cancellation, pthread_exit, or return) that has not yet been joined. Its resources (stack, thread descriptor) are held until another thread calls pthread_join(). After pthread_cancel(), always join to prevent zombie accumulation — especially in long-running servers that cancel many threads.
Q11. Describe a scenario where not using cleanup handlers could cause the entire program to hang.
Answer: Thread A acquires mutex M, then is canceled at a cancellation point while holding M (e.g., pthread_cond_wait with no cleanup handler to unlock). Now mutex M is permanently locked. Thread B tries to acquire M and blocks forever. Thread C also tries to acquire M and blocks. The program deadlocks completely with no way to recover. Cleanup handlers that unlock M prevent this entirely.

Compile and Run — All Examples

# Compile any example (always link -lpthread)
gcc -Wall -g -o program_name source.c -lpthread

# Example: basic cancellation
gcc -Wall -g -o basic_cancel basic_cancel.c -lpthread
./basic_cancel

# Example: cleanup handler (no arg = cancel path, arg = signal path)
gcc -Wall -g -o thread_cleanup thread_cleanup.c -lpthread
./thread_cleanup          # cancel path
./thread_cleanup x        # normal signal path

# Example: testcancel in compute loop
gcc -Wall -g -O2 -o testcancel_compute testcancel_compute.c -lpthread
./testcancel_compute

# Check for thread errors with address sanitizer + thread sanitizer
gcc -Wall -g -fsanitize=address,thread -o safe_test source.c -lpthread

Chapter 32 Complete! 🎉
You now know everything about POSIX Thread Cancellation — from basic requests to async safety.

← Back to Chapter Index EmbeddedPathashala Home

© EmbeddedPathashala | TLPI Chapter 32 | Section 32.7 — Summary

Leave a Reply

Your email address will not be published. Required fields are marked *