Threads Module User's Guide : PART II Concurrency Packages : Chapter 3 The Threading Package : The Runnable Object Classes : Canceling a Runnable
Canceling a Runnable
Canceling a runnable is implemented by the following functions:
RWWaitStatus RWRunnable::requestCancellation(void)
RWWaitStatus RWRunnable::requestCancellation(unsigned long milliseconds)
void RWRunnableSelf::serviceCancellation(void)
void rwServiceCancellation();
Cancellation is used to request a thread to exit a runnable. The cancellation operations are similar in function to the interrupt operations. Like the interrupt, cancellation is a synchronous process; a thread can only be canceled if it chooses to be.
Canceling a Runnable from Another Thread
To cancel a runnable from another thread, you need to call the RWRunnable function, requestCancellation(). This function sets a flag maintained within the runnable instance, and waits for the runnable to complete cancellation.
Completing a Cancellation Request
To complete the cancellation request, the thread executing inside the runnable must call the serviceCancellation() function provided by the RWRunnableSelf handle class. If there is an outstanding cancellation request, this function throws an RWCancellation exception object; otherwise the function simply returns to the caller. A thrown cancellation object notifies the current runnable that cancellation has started, causing the runnable’s execution state to change to RW_THR_CANCELING[10].
At this point, the C++ exception processing begins unwinding the call-stack until an exception handler is found for the RWCancellation object. If the code that was executing inside the runnable does nothing to stop the unwind, the cancellation exception propagates all the way back out of the runnable’s run() member and into exception-handling code inside the runnable class. If this happens, the cancellation process concludes, and the requesting thread is notified that cancellation has completed successfully.
When a runnable is canceled, its completion state is changed to RW_THR_CANCELED{4}, its execution state is reset back to the RW_THR_INITIAL[11], and an RWTHROperationCanceled exception is stored for retrieval by raise().
Aborting a Cancellation
The cancellation process can be aborted by catching the RWCancellation exception object before it can propagate back into the runnable. When the cancellation object is destroyed, it automatically notifies the current runnable that the cancellation process has been aborted. The abort notification causes the runnable to enter the RW_THR_ABORTING state and the RW_THR_RUNNING state. The RW_THR_ABORTING state is used to signal any waiting threads that the cancellation process was aborted.
Types of requestCancellation Functions
Threads Module has two types of requestCancellation() functions:
One waits indefinitely for the runnable to either cancel or exit.
One accepts a time-limit for the wait.
The requestCancellation() function returns one of the following:
RW_THR_COMPLETED if the runnable was successfully canceled, exited, or was not active in the first place.
RW_THR_ABORTED if the cancellation process was started, but the RWCancellation exception object was not allowed to propagate back to the runnable class.
RW_THR_TIMEOUT if the request was time-limited and the runnable failed to complete cancellation within the allotted time.
Once a canceled runnable begins the cancellation process, it continues that process even if the original request for cancellation times out.
Undoing a Cancellation Request
A cancellation request cannot be undone; once a runnable is marked for cancellation it stays marked until it exits. An aborted cancellation is restarted the next time serviceCancellation() is called.
Using Synchronization Classes to Service Cancellation Requests
The Threads Module synchronization classes can automatically service cancellation requests before performing any operation that may result in blocking the calling thread.
To use this capability, a synchronization object must have its cancellation processing enabled. You can do this when the synchronization object is constructed by passing the appropriate RWCancellationState value to the constructor. The default cancellation state of a synchronization object is RW_CANCELLATION_DISABLED. You can override that default, however, by specifying RW_CANCELLATION_ENABLED when you construct the synchronization instance, as shown in Example 10.
Example 10 – Constructing synchronization objects to automatically service cancellation requests
RWMutexLock mutex(RW_CANCELLATION_ENABLED);
 
try {
mutex.acquire(); // May throw cancellation!
}
catch(RWCancellation&) {
// Do something about it!
}
Except when using nested lockguards, you can also enable synchronization cancellation processing by using the setCancellation(RWCancellationState) member function provided by RWSynchObject class, the base class for all synchronization mechanisms in Threads Module. Example 11 shows this approach.
Example 11 – Setting synchronization objects to automatically service cancellation requests
RWMutexLock mutex; // Defaults to disabled!
 
