Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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.

Issue 619 Discussion

nxsem_wait_uninterruptible

In NuttX, thread or tasks may be canceled using pthread_cancle() or task_delete() These can also be cancel via signals if default signal actions are enabled.

...

In NuttX, when threads are blocked waiting of a semaphore when they are canceled, nxsem_wait() will return -ECANCELED and the correct behavior to let the error ripple back to the cancellation point where the thread/task will be canceled after all resources have been cleaned up.

Many internal OS functions use nxsem_wait_uninterruptible() for waiting. nxsem_wait_uninterruptible() is defined in include/nuttx/semaphore.h. nxsem_wait_uninterruptible() wraps a call to nxsem_wait() in a loop like:

...

The correct behavior is to continue waiting on -EINTR only.  If the task/thread is awakened by a signal just continue waiting.  The behavior for -ECANCELED is not correct. It should not loop.  Looping like this will prevent the cancellation from working.  Instead of returning through the cancellation point, the code will be stuck in the above loop (in defferred cancellation mode) and cannot be cancelled.

The correct behavior is to return when -ECANCELED is encountered, not to continue looping:

...

These are other related inline functions in include/nuttx/semaphore.h that behave in this same way. This issue applies to them as well.

Corollary:  If cancellation points are enabled, the default cancellation mode should be deferred mode.

Mutual Exclusion Semaphores vs. Signaling Semaphores

Semaphore have two usages in NuttX:

  1. Mutual Exclusion Semaphores:  Protect the shared data and force mutually exclusive use of semaphores.
  2. Signling Semaphores:  Used to wait for events to be signaled asynchronously

Should we return -ECANCEL/-EINTR for both?  Or only for ithe second?  I think both I refer to those use cases as mutual exclusion semaphores, and signaling semaphores. I think both should work the same: For both the semaphore wait should be terminated and correct error should be returned.

If the API requires returning EINTR if a signal is received, we should return the error.  This is required in most many POSIX interfaces and don't see how you could avoid that.  However, the mutual exclusion semaphores are the just there for the correctness in the design.  There is seldom any real competitor for the exclusion semaphore andso, in general, they never do not block and, hence, would never generate the any error.

And even if taking the mutual exclusion semaphore does cause the thread to block, it should block form only a very brief moment of time and the likelihood of a signal received or of the task being canceled is very small.

But if there is even a small possibility of that event happening, it WILL happen in an embedded system (Murphy's law).

But if during the that brief moment while the thread is blocked, a signal is received, I think that it is correct to return -EINTR. This is not so critical only because such an event it is rare.  But the specification requires it so we should do it.

But the ECANCELED error is ABSOLUTELY critical to return under any circumstance.  That cancellation notification is received only once and it if is ignored, then the thread will not cancel it will continue to run. It really must return immediately with ECANCELED for the cancellation to work quickly and reliably, where ever ECANCELED is detected.  Ignoring it is a hard bug in any case.

The signaling semaphores are a little different story. The may block for a very long time, for example, waiting for the receipt of data that will never come. For example, a read from a serial port will hang indefinitely if nothing is received on the serial port.  So if a signal is received or if the thread is canceled, then it is essential to terminate the semaphore wait and return the error condition. But we   We should have no difference of opinion on that.

There is really no difference in the actions that must be taken if a signal is received of if the task is canceled. Both mutual exclusion semaphores and signaling semaphores should wake up and return the error condition. The only real differences is that mutual exclusion semaphores either do not block or, if they do, the do not block as long.long.

Returning EINTR really only applies to read() and write() (and open()) functions as covered by Issue #669 . In other places, EINTR can be ignored.  open() and close() should also return EINTR.  But I don't think that is very meaningful for close() since most people ignore the return value from close().  But, on the other hand, ECANCELED can never be ignored under any condition.

Detecting, Remembering, and Propagating Signal Interrupts and Cancellation Events

Currently, the only way for an OS interface function to know if a signal was received is to be awakened with an EINTR error. That is why is is critical to always return the EINTR error and conform with the POSIX requirements.

However, if a signal is received while the OS interface is NOT waiting, then there is no way to know if the signal was received.  I think that is a limitation in the current design.  There probably should be some global latching indication, perhaps in the TCB, that a signal interrupt has been received.

There is already a TCBTCB_FLAG_CANCEL_PENDING in the TCB that will tell us that the if the task has been canceled.  That flag is tested in the leave_cancellation_point() function so I don't think that there is any corresponding issue for thread cancellation.  We just need to make sure that all waits are aborted if ECANCELED is received and let the error indication ripple all they back to the leave_cancellation_point() function. Then the function will exit cleanly, safely, and quickly.

Interestingly, the ECANCELED error will never be seen by the application.  It just triggers the return uwind sequence where all resources are recovered and finally until leave_cancellation_point() is called -- then .  Then the thread will exit before it returns to the applicaton.

You can see all of this working in the board/sim/sim/sim/configs/ostest configuration if you also enable:

CONFIG_CANCELLATION_POINTS=y
CONFIG_PTHREAD_CLEANUP=y

There is a cancel.c test within the OS test, but the more interesting test is the pthread_cleanup.c test.  You can see how all this works when the code unwinds with the ECANCELED error and calls leave_cancellation_point(). Just single step through pthread_cond_wait() to see this.  pthread_cond_wait() is a cancellation point() and that is where the thread exit will occur.

PR #749 adds those settingst settings to that sim otest defconfig

...