Cancellation State and Type

 

Chapter 32 — Section 32.2
Cancellation State and Type
A thread controls whether it accepts cancellation (state) and when it acts on it (type)

Overview: Two Dimensions of Control

When a thread receives a cancellation request, how it responds is controlled by two independent settings — state and type. Think of them as two gates the request must pass through.

pthread_cancel()
Request
Arrives
GATE 1
Cancellation
State
ENABLE / DISABLE
if ENABLED
GATE 2
Cancellation
Type
DEFERRED / ASYNC
Thread
Terminates

If state is DISABLED, the request stays pending — never reaches Gate 2. If state is ENABLED, the type decides timing: DEFERRED waits for a safe cancellation point; ASYNCHRONOUS allows cancellation anywhere.

32.2a — Cancellation State: pthread_setcancelstate()

#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
Returns: 0 on success, positive error number on error

The Two State Values

PTHREAD_CANCEL_ENABLE
Thread accepts cancellation requests. This is the default for all newly created threads. Whether it acts immediately or waits depends on the cancellation type.
PTHREAD_CANCEL_DISABLE
Thread ignores cancellation requests for now. The request stays pending and will be delivered later when the thread re-enables cancellation. Useful to protect critical sections.

The oldstate Parameter — Saving & Restoring

pthread_setcancelstate() stores the previous state in *oldstate. This lets you temporarily disable cancellation and then restore it afterward:

int oldstate;

/* Disable cancellation — save old state */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);

/* ... critical section: all steps MUST complete ... */
update_database();
write_log_entry();
flush_buffer();

/* Restore previous state (could be ENABLE or DISABLE) */
pthread_setcancelstate(oldstate, NULL);

⚠️ Portability: Always pass a non-NULL oldstate
Linux allows NULL for oldstate if you don’t need the previous value. But SUSv3 does not guarantee this is supported on all systems. For portable code, always pass a valid int * variable even if you don’t use the result.

32.2b — Cancellation Type: pthread_setcanceltype()

#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
Returns: 0 on success, positive error number on error

The Two Type Values

PTHREAD_CANCEL_DEFERRED
DEFAULT
Cancellation is delayed until the thread reaches a “cancellation point” — a specific system call like sleep(), read(), write(), etc. Safe and predictable.
PTHREAD_CANCEL_ASYNCHRONOUS
DANGEROUS — Use Rarely
Thread may be canceled at any machine instruction, not just cancellation points. Very hard to make safe — mutexes may stay locked, malloc may be mid-call. Details in Section 32.6.

Like oldstate, always pass a non-NULL oldtype pointer for portable code even if you won’t use the returned value.

32.2c — Inheritance: fork() and exec()

What happens to a thread’s cancellation state and type when it calls fork() or exec()? The rules are clear:

Syscall Effect on Cancellation State Effect on Cancellation Type
fork() Child inherits the calling thread’s current state (ENABLE or DISABLE) Child inherits the calling thread’s current type (DEFERRED or ASYNC)
exec() Reset to default: PTHREAD_CANCEL_ENABLE Reset to default: PTHREAD_CANCEL_DEFERRED
Why does exec() reset them?
When a process calls exec(), a completely new program image is loaded. All previous thread state is irrelevant — the new program starts with a single fresh main thread with default cancellation settings. The old custom settings from the previous program have no meaning in the new program.

Decision Matrix: State + Type Combinations

State Type What Happens to Cancel Request?
DISABLE Any Request stays pending. Thread continues running. Will fire when state is re-enabled.
ENABLE DEFERRED Thread waits until it reaches a cancellation point (e.g., sleep, read, write). Then terminates. ← Default & recommended
ENABLE ASYNCHRONOUS Thread may be canceled at any instruction — even inside malloc() or mutex lock. Very dangerous. Rarely safe.

Code Example 1: Temporarily Disabling Cancellation

A thread disables cancellation during a critical section (multi-step database update), then re-enables it.

/* cancel_disable.c - Protect a critical section from cancellation */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

static void *
workerThread(void *arg)
{
    int oldstate;

    printf("Thread: running, default state=ENABLE, type=DEFERRED\n");

    /* Phase 1: Normal work (cancellation ENABLED) */
    printf("Thread: doing normal work (cancellable)\n");
    sleep(2);    /* cancellation point — if cancel pending, fires here */

    /* Phase 2: Critical section — DISABLE cancellation */
    printf("Thread: entering critical section — disabling cancel\n");
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);

    /* These three steps MUST all complete together */
    printf("Thread: step 1 — writing record\n");
    sleep(1);    /* sleep() here is NOT a cancellation point (cancel disabled) */
    printf("Thread: step 2 — updating index\n");
    sleep(1);
    printf("Thread: step 3 — committing transaction\n");
    sleep(1);

    /* Phase 3: Restore previous state */
    printf("Thread: critical section done — restoring cancel state\n");
    pthread_setcancelstate(oldstate, NULL);

    /* If a cancel was pending, it fires at the next cancellation point */
    printf("Thread: back to normal work\n");
    sleep(10);   /* Any pending cancel request fires here */

    printf("Thread: finished normally\n");
    return NULL;
}