mutex.setCancellation(RW_CANCELLATION_ENABLED);
try {
mutex.acquire(); // May throw cancellation!
}
catch(RWCancellation&) {
// Do something about it!
}
Figure 13 shows the interaction and state changes associated with the cancellation process.
Figure 13 – Cancel operations — interaction and timing
Designing Your Code
If you want to support cancellation in your application, you need to design your code to allow for the propagation of the RWCancellation object:
Exception specifications for functions, if used, must include the RWCancellation or RWTHRxmsg exceptions.
Your catch(...) blocks must rethrow the original exception.
Any block that explicitly catches an RWCancellation or RWTHRxmsg exception must rethrow the original exception, or rethrow the instance that was caught.
Throwing a new exception instance results in the destruction of the original RWCancellation instance, effectively aborting the cancellation process.
These coding guidelines are illustrated in the code fragments in Example 12.
Example 12 – Supporting cancellation in an application
void function1() throw(RWCancellation)
{
RWRunnableSelf self = rwRunnable();
try {
self.serviceCancellation();
}
catch(RWCancellation&) {
// Cancellation occurred!
throw; // Rethrow the original instance
}
}
 
void function2() throw(RWTHRxmsg)
{
RWRunnableSelf self = rwRunnable();
try {
self.serviceCancellation();
}
catch(RWTHRxmsg& msg) { // Catch by reference!
// An RWTHRxmsg exception occurred!
throw msg; // Rethrow the instance caught
}
}
 
void function3() // No spec!
{
RWRunnableSelf self = rwRunnable();
try {
self.serviceCancellation();
}
catch(...) { // Catches anything
// Some exception occurred!
throw; // Rethrow the original instance
}
}
Each of these code fragments uses the global function rwRunnable() to retrieve a handle to the runnable that is the current source or active runnable of the calling thread. This handle is then used to call serviceCancellation(). This pair of statements may be replaced with the global function, rwServiceCancellation(), which combines these two operations into a single function.
Servicing cancellations can be very complicated when you are accessing thread-shared data that is protected by locks such as mutexes. If a runnable is canceled in the middle of acquiring and releasing locks on shared data, then the locks must also be released to prevent deadlock and the data must be set to a correct and consistent state.
For this reason, when using nested lockguards, avoid using the function RWMutexLock::setCancellation(RWCancellationState). Instead, before each attempt to acquire the mutex, use ::rwServiceCancellation() to service cancellation requests. To do otherwise may cause your attempts to acquire the mutex to fail, because the mutex is already canceled. This may cause your program to terminate, due to assertion errors. Example 13 demonstrates this concept.
Example 13 – Serving cancellation requests with nested lockguards
#include <rw/thread/RWCancellation.h>
#include <rw/thread/RWRunnableSelf.h>
#include <rw/thread/RWThread.h>
#include <rw/thread/RWThreadFunction.h>
#include <rw/sync/RWMutexLock.h>
#include <iostream>
 
using namespace std;
void func1(void)
{
RWMutexLock _mutex;
 
::rwServiceCancellation(); // Don’t use
// _mutex.setCancellation();
// Instead, service cancellations
// before attempting to acquire the
// mutex.
RWLockGuard<RWMutexLock> lock(_mutex); // Acquire the mutex.
 
rwSleep(1);
 
{
RWUnlockGuard<RWMutexLock> unlock(_mutex);// Release the mutex.
 
cout << "going to sleep..." << endl;
rwSleep(1500);
cout << "waking up..." << endl;
::rwServiceCancellation(); // Service cancellations before you
// attempt to acquire the mutex.
 
} // Acquire the mutex.
cout << "Everything was successful" << endl;
} // Release the mutex.
void main(void)
{
RWThread thr1 = RWThreadFunction::make(func1);
thr1.start();
 
rwSleep(100);
 
cout << "requesting cancellation..." << endl;
thr1.requestCancellation();
cout << "cancellation request returned." << endl;
}