Thread Cancellation in Linux

On linux, cancellation is implementated using signals. On LinuxThreads, the second real-time signal is used, if real-time signals are available, otherwise SIGUSR2 is used.
Thread cancellation lets a thread terminate the execution of any other thread in the process. This is done by pthread_cancel().

Syntax :  int pthread_cancel(pthread_t thread)Return Value : On success, it returns 0. On failure it returns Nonzero error number (ESRCH: No thread with the ID thread could be found).Description : The pthread_cancel() function sends a cancellation request to the thread. Whether and when the target thread reacts to the cancellation request depends on two attributes that are under the control of that thread: its cancelability state and type.(We’ll discuss about two attributes later in this article).

Below are the possible ways to send cancellation request :
- Parent can send cancellation request to it’s child thread (pthread_cancel_1.c)
- One child thread can send cancellation request to another child thread (pthread_cancel_2.c)
- Child thread can send cancellation request to parent thread (pthread_cancel_3.c)
Thread must get cancellation request within a same process, means that thread that belong to one process can send a thread cancellation request to another thread that belong to same process.

When a cancellation requested is acted on, the following steps occur for thread (in this order):
1. Cancellation clean-up handlers are popped (in the reverse of the
order in which they were pushed) and called.
2. Thread-specific data destructors are called, in an unspecified order.
3. The thread is terminated.

Clean-up Handler

Clean-up handler is afunctions which execute while thread get cancellation request. If no clean up handlers setted for the particular thread, than execution goes to next step mentioned above.

How to Set Clean up Handler for thread?
To set clean up handler for thread, pthread library gives function pthread_cleanup_push() & pthread_cleanup_pop()

Syntax : 
void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);
Description : The pthread_cleanup_push() pushes routine onto the top of stack of clean-up handlers. When routine is later invoked, it will be given arg as its argument. The pthread_cleanup_pop() removes the routine at the top of the stack of clean-up handlers when execute is zero, and optionally executes it if execute is nonzero.

Below example demonstrate how to set cleanup routine and its execution while thread get cancellation request. On request of pthread_cancel() clean up routine invoked. As you can see first cleanup_1_routine pushed and then cleanup_2_routine. While cancellation happened, cleanup_2_routine poped first and then cleanup_1_routine. This is because all the cleanups are called in the reverse of the order in which they were pushed while cancellation request served.
Let’s discuss few scenario wih below code.
Comment pthread_cancel() and execute the code. You’ll see both cleanup handler not called. If you want to call cleanup handler even without cancellation request, you can do by passing nonzero argument in pthread_cleanup_pop(). If you want any particular cleanup routine to call without cancellation, set nonzero value in its appropriate pthread_cleanup_pop()

Note that, On Linux, the pthread_cleanup_push() and pthread_cleanup_pop() functions are implemented as macros that expand to text containing ‘{‘ and ‘}’ respectively, we have to write both the function equally. Also take care of variables declared within the scope of paired calls to these functions.

Note : If you use pthread_exit() in cleanup handler, it will go to infinite loop as pthread_exit() again call to cleanup handler.

Data Destructors

Data destructor is also a function like cleanup handler, but having different propery and different application in thread. Data destructor is connect with user defined data type and pre defined data types. For this, one key is associated with one destructure function. This key is further associate with any object that needs to do operation while thread get cancellation request. This operation can be anythig user wanted to do with object. In real time application, this destructors is used to free the memory which occupied by object in calling thread.

How to set Data destructor in thread?
To set data destructor for thread, pthread library gives function such as,
pthread_key_create() : To create thread-specific data key
pthread_key_delete() : To delete thread-specific data key
pthread_setspecific() : To bound object with thread-specific data key
pthread_getspecific() : To get bounded object with thread-specific data key

Syntax : 
int pthread_key_create(pthread_key_t *key,void(*destructor)(void*));
int pthread_key_delete(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void * value);
void * pthread_getspecific(pthread_key_t key);
Return Value :
pthread_key_create(), pthread_key_delete(), and pthread_setspecific() return 0 on success and non-zero error code on failure.(EAGAIN : PTHREAD_KEYS_MAX keys are already allocated, EINVAL : Key is not valid)
pthread_getspecific returns the value associated with key on success, and NULL on error.
Description :
- pthread_key_create allocates a new thread-specified key. The key is stored in the location pointed to by key. There is a limit of PTHREAD_KEYS_MAX on number of key allocated at a given time. The destructor argument, specifies a destructor function associated with the key. When a thread terminates via pthread_exit() or by cancellation, destructor is called with arguments value associated with the key by using pthread_setspecific(). The order in which destructor functions are called at thread termination time is unspecified when one thread have setted multiple destructor.
- pthread_key_delete() delete thread-specified data key previously returned by pthread_key_create(). The thread-specified data value associated with key need not be NULL at the time pthread_key_delete() is called. The pthread_key_delete() shall be callable from within destructor functions. No destructor function shall be invoked by pthread_key_delete(). Any destructor function that may have been associated with key shall no longer be called upon thread exit.- pthread_setspecific() also changes the value associated with key in the calling thread, storing the given pointer instead. This value is passed to destructor argument when destructor called.- pthread_getspecific() returns the value currently associated with key in the calling thread.

