PTHREAD_CANCEL_DEFERRED

David Xu davidxu at freebsd.org
Thu Aug 12 09:25:48 UTC 2010


Kostik Belousov wrote:
> On Thu, Aug 12, 2010 at 10:59:57AM +0000, David Xu wrote:
>> Kostik Belousov wrote:
>>> Hi,
>>> Let consider the thread in the state where the cancelation is enabled
>>> and cancelation mode set to the PTHREAD_CANCEL_DEFERRED.
>>>
>>> SUSv4 says the following:
>>> Whenever a thread has cancelability enabled and a cancellation request
>>> has been made with that thread as the target, and the thread then
>>> calls any function that is a cancellation point (such as
>>> pthread_testcancel() or read()), the cancellation request shall be
>>> acted upon before the function returns. If a thread has cancelability
>>> enabled and a cancellation request is made with the thread as a target
>>> while the thread is suspended at a cancellation point, the thread
>>> shall be awakened and the cancellation request shall be acted upon.
>>>
>>> Take the close(2) as example, and assume that the cancel is enabled
>>> for the thread in deferred mode. libthr wrapper around the syscall
>>> executes this:
>>>
>>>     	curthread->cancel_point++;
>>> 	testcancel(curthread);
>>> 	__sys_close(fd);
>>> 	curthread->cancel_point--;
>>>
>>> The pthread_cancel() sends the SIGCANCEL signal to the
>>> thread. SIGCANCEL handler calls pthread_exit() if thread has the
>>> cancel_point greater then 0.
>>>
>>> I think this is not right. For instance, thread can be canceled due to
>>> SIGCANCEL delivery at the point after the return into the usermode
>> >from close(), but before cancel_point is decremented. IMO, the cited
>>> statements from the SUSv4 prohibit such behaviour. We should cancel
>>> either before closing fd, or during the sleep in close(), but then the
>>> syscall should be aborted ("as if signal is delivered"), and again fd
>>> should not be closed.
>>>
>>> Actually, besides not being compliant, I think that the current
>>> behaviour is not useful, since in deferred case, we cannot know
>>> whether the action by the call that is cancelation point was performed
>>> or not.
>>>
>>> This is not a rant, I probably will fix the issue if it is agreed
>>> upon. Opinions ?
>> it is true that a cancellation point does not return when cancellation
>> happens, so we really does not know if it did something or not,
>> and SUSv4 does not say cancellation point is an atomic transaction,
>> and whether it will rollback when cancellation happens, the problem may
>> happen even if you want to fix it, if the cancellation request is sent
>> after int 0x80 instruction returns (on i386) but before libc close()
>> stub returns, the cancellation still can happen, so could you tell
>> if the file handle is closed or not ? there is no way to tell
>> caller that the close() really did something.
>> I found the issue when I wrote the code, and it seems Linux does
>> it in same way, so I had not done further work on it.
> 
> What I am proposing is to not cancel if close() indeed closed the
> descriptor. The same could be done for all other cancelation points that
> are syscalls.
> 
> I evaluated the possible implementation. It looks like it is enough,
> when thread is switched to cancel enabled and deferred mode, to make
> SIGCANCEL delivery special. Namely, it should be only delivered at the
> cancelable syscall _entry_ point and on syscall return when error is
> EINTR or ERESTART. pthread_testcancel() is also becoming a dummy syscall,
> with the same rules of SIGCANCEL delivery.
> 
> Cancelable syscalls would be marked in the syscall table, the cost of
> the change is that pthread_setcanceltype() becomes a syscall.

This might be not enough, because I found a scenario:
suppose there are two threads A and B:

B: pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED)
A: pthread_cancel(B)
    it sets cancel_pending to 1 and send SIGCANCEL to B
B: got SIGCANCEL delivered to userland and did nothing
    because it is not at cancel point.
B: still doing other things...
B: call close()
B: sleep in kernel because no SIGCANCEL found.

if you don't call testcancel() in close() stub like current libthr did,
B won't response to the cancel request, you lost the race.





More information about the freebsd-threads mailing list