Home | Projects | Notes > Multi-Threading (POSIX Threads) > Thread Cancellation - Asynchronous Cancellation
The cancellation request (CR) sent by the thread T1 is queued by the OS before directly canceling the target thread T2. This is because the OS may be busy carrying out other tasks such as context switching between processes when it receives the cancellation request from a thread. Therefore, the OS may or may NOT terminate the target thread T2 instantaneously. (This is where the name "asynchronous" comes from.)
At
With the asynchronous cancellation, a thread can be canceled at any arbitrary point in its execution flow, and this can lead to the following problems:
Resource leak
Invariants
Deadlocks
Abrupt cancellation of a thread may lead to the problem of resource leak.
Examples:
Terminating without closing the open file descriptor/sockets
Terminating without freeing the allocated memory.
A thread must be given one last chance to clean up the resources before it is terminated.
Solution:
This can be handled by the thread cleanup handlers. (See the Solution to Resource Leak Problem section below.)
Abrupt cancellation of a thread may lead to the problem of invarnats which may in turn lead to the data structure corruption, memory leak, wrong computation, etc. (Invariants means a data structure in inconsistent state.)
Examples:
Canceling the thread while inserting/removing an element to/from a doubly-linked list.
Canceling the thread while inserting/removing a node to/from a balanced tree (e.g., red-black/AVL trees).
Canceling the thread which is in the process of executing system calls (e.g., malloc()
). Incomplete termination of a system cal may lead to the kernel corruption or variants in the kernel space.
A thread must not get canceled while it is updating the data structures or processing the system calls. In other words, a thread must cancel in a controlled manner at a certain point in its execution flow, where it safety is guaranteed.
Solution:
This can be handled in deferred cancellation by the cancellation points. (See Thread Cancellation - Deferred Cancellation)
What would happen if a thread is forced to terminate when it has locked a mutex and never had a chance to release it? The mutex will be left in locked state forever by the no-longer existing thread and no other threads in the process will be able to obtain the permission to enter the critical section that has been permanently locked.
When a thread is canceled, it must not have any mutex held in the locked state.
POSIX standards provide the concept of Thread Cleanup Handlers. Thread cleanup handlers are functions that are invoked just before the thread gets canceled.
xxxxxxxxxx
11void (*cleanup_handler)(void*); /* function ptr to the cleanup handler function */
Only after the thread cleanup handler function returns, the thread can be canceled immediately.
Thread cleanup handlers are specified in the form of a stack; stack of functions. Upon cancellation, the cleanup handlers will be invoked from top of the stack to the bottom of the stack (i.e., in LIFO manner).
xxxxxxxxxx
121/* thread function */
2thread_fn()
3{
4 /* register the cleanup handlers by pushing them into the stack */
5 pthread_cleanup_push(f1, arg); /* arg is an argument to the passed function f1 */
6 pthread_cleanup_push(f2, arg);
7 ...
8 /* pop the cleanup handlers out of the stack if thread_fn does not cancel and execute to completion
9 sucessfully */
10 pthread_cleanup_pop(1); /* arg (non-0: pop cleanup function and invoke it, 0: just pop) */
11 pthread_cleanup_pop(1);
12}
pthread_cleanup_push();
API is replaced by a {
and some intermediate code at the compile-time.
pthread_cleanup_pop();
API is replaced by some intermediate code followed by a }
.
Which means that these APIs are not functions but macros. Therefore, after compilation, the code will look like:
xxxxxxxxxx
141/* thread function */
2thread_fn()
3{
4 {
5 /* code inserted by the compiler - 1 */
6 {
7 /* code inserted by the compiler - 2 */
8 ...
9 ...
10 /* code inserted by the compiler - 2' */
11 }
12 /* code inserted by the compiler - 1' */
13 }
14}
Note that failing to match pthread_cleanup_push()
and pthread_cleanup_pop()
will result in unbalanced parentheses which will eventually lead to compilation failure!
Cleanup functions are also invoked when a thread is terminated using pthread_exit()
.
Cleanup functions are NOT invoked when a thread is terminated using return
statement.
The following is a program to demonstrate asynchronous cancellation of threads with resource leak handling.
xxxxxxxxxx
1261/*
2 * File Name : asynchronous_cancellation.c
3 * Description : C program to demonstrate asynchronous cancellation with resource leak handling
4 * Author : Modified by Kyungjae Lee
5 * (Original: Abhishek Sagar - Juniper Networks)
6 * Date Created : 01/05/2023
7 */
8
9
10
11
12
13
14
15
16
17pthread_t slaves[N_SLAVES]; /* array of thread handles */
18
19/* thread cleanup handler to prevent resource (memory) leak */
20void memory_cleanup_handler(void *arg)
21{
22 printf("%s invoked...\n", __FUNCTION__);
23 free(arg);
24}
25
26/* thread cleanup handler to prevent resource (file) leak */
27void file_cleanup_handler(void *arg)
28{
29 printf("%s invoked...\n", __FUNCTION__);
30 close((FILE*)arg);
31}
32
33/* thread function */
34void* write_into_file(void *arg)
35{
36 char file_name[64];
37 char string_to_write[64];
38 int len;
39 int count = 0;
40
41 /* set the thread eligible for cancellation */
42 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE /* PTHREAD_CANCEL_DISABLE */, 0);
43 /* set cancellation mode to ASYNCHRONOUS */
44 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);
45 /* Notice that this two APIs do not take the thread handle as an argument. This
46 * implies that the thread that invokes these APIs can only affect itself. In
47 * other words, no thread can set the 'cancelstate' or the 'canceltype' of another
48 * thread. */
49
50 int *thread_id = (int*)arg;
51
52 /* register the cleanup handler for the dynamically allocated variable 'arg' */
53 pthread_cleanup_push(memory_cleanup_handler, arg);
54
55 sprintf(file_name, "thread_%d.txt", *thread_id);
56
57 FILE *fptr = fopen(file_name, "w");
58
59 if(!fptr)
60 {
61 printf("Error : Could not open log file %s, errno = %d\n", file_name, errno);
62 pthread_exit(0); /* Make sure to terminate the program using this API so the cleanup handlers
63 can be invoked at the termination. Terminating using 'return' statement
64 will not invoke the cleanup handlers */
65 /* return 0; */
66 }
67
68 /* register the cleanup handler for the opened file pointed to by 'fptr' */
69 pthread_cleanup_push(file_cleanup_handler, fptr);
70
71 int a = 0;
72
73 while(a < 20)
74 {
75 len = sprintf(string_to_write, "%d : I am thread %d\n", count++, *thread_id);
76 fwrite(string_to_write, sizeof(char), len, fptr);
77 fflush(fptr);
78 sleep(1);
79 a++;
80 }
81
82 /* pop the cleanup handlers out of stack */
83 pthread_cleanup_pop(1); /* arg (non-0: pop cleanup function and invoke it, 0: just pop) */
84 pthread_cleanup_pop(1);
85
86 return 0;
87}
88
89int main(int argc, char **argv)
90{
91 int i;
92 int *thread_id = 0;
93
94 for (i = 0; i < N_SLAVES; i++){
95 thread_id = calloc(1, sizeof(*thread_id));
96 *thread_id = i;
97 pthread_create(&slaves[i], 0, write_into_file, thread_id);
98 }
99
100 /* main menu */
101 int choice;
102 int thread_num;
103
104 while(1) {
105
106 printf("1. Cancel the thread\n");
107 scanf("%d", &choice);
108 printf("Enter slave thread id [0-%d] :", N_SLAVES -1);
109 scanf("%d", &thread_num);
110 if(thread_num < 0 || thread_num >= N_SLAVES) {
111 printf("Invalid Thread id\n");
112 exit(0);
113 }
114
115 printf("\n");
116
117 switch(choice) {
118 case 1:
119 pthread_cancel(slaves[thread_num]);
120 break;
121 default:
122 continue;
123 }
124 }
125 return 0;
126}
By running tail -f thread_0.txt
in a separate window, you'll be able to see the thread 0 writing to thread_0.txt
file every second in real-time.
xxxxxxxxxx
71$ tail -f thread_0.txt
20 : I am thread 0
31 : I am thread 0
42 : I am thread 0
53 : I am thread 0
64 : I am thread 0
7...
Sagar, A. (2022). Part A - Multithreading & Thread Synchronization - Pthreads [Video file]. Retrieved from https://www.udemy.com/course/multithreading_parta/