Below example demonstrate the destructor call on thread cancellation. It is not necessary to create key from main thread. You can create key anywhere when you need. But if you want to call destructor on thread cancellation, you must create key, associate destructor and assign thread-specified value before cancellation request received by thread.
Comment pthread_cancel() and run the code, you’ll notice even though cancellation request not occured, destructor called after pthread_exit(). There is only one way you can stop it by delete key using pthread_key_delete().

Note : Unlike cleanup handler, when thread having multiple destructor get cancellation request, there is no gurantee which destructor execute first. To prevent this, make sure that each destructor is independent to one another.

Note : If you use pthread_exit() in data destructor, it will go to infinite loop as pthread_exit() again call to data destructor.

Above all examples are describe the thread exit method on cancellation request. However, there is some application that wants their executation not disturb due to cancellation request. Pthread library gives additional functionality to control thread cancellation request in better way that it wont affect the thread instruction that are currently executing and served the cancellation request later on.
Lets discuss two attributes that are under the control of that thread: its cancellability state and type.

Cancellability state

Thread cancellability decide wheather thread affect the execution of calling thread or not. To set cancellability state pthred library provide pthread_setcancelstate().

Syntax : int pthread_setcancelstate(int state, int * oldstate);Return : On success, these functions return 0 and return nonzero on failure (EINVAL : Invalid value for state).Description : It sets the cancelability state of the calling thread to the value given in state. The previous cancelability state of the thread is returned in the buffer pointed to by oldstate. oldstate can be null if user do not want to know previous state. The state argument must have one of following values:- PTHREAD_CANCEL_ENABLE *: This is the default cancelability state in all new threads, including the initial thread. The thread's cancelability type determines when a cancelable thread will respond to a cancellation request.
- PTHREAD_CANCEL_DISABLE : If a cancellation request is received, it is blocked until cancelability is enabled.

Below example describe the behaviour of cancellation request to thread execution whose cancellability state is disable.

If you notice, in output of the above program thread_cleanup_routine() called after threada completion. This is because the cancel request generated from called thread is in pending state of calling thread. This pending request serve based on the cancellability type after we set the state as enable. If you comment line number 58 and run the code again, you’ll not see execution of cleanup_routine().

Cancellability type

Thread cancellability type decide wheather thread can server cancellation request immediatly or later. To set cancellability type pthred library provide pthread_setcanceltype().

Syntax : int pthread_setcanceltype(int type, int *oldtype);Return : On success, these functions return 0 and return nonzero on failure (EINVAL : Invalid value for type).Description : It sets the cancelability type of the calling thread to the value given in type. The previous cancelability type of the thread is returned in the buffer pointed to by oldtype. Valid settings for type include:- PTHREAD_CANCEL_ASYNCHRONOUS : Cancellation requests are acted on immediately. This is the default cancelability type in all new threads, including the initial thread.
- PTHREAD_CANCEL_DEFERRED *: Cancellation requests are acted on as soon as the thread reaches a cancellation point(see pthreads(7)).
Note that both the type work only when cancellability state is enable.

Below example demonstrate use of pthread_setcanceltype().
When type argument in pthread_setcanceltype() pass as ASYNCHRONOUS, child thread immediately stops its execution and call cleanup_routine.
When type argument in pthread_setcanceltype() pass as DEFERRED, child thread complete its instruction untill it found cancellation point. Once it found cancellation point, it execute cancellation point request and jump to cleanup_routine.
Note that pthread_testcancel() is library provided function. It creates a cancellation point within the calling thread, so that a thread that is executing code that contains no cancellation points will respond to a cancellation request.

While type is ASYNCHRONOUS, you’ll get count value any number between 1 to 100 as cancellation request cancel the thread execution.
While type is DEFERRED, you’ll get count value 500 everytime.
Comment both cancellation point from above code, set type as DEFERRED and you’ll see cancellation request wil not get respond.

PTHREAD_CANCELED Status

When child thread is cancelled by parent thread/other thread, child thread send cancellation status to it’s parent thread while joining. This is exaplained in below example.

When child thread return to parent, it send status of cancellation in thread_ret_status. By checking its value, we can derive thread is cancelled or not. Comment the pthread_cancel() function and run the code. You’ll see “Thread wasn’t canceled” printed in main thread. If child thread is detached(not joinable), parent thread will not get cancellation status of child.

Below is the flowchart of thread cancellation request, which is sum up of everything mentioned above.

Image for post
Image for post

Written by

Embedded Software Engineer at Matrix Comsec

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store