PTHREAD_CANCEL_DEFERRED

David Xu davidxu at freebsd.org
Fri Aug 13 01:14:57 UTC 2010


Kostik Belousov wrote:
> On Thu, Aug 12, 2010 at 05:25:47PM +0000, David Xu wrote:
>> 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.
> [1]
> 
>> B: still doing other things...
>> B: call close()
> [2]
> 
>> 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.
> This situation should be handled by my proposal, since SIGCANCEL is
> delivered only
> - at the syscall entry point
> - at the syscall premature return
> Userspace would not get SIGCANCEL at time of [1], instead, signal will
> be delivered at [2].
kernel may don't know if the syscall is cancelable, because it depends
on usage, if the close() syscall is used by fclose(), then the syscall
is not cancellation point, libc avoids this by using _close(),
and libthr does not override it. if kernel knows when a thread is at
cancellation point, then it needs another syscall to set and unset
the flag, but that's too expensive and in practical it is not
acceptable.

Regards,
David Xu



More information about the freebsd-threads mailing list