A supervised block has a meaning when the signal can be generated by some actions occurring in the block, not with a signal that can occur at any time, even before or after it, except perhaps the case in which we want to protect the block so as to ensure that it performs a transaction (in which case when it is interrupted by a signal and it cannot complete the transaction, it rolls back. Note that a protection is to block signals. A process that wants to treat graceful kill must enclose all the code in a supervised block (which is what cancellation does). As an example, consider time supervision, in which the supervised block starts exactly when a timer is started, and ends when it is stopped.
In the case 1 above, a signal handler is needed to do some action when a specific signal has occurred and a system call is interrupted by a signal. Since handlers for other signals can be in force, that signal handler for it would set a flag, which is not set by the others. Note that the default disposition is either to ignore or to terminate the process, and therefore, to continue execution, a signal handler is needed. System calls return the EINTR error to let a program know that they aborted. However, this happens with any signal. Testing a flag set by a handler is more specific. This has a race: after having tested EINTR, the signal handler could run and set its flag. A subsequent test is then not revealing: it would be true also when another signal interrupted the system call, and then the desired one interrupted the thread after the call. It is silly that we cannot know what signal interrupted a call. But then perhaps there is a race also in the example of the Advanced ... from which I took the idea of the specific kill flag. Well, on the other hand, a system call that returns with EINTR has not allocated a resource anyway. ... well, if we block signals, which should be the default for all threads except one dedicated to that, then there is no need for a handler to set a flag: it could just do nothing, being its purpose that of avoiding to use the default disposition which is likely to kill unconditionally the process.
Let's tackle the implementation of supervised blocks by considering first the races that occur when they are implemented naively by registering a handler that interrupts slow system calls and sets a flag, and testing before and/or after system calls the value of the flag.
The solution to avoid to loose a signal in a supervised block is that of
pselect(): signals are blocked, then in the block
pselect() is called, which atomically suspends the process and
unblocks signals (to block them again when resumes). It is then possible to
test the occurrence of the signal (having a handler that sets a flag) or EINTR.
Also a ppoll() does that. This is a way to make signals
persistent. Unfortunately, this feature is present only for a couple of system
calls. E.g. there is no equivalent for a sem_wait(). Besides that,
not all slow system calls are aborted by signals. Note that blocking signals
and testing EINTR is exactly making the victim suicide. There is no way to
handle killing safely when done asynchronously (i.e. without the help of the
victim). When a signal comes, and pselect() is waiting, the
handler (if any) is executed. EINTR can be tested to detect abortion of system
calls.
Races are defined in wikipedia as results that are time dependent, while they should not have been. However, in wikipedia they seem restricted to the ones occurring on data when several processes read/write them, which is normally solved with the notion of atomicity. They are present also in other contexts, such as signals when we implement supervised blocks. In this case we want that another thread of execution (the signal handler) meddles with the supervised one, and want to avoid races that occur in doing it (as the ones reported in this document). With supervised blocks the tools we have are atomically blocking signals+waiting/unblocking+resuming.