Cancellation Types

In POSIX, there are two pthread cancellation types: PTHREAD_CANCEL_DEFERRED and PTHREAD_CANCEL_ASYNCHRONOUS. PTHREAD_CANCEL_DEFERRED is the default and the normal kind of pthread cancellation that should be used. PTHREAD_CANCEL_ASYNCHRONOUS is brutal; it simply kills the pthread immediately without regard for what it is doing.

PTHREAD_CANCEL_DEFERRED works differently; nothing happens immediately. Instead, the cancellation will be deferred until the pthread is at (or a better preposition would be within) a cancellation point. You cannot have the PTHREAD_CANCEL_DEFERRED cancellation type without cancellation points and you will not have cancellation points unless you enable them with CONFIG_CANCELLATION_POINTS=y.

Cancellation Points

Cancellation points add special instrumentation to a specific set of interface functions. Those interface functions are listed here: OpenGroup.org.

Enter and Leave Cancelation Point

In NuttX, each of these functions CONFIG_CANCELLATION_POINTS=y will enable a special call to enter_cancellation_point() at the beginning of the function and a matching call to leave_cancellation_point() before the interface function returns. These may be nested: One cancellation point function may call another which may call another, etc. The select() function, for example, calls poll() which calls sem_wait() so the nesting will be three deep.

These two cancellation point hooks behave in a complementary way. enter_cancellation_point() increments the nesting level and leave_cancellation_point() decrements the nesting level. No action is taken unless the nesting level is zero then, in that case, if there is a pending cancellation then the thread exists normally by simply calling pthread_exit().

pthread_cancel()

When pthread_cancel() is called, it will check if the deferred cancellation mode is enabled for the target pthread. If so it will (1) mark the cancellation as pending, and (2) if the thread is waiting on a semaphore or message queue (and is in a cancelable state), it will wake it just in the same was as if the thread received a single (except with ECANCELED instead of EINTR).

If, on the other hand, the asynchronous mode is enabled (and the thread is in a cancelable state), pthread_cancel() will, instead, kill the thread immediately.

NOTE: For pthreads, the cancelable state is controlled by the interface pthread_setcancelstate(). This allows the pthread to disable or re-enable cancelability. If the thread is not cancelable when pthread_cancel() is called, it will never do more than just mark the cancellation as pending. When the cancelable state is re-enabled, then any pending cancellation will take effect

select() Walk-Through

Let's walk through the case of select() to see how would work:

So this pthread will spend almost all of its time waiting in sem_wait() and that is the most likely state of the pthread when pthread_cancel() is called with deferred cancellation eanbled.

If asynchronous pthread cancellation were selected, then the behavior would be very different. The first two steps would be the same but then:

select() will not return to the caller in either case.

Application Interfaces

The following pthread application interfaces are available to manage cancellation:

Task Deletion

NuttX also supports some non-standard interfaces for task deletion (i.e., cancellation) of tasks as well. The cancellation point logic is identical and the task application interfaces are very similar:

Design Issues

There is one logical problem in many parts of the system. The following sequence of code appears in many places in the code base. It will work not work with thread/task cancellation:

  void some_lock(FAR sem_t *sem)
  {
    while ((ret = nxsem_wait(sem)) < 0)
      {
        DEBUGASSERT(ret = -EINTR || ret = -ECANCELED);
      }
  }

That logic will makes it impossible to cancel the thread. Let me explain why:

The end result is that Function B does not return to Function A so leave_cancellation_point() is never called and the task is not canceled. The fix requires a little re-design. The locking function would have to be like:

  int some_lock(FAR sem_t *sem)
  {
    while ((ret = nxsem_wait(sem)) < 0)
      {
        DEBUGASSERT(ret = -EINTR || ret = -ECANCELED);
        if (ret != -EINTR)
          {
            break;
          }
      }

    return ret;
  }

It then returns a success/failure indication. Calling logic would also have to be modified to detect the failure case and handle it appropriately. In the case that the failure case, the calling logic (Function B in this example) needs to clean up and return the failure upstream (to Function A) which must also clean up. Then, eventually, leave_critical_section() will be called and the function will exit correctly.