32.5a — Why Do We Need Cleanup Handlers?
Thread cancellation is dangerous without cleanup. Consider a thread that:
Cleanup handlers are programmer-defined functions that are automatically invoked when a thread is canceled (or calls pthread_exit()). They let you undo partial work and restore consistent state.
32.5b — The Cleanup Handler Stack
Each thread maintains its own stack of cleanup handlers. When a thread is canceled (or calls pthread_exit()), the handlers are called in LIFO order — last pushed is first called.
This LIFO order mirrors the nesting of code — resources acquired last should be released first (just like C++ destructors and RAII). After all handlers complete, the thread terminates.
32.5c — The API: push and pop
pthread_cleanup_push() — Adding a Handler
| Parameter | Type | Description |
|---|---|---|
| routine | void (*)(void*) | Pointer to the cleanup function. Must have signature void handler(void *arg) |
| arg | void * | Argument passed to routine when invoked. Can be a pointer to any data (cast as needed). |
The cleanup function must have this exact signature:
void
myCleanupHandler(void *arg)
{
/* arg is the value passed to pthread_cleanup_push() */
MyResource *res = (MyResource *)arg; /* cast as needed */
free(res->buffer);
pthread_mutex_unlock(&res->lock);
}
pthread_cleanup_pop() — Removing a Handler
| execute value | What Happens |
|---|---|
| nonzero (e.g. 1) | Removes the handler from the stack AND executes it. Use when you want cleanup to run regardless of whether you were canceled. |
| zero (0) | Removes the handler from the stack without executing it. Use when the cleanup is no longer needed (e.g., you already freed the resource manually). |
32.5d — ⚠️ Critical: The Macro Brace-Pairing Rule
On Linux (and many other implementations), pthread_cleanup_push() and pthread_cleanup_pop() are implemented as macros that expand to code containing an opening brace { and a closing brace } respectively.
This has an important consequence: every push must be paired with exactly one pop in the same lexical block.
pthread_cleanup_push(func, arg);
...
if (condition) {
pthread_cleanup_pop(0); /* WRONG */
}
/* Braces don't match — compile error
or undefined behavior */
pthread_cleanup_push(func, arg);
...
if (condition) {
/* do conditional work */
}
pthread_cleanup_pop(0); /* CORRECT */
/* push and pop at same nesting
level — braces match */
pthread_cleanup_push() and its matching pthread_cleanup_pop() are limited to that lexical scope due to the macro brace expansion. Declare variables you’ll need in the cleanup handler before the push call.32.5e — Cleanup Handlers and pthread_exit()
Cleanup handlers are invoked not only when a thread is canceled, but also when a thread calls pthread_exit(). This is extremely useful — a thread can call pthread_exit() to terminate early, and any cleanup handlers that were pushed but not yet popped will still run automatically.
| How Thread Terminates | Cleanup Handlers Called? |
|---|---|
| pthread_cancel() → reaches cancel point | ✅ YES — all unpoped handlers run |
| pthread_exit() | ✅ YES — all unpoped handlers run |
| return from thread start function | ❌ NO — handlers NOT called on plain return |
return NULL;, cleanup handlers are not automatically invoked. Only cancellation and pthread_exit() trigger them. If you want cleanup to always run, either use pthread_cleanup_pop(1) before returning, or use pthread_exit() instead of return.Code Example 1: TLPI Listing 32-2 — Complete Cleanup Handler Example
Thread allocates memory and locks a mutex. Cleanup handler frees memory and unlocks mutex — called both on cancellation and on normal flow via pthread_cleanup_pop(1).
/* thread_cleanup.c — Modeled after TLPI Listing 32-2 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static int glob = 0; /* predicate variable for condition wait */
/* -----------------------------------------------------------
Cleanup handler: frees buffer and unlocks mutex.
Called automatically on cancellation or pthread_cleanup_pop(1).
----------------------------------------------------------- */
static void
cleanupHandler(void *arg)
{
int s;
printf("cleanup: freeing buffer at %p\n", arg);
free(arg); /* free allocated buffer */
printf("cleanup: unlocking mutex\n");
s = pthread_mutex_unlock(&mtx); /* unlock held mutex */
if (s != 0) perror("pthread_mutex_unlock");
}
/* -----------------------------------------------------------
Thread function
----------------------------------------------------------- */
static void *
threadFunc(void *arg)
{
int s;
void *buf;
/* Step 1: allocate memory (not a cancellation point) */
buf = malloc(0x10000);
printf("thread: allocated buffer at %p\n", buf);
/* Step 2: lock mutex (not a cancellation point) */
s = pthread_mutex_lock(&mtx);
if (s != 0) perror("pthread_mutex_lock");
/* Step 3: install cleanup handler AFTER acquiring resources
arg = buf, so handler knows what to free */
pthread_cleanup_push(cleanupHandler, buf);
/* Step 4: wait on condition variable (CANCELLATION POINT)
— if canceled here, cleanupHandler is auto-called */
while (glob == 0) {
s = pthread_cond_wait(&cond, &mtx); /* cancel point */
if (s != 0) perror("pthread_cond_wait");
}
printf("thread: condition wait loop completed\n");
/* Step 5: pop handler with execute=1 → runs even on normal exit */
pthread_cleanup_pop(1);
return NULL;
}
int
main(int argc, char *argv[])
{
pthread_t thr;
void * res;
int s;
s = pthread_create(&thr, NULL, threadFunc, NULL);
if (s != 0) { perror("pthread_create"); exit(EXIT_FAILURE); }
sleep(2); /* give thread time to start and enter wait */
if (argc == 1) {
/* No argument → cancel the thread */
printf("main: canceling thread\n");
s = pthread_cancel(thr);
if (s != 0) perror("pthread_cancel");
} else {
/* Argument supplied → signal condition variable normally */
printf("main: signaling condition variable\n");
glob = 1;
s = pthread_cond_signal(&cond);
if (s != 0) perror("pthread_cond_signal");
}
s = pthread_join(thr, &res);
if (s != 0) { perror("pthread_join"); exit(EXIT_FAILURE); }
if (res == PTHREAD_CANCELED)
printf("main: thread was canceled\n");
else
printf("main: thread terminated normally\n");
exit(EXIT_SUCCESS);
}
pthread_cleanup_pop(1)). This is great design: write cleanup once, get safety in both cases.Code Example 2: Multiple Cleanup Handlers — LIFO Order
Three cleanup handlers are pushed. When cancelled, they execute in reverse order — demonstrating LIFO behavior.
/* multi_cleanup.c — Multiple handlers execute in LIFO order */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
static void handlerA(void *arg) { printf(" handlerA: releasing %s\n", (char *)arg); }
static void handlerB(void *arg) { printf(" handlerB: releasing %s\n", (char *)arg); }
static void handlerC(void *arg) { printf(" handlerC: releasing %s\n", (char *)arg); }
static void *
threadFunc(void *arg)
{
printf("Thread: pushing handlers A, B, C (will be called C, B, A)\n");
pthread_cleanup_push(handlerA, "resource-A (file)"); /* pushed 1st */
pthread_cleanup_push(handlerB, "resource-B (mutex)"); /* pushed 2nd */
pthread_cleanup_push(handlerC, "resource-C (buffer)"); /* pushed 3rd */
printf("Thread: all handlers pushed, now sleeping (cancel point)\n");
sleep(10); /* cancellation point — cancel fires here */
/* If not canceled, pop all handlers */
pthread_cleanup_pop(0); /* pop C without executing */
pthread_cleanup_pop(0); /* pop B without executing */
pthread_cleanup_pop(0); /* pop A without executing */
printf("Thread: finished normally\n");
return NULL;
}
int
main(void)
{
pthread_t tid;
void *res;
pthread_create(&tid, NULL, threadFunc, NULL);
sleep(1);
printf("Main: canceling thread\n");
pthread_cancel(tid);
pthread_join(tid, &res);
printf("Main: thread %s\n",
res == PTHREAD_CANCELED ? "was canceled" : "finished normally");
return 0;
}
Interview Questions — Section 32.5
pop(0) removes the top handler from the stack without calling it — use when cleanup is no longer needed. pop(1) removes the handler and also executes it — use when you want cleanup to happen regardless of whether you were canceled.{ (push) and closing brace } (pop). Mismatching their scope causes compilation errors or undefined behavior due to unbalanced braces.return from the thread start function does NOT invoke cleanup handlers. Only cancellation and pthread_exit() trigger them automatically. To handle cleanup on return, call pthread_cleanup_pop(1) before returning, or use pthread_exit().void handler(void *arg) — takes a single void * parameter (the value passed as the second argument to pthread_cleanup_push) and returns void. Inside the handler, cast the void * to the appropriate type.