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:
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;
}