[PATCH] nptl: Disable asynchronous cancellation on __do_cancel (BZ 32782)
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Wed Mar 12 18:13:48 GMT 2025
On 12/03/25 15:04, Florian Weimer wrote:
> * Adhemerval Zanella Netto:
>
>>>> diff --git a/sysdeps/nptl/pthreadP.h b/sysdeps/nptl/pthreadP.h
>>>> index 2d620ed20d..63de8904f4 100644
>>>> --- a/sysdeps/nptl/pthreadP.h
>>>> +++ b/sysdeps/nptl/pthreadP.h
>>>> @@ -267,8 +267,16 @@ __do_cancel (void *result)
>>>>
>>>> self->result = result;
>>>>
>>>> - /* Make sure we get no more cancellations. */
>>>> - atomic_fetch_or_relaxed (&self->cancelhandling, EXITING_BITMASK);
>>>> + /* Disable asynchronous cancellation and signal that thread is exiting. */
>>>> + int cancelhandling = atomic_load_relaxed (&self->cancelhandling);
>>>> + int newval;
>>>> + do
>>>> + {
>>>> + newval = (cancelhandling & ~CANCELTYPE_BITMASK) | EXITING_BITMASK;
>>>> + }
>>>> + while (!atomic_compare_exchange_weak_acquire (&self->cancelhandling,
>>>> + &cancelhandling,
>>>> + newval));
>>>
>>> Unecessary extra braces.
>>
>> Do you mean just:
>>
>> int cancelhandling = atomic_load_relaxed (&self->cancelhandling);
>> int newval;
>> do
>> newval = (cancelhandling & ~CANCELTYPE_BITMASK) | EXITING_BITMASK;
>> while (!atomic_compare_exchange_weak_acquire (&self->cancelhandling,
>> &cancelhandling,
>> newval));
>>
>> ?
>
> Exactly.
>
>>> The cause and nature of the change match my expectations, I merely
>>> wonder how the behavior compares to glibc before your cancellation fix
>>> was applied.
>>
>> Before the bz12683 fix, the SIGCANCEL handler also did not act upon
>> cancellation if the thread was already cancelled:
>>
>> 45 int oldval = atomic_load_relaxed (&self->cancelhandling);
>> 46 while (1)
>> 47 {
>> 48 /* We are canceled now. When canceled by another thread this flag
>> 49 is already set but if the signal is directly send (internally or
>> 50 from another process) is has to be done here. */
>> 51 int newval = oldval | CANCELING_BITMASK | CANCELED_BITMASK;
>> 52
>> 53 if (oldval == newval || (oldval & EXITING_BITMASK) != 0)
>> 54 /* Already canceled or exiting. */
>> 55 break;
>> 56
>> 57 if (atomic_compare_exchange_weak_acquire (&self->cancelhandling,
>> 58 &oldval, newval))
>> 59 {
>> 60 self->result = PTHREAD_CANCELED;
>> 61
>> 62 /* Make sure asynchronous cancellation is still enabled. */
>> 63 if ((oldval & CANCELTYPE_BITMASK) != 0)
>> 64 /* Run the registered destructors and terminate the thread. */
>> 65 __do_cancel ();
>> 66 }
>> 67 }
>>
>> The different was it checked the EXITING_BITMASK. Another possibility
>> would to add a function like cancel_async_enabled_and_not_cancelled
>> and check it instead of cancel_async_enabled; but I think this is
>> slight more clear (since the same logic is already used on
>> __pthread_unwind).
>
> There's still an observable difference: We used to disable cancellation
> (state == 0), but we still preserve the async cancel state (state == 1).
> The difference is observable in cancellation handlers (tf_cleanup in the
> test case).
>
> The difference should not matter in practice, but maybe we should
> reproduce the previous behavior and not clear the async cancel flag?
Indeed I though about that, I will change to check for EXITING_BITMASK
and keep the async state unchanged.
More information about the Libc-alpha
mailing list