Cancellation is a serious problem in multi-threading environment. In one word, a well built program does not use asyncrhonous cancellation, that is, does not interrupt another thread about which almost nothing is known. Doing so may result in disastrous outcomes, as interrupting the target thread while it is holding a mutex, blocking forever other threads. Or problems may more subtle: interrupting an application thread while holding heap memory will result in memory leaks that will probably be unnoticeable until in a later production phase, where an heavy use will show up memory consumption.
How to solve this problem? The best solution is probably never to call the stop() method of the Thread class, unless you are going to quit your application. If you want to tell another thread that is time to quit, you should set a flag that will be read from target thread at one point; this is called "kind cancellation" To implement it, you must make sure that you never block your threads forever; you must use timeouts, callbacks and anything to ensure that sooner or later your thread will be waken up, so if another threads wants to cancel it, the target thread will have a chance to read the cancellation request and then quit.
Wefts++ has support for kind cancellation and for a quasi-kind cancellation called "deferred". To activate the support for kind cancellation, start a thread with cancellation disabled (see Thread::start). Then, when you issue a stop() on that thread, a flag will be risen into the Thread class; this flags will prevent many automatic things to happen (i.e. will prevent threads from joining the stopped one), and will make so that the Thread::testCancel() method will quit your thread.
Deferred cancellation is similar, but it also interrupts some system blocking calls, as Wefts::Sleep() function and condition waits (Cond::wait()). Some systems (unix/pthread) provide also support for I/O call deferred cancellation, so that a stop() could interrupt a blocking read() call. Some unix flavor could use I/O calls as true cancellation points (i.e. as if a testCancel() were issued inside the I/O call), others may just interrupt the I/O calls so that your program is resumed with an error (usually unix errno==EINTR). The best way to work around this difference is to put a testCancel() just after I/O blocking calls, so that when the call is interrupted because of a cancellation request, the thread is immediately canceled.
Cleanup is a concept tightly related with automatic cancellation and thread termination. A cleanup sequence is a set of actions that are done at thread termination, both if this termination is "natural" that is, if the thread just finished running, or if a cancellation request has been fulfilled via the Thread::testCancel() method or via OS deferred cancellation mechanism.
Wefts provides its own cleanup system in two flavors: thread cleanup stack list and condition wait cleanup routine. Moreover, Thread class provide an overloadable Thread::cleanup() method that is called at thread termination. Finally, after all the cleanup sequence is done, Wefts automatically takes care of:
Cleanup actions are organized in a stack (Last-in-first-out structure) so that it is possible to have "inner" cleanup managers, and can be added with the method Thread::pushCleanupHandler( CleanupHandler *, int ), and removed with Thread::popCleanupHandler( bool ). A typical scheme may be:
class MyThread: public Thread, public CleanupHandler { ... // code within a thread sub class method void *run() { ... pushCleanupHandler( this, 1 ); ... do some mess and allocate member m_heap1 testCancel(); // or other things that can cause thread to terminate pushCleanupHandler( this, 2 ); ... do some mess and allocate member m_heap2 if (something) return 0; // causing thread to terminate, and cleanup to go! ... free m_heap2 popCleanupHandler(); testCancel(); ... free m_heap1 popCleanupHandler(); ... } // overload CleanupHandler virtual void handleCleanup( int position ) { if ( position == 2 ) { ... free m_heap2 and m_heap1 } else if ( position == 1 ) { ... free m_heap1 only! } } ...
Remember that if cancellation is enabled, you can be canceled only at cancellation points, and if it is disabled, you can't be canceled unless you issue a testCancel(), so you can push the handler and then allocate the memory, or the reverse; the important thing is that there must not be any cancellation point in the code between the cleanup handler push and the things that are done ( that must be undone in the handler).
Using the method Thread::popCleanup() and passing true as a parameter, the handler will be first executed and then removed, so if you have a long code in the cleanup sequence you can reuse it in your functions without having to duplicate it.
Condition waits have a special cleanup sequence. Conditions are wefts object that encapsulates both an object that can be signaled and a wefts Mutex; when a cancellation request is issued while a therad is waiting on a condition, the Mutex object is locked before the cleanup action is taken. So, if your program uses raw conditions or their derivate classes, you must either:
To implement the cleanup sequence for a condition, you have two options: if you are in control of the thread that is going to wait the condition, you can just use Thread::pushCleanupHandler() and Thread::popCleanupHandler(), knowing that you will be called with Condition variable locked, and taking good care to release it when your are done. If you can't control the Thread object that is waiting, you can set a Condition object specific wait using Condition::onStop() method. This method works as pushCleanupHandler, but it is more efficient, it does not require to have a reference to the Thread object and it sets only one hander at a time. So, you can create a condition that may be stopped without worries about holding the mutex in exit in this way:
class MyCondition: public FastCondition, public CleanupHandler ... virtual bool wait() { onStop( this, 1 ); bool ret = FastCondition::wait(); onStop(); // reset stop cleanup routine } handleCleanup( int value ) { if (value == 1 ) unlock(); } };