int
main(void)
{
    pthread_t tid;
    void *res;

    pthread_create(&tid, NULL, workerThread, NULL);
    sleep(3);   /* Wait until thread is inside critical section */

    printf("Main: sending cancel (thread is in critical section)\n");
    pthread_cancel(tid);    /* Cancel pending — but critical section is protected */

    pthread_join(tid, &res);

    printf("Main: thread %s\n",
           (res == PTHREAD_CANCELED) ? "was canceled" : "finished normally");
    return 0;
}

/* Expected output:
   Thread: running, default state=ENABLE, type=DEFERRED
   Thread: doing normal work (cancellable)
   Thread: entering critical section — disabling cancel
   Thread: step 1 — writing record
   Main: sending cancel (thread is in critical section)
   Thread: step 2 — updating index
   Thread: step 3 — committing transaction
   Thread: critical section done — restoring cancel state
   Main: thread was canceled
   [cancel fires at sleep(10) after state is re-enabled]
*/

Code Example 2: Checking & Saving Cancel Type

Demonstrates saving/restoring cancel type, and shows the oldtype portability pattern.

/* cancel_type.c - Saving and restoring cancel type */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

static void *
threadFunc(void *arg)
{
    int oldstate, oldtype;

    /* Always use non-NULL for portability (SUSv3 requirement) */
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);

    printf("Thread: old state=%s, old type=%s\n",
           oldstate == PTHREAD_CANCEL_ENABLE ? "ENABLE" : "DISABLE",
           oldtype  == PTHREAD_CANCEL_DEFERRED ? "DEFERRED" : "ASYNC");

    printf("Thread: sleeping (deferred cancel — fires only at cancel point)\n");
    sleep(10);   /* cancellation point — will catch pending cancel */

    printf("Thread: woke up 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 status = %s\n",
           res == PTHREAD_CANCELED ? "CANCELED" : "NORMAL");
    return 0;
}

Interview Questions — Section 32.2

Q1. What is the difference between cancellation state and cancellation type?
Answer: State (ENABLE/DISABLE) controls whether the thread accepts cancellation at all. Type (DEFERRED/ASYNCHRONOUS) controls when it acts on an accepted cancellation request. State is the outer gate; type is the inner gate.
Q2. What are the default cancellation state and type for a new thread?
Answer: Default state is PTHREAD_CANCEL_ENABLE — the thread accepts cancellations. Default type is PTHREAD_CANCEL_DEFERRED — cancellation is delayed until the next cancellation point.
Q3. If a thread disables cancellation and a cancel request arrives, what happens?
Answer: The request stays pending — it is not lost and not acted upon. As soon as the thread calls pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, ...) again, the pending request is re-evaluated, and if the type is DEFERRED, it fires at the next cancellation point.
Q4. Why should you always pass a non-NULL oldstate to pthread_setcancelstate()?
Answer: Although Linux allows NULL, SUSv3 does not specify this behavior, making it non-portable. On other UNIX implementations, passing NULL may cause undefined behavior or a crash. For portable code, always declare int oldstate; and pass &oldstate.
Q5. How does calling fork() affect a child’s cancellation state/type?
Answer: The child process inherits the calling thread’s current state and type. If the parent thread had PTHREAD_CANCEL_DISABLE set when it called fork(), the child’s (main thread) will also start with cancellation disabled.
Q6. How does exec() affect cancellation settings?
Answer: After exec(), the new program’s main thread gets the defaults: state = PTHREAD_CANCEL_ENABLE, type = PTHREAD_CANCEL_DEFERRED. The old program’s custom settings are irrelevant to the new program.
Q7. Why is PTHREAD_CANCEL_ASYNCHRONOUS considered dangerous?
Answer: With asynchronous cancellation, the thread can be stopped at any machine instruction — even in the middle of a malloc() call, inside a mutex lock, or during a system call. Cleanup handlers can’t know what state resources are in, making proper cleanup nearly impossible. It should only be used for compute-bound loops with no resource allocation.

© EmbeddedPathashala | TLPI Chapter 32 | Section 32.2

Leave a Reply

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