Main Content

Asynchronously cancellable thread

Calling thread might be canceled in an unsafe state

Since R2020a

Description

This defect occurs when you use pthread_setcanceltype with argument PTHREAD_CANCEL_ASYNCHRONOUS to set the cancellability type of a calling thread to asynchronous (or immediate). An asynchronously cancellable thread can be canceled at any time, usually immediately upon receiving a cancellation request.

Risk

The calling thread might be canceled in an unsafe state that could result in a resources leak, a deadlock, a data race, data corruption, or unpredictable behavior.

Fix

Remove the call to pthread_setcanceltype with argument PTHREAD_CANCEL_ASYNCHRONOUS to use the default cancellability type PTHREAD_CANCEL_DEFERRED instead. With the default cancellability type, the thread defers cancellation requests until it calls a function that is a cancellation point.

Examples

expand all

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

static int fatal_error(void)
{
    exit(1);
}


volatile int a = 5;
volatile int b = 10;

pthread_mutex_t global_lock = PTHREAD_MUTEX_INITIALIZER;

void* swap_values_thread(void* dummy)
{
    int i;
    int c;
    int result;
    if ((result =
             pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &i)) != 0) {
        /* handle error */
        fatal_error();
    }
    while (1) {
        if ((result = pthread_mutex_lock(&global_lock)) != 0) {
            /* handle error */
            fatal_error();
        }
        c = b;
        b = a;
        a = c;
        if ((result = pthread_mutex_unlock(&global_lock)) != 0) {
            /* handle error */
            fatal_error();
        }
    }
    return NULL;
}

int main(void)
{
    int result;
    pthread_t worker;

    if ((result = pthread_create(&worker, NULL, swap_values_thread, NULL)) != 0) {
        /* handle error */
        fatal_error();
    }

    /* Additional code */

    if ((result = pthread_cancel(worker)) != 0) {
        /* handle error */
        fatal_error();
    }


    if ((result = pthread_join(worker, 0)) != 0) {
        /* handle error */
        fatal_error();
    }

    if ((result = pthread_mutex_lock(&global_lock)) != 0) {
        /* handle error */
        fatal_error();
    }
    printf("a: %i | b: %i", a, b);
    if ((result = pthread_mutex_unlock(&global_lock)) != 0) {
        /* handle error */
        fatal_error();
    }

    return 0;
}

In this example, the cancellability type of the worker thread is set to asynchronous. The mutex global_lock helps ensure that the worker and main threads do not access variables a and b at the same time. However, the worker thread might be canceled while holding global_lock, and the main thread will never acquire global_lock, which results in a deadlock.

Correction — Use the Default Cancellability Type

One possible correction is to remove the call to pthread_setcanceltype. By default, the cancellability type of a new thread is set to PTHREAD_CANCEL_DEFERRED. The worker thread defers cancellation requests until it calls a function that is a cancellation point.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

static int fatal_error(void)
{
    exit(1);
}


volatile int a = 5;
volatile int b = 10;

pthread_mutex_t global_lock = PTHREAD_MUTEX_INITIALIZER;

void* swap_values_thread(void* dummy)
{
    int i;
    int c;
    int result;
    while (1) {
        if ((result = pthread_mutex_lock(&global_lock)) != 0) {
            /* handle error */
            fatal_error();
        }
        c = b;
        b = a;
        a = c;
        if ((result = pthread_mutex_unlock(&global_lock)) != 0) {
            /* handle error */
            fatal_error();
        }
    }
    return NULL;
}

int main(void)
{
    int result;
    pthread_t worker;

    if ((result = pthread_create(&worker, NULL, swap_values_thread, NULL)) != 0) {
        /* handle error */
        fatal_error();
    }

    /* Additional code */

    if ((result = pthread_cancel(worker)) != 0) {
        /* handle error */
        fatal_error();
    }


    if ((result = pthread_join(worker, 0)) != 0) {
        /* handle error */
        fatal_error();
    }

    if ((result = pthread_mutex_lock(&global_lock)) != 0) {
        /* handle error */
        fatal_error();
    }
    printf("a: %i | b: %i", a, b);
    if ((result = pthread_mutex_unlock(&global_lock)) != 0) {
        /* handle error */
        fatal_error();
    }

    return 0;
}

Result Information

Group: Concurrency
Language: C | C++
Default: Off
Command-Line Syntax: ASYNCHRONOUSLY_CANCELLABLE_THREAD
Impact: Medium

Version History

Introduced in R2020a