[PATCH v2 4/4] libio: Fix race access to _IO_FLAGS2_NEED_LOCK
Adhemerval Zanella
adhemerval.zanella@linaro.org
Mon May 10 21:46:46 GMT 2021
On 10/05/2021 18:42, Adhemerval Zanella wrote:
>
>
> On 10/05/2021 16:33, Florian Weimer wrote:
>> * Adhemerval Zanella:
>>
>>> On 10/05/2021 16:18, Florian Weimer wrote:
>>>> * Adhemerval Zanella:
>>>>
>>>>> I see that my first assumption of moving the _IO_FLAGS2_NEED_LOCK after
>>>>> the lock should be suffice. The data race is not really related to
>>>>> _IO_FLAGS2_NEED_LOCK set in fact, but rather the small window where
>>>>> the _flags2 read and update with _IO_FLAGS2_NEED_LOCK might lost some
>>>>> bits update.
>>>>
>>>> Right.
>>>>
>>>>> Maybe the comment:
>>>>>
>>>>> /* The _IO_FLAGS2_NEED_LOCK is required here to enable the non-atomic
>>>>> fast path on function that call _IO_need_lock. However it requires
>>>>> to be set with stream locked to avoid a potential data race. */
>>>>
>>>> s/fast path/path/ I suppose, and “it necessary to set it with the stream
>>>> locked”.
>>>
>>>
>>> /* The _IO_FLAGS2_NEED_LOCK is required here to enable the non-atomic
>>> path on function that call _IO_need_lock. However it is necessary
>>> to set it with the stream locked to avoid a potential data race. */
>>
>> Meh, I'm confused. Aren't we disabling the non-atomic path?
>
> With this discussion it seems to me that the _IO_FLAGS2_NEED_LOCK set on
> flockfile is indeed superfluous. On the code:
>
> {
> flockfile (arq);
>
> write (STDERR_FILENO, "(1)\n", sizeof ("(1)\n"));
> feof (arq);
>
> funlockfile (arq);
> }
>
> {
> write (STDERR_FILENO, "(2)\n", sizeof ("(2)\n"));
> feof (arq);
> }
>
> The second feof will always take the lock because _IO_FLAGS2_NEED_LOCK is
> sticky, even though this is fine to use the fast non-atomic path (I am
> assuming single-thread here).
>
>>
>>>> Why not? The single-thread optimization would still avoid the atomic
>>>> compare-and-set, which should be a significant win on AArch64 and POWER.
>>>> There is more work to be done for the single-threaded case compared to
>>>> _IO_FLAGS2_NEED_LOCK, but all that is without atomics, so I think those
>>>> should be reasonably fast.
>>>
>>> Because it would solve only for single-thread case, for multithread
>>> case the single-thread optimization won't give the function that
>>> call _IO_need_lock the correct information to elide the lock.
>>>
>>> The seqeuence:
>>>
>>> flockfile (arq);
>>>
>>> feof (arq);
>>> ferror (arq);
>>> ...
>>>
>>> funlockfile (arq);
>>>
>>> Should only incur is atomic operation on flockfile and funlockfile
>>> in both single and multi-thread case. BY tying the feof taking the
>>> lock to single-thread optimization it incur in extra atomic operations
>>> on multi-thread case.
>>
>> There are two relevant locking optimizations: flockfile acquires the
>> lock for the current thread. At that point, the ownership check for the
>> recursive lock succeeds. That check doesn't use an atomic load, so it's
>> fast.
>>
>> The other optimization is generally enabled via __fsetlocking (arq,
>> FSETLOCKING_BYCALLER), which enables the _IO_USER_LOCK flag that
>> disables locking in feof and ferror as well (but not for flockfile and
>> funlockfile).
>>
>> The latter is perhaps not necessary because the application could just
>> call flockfile.
>
> This is indeed messy, ideally I think we would like something like:
>
> int
> _IO_feof (FILE *fp)
> {
> __flockfile (fp);
> int result = _IO_feof_unlocked ();
> __funlockfile (fp);
> return result;
> }
>
> void
> __flockfile (FILE *stream)
> {
> _IO_lock_lock (*stream->lock);
> }
>
> static inline void
> _IO_lock_lock (_IO_lock_t *lock)
> {
> void *self = THREAD_SELF;
> if (lock->owner != self)
> {
> lll_lock (name->lock, LLL_PRIVATE);
> lock->owner = self;
> }
> ++lock->cnt;
> }
>
> The __flockfile should provide single-thread optimization through the
> _IO_lock_lock (and the recursive lock). However we can't really use
> __flockfile internally because of __fsetlocking (FSETLOCKING_BYCALLER),
> so we need a wrapper to check for _IO_USER_LOCK (which is provided by
> the _IO_need_lock wrapper).
>
> The _IO_need_lock and _unlocked call really seems unnecessary, at least
> on Linux that already provides a recursive lock with owner.
>
Sigh... we still need for the open_memstream case (de895ddcd7fc45c).
Maybe we make open_memstream use a invalid owner value (either NULL
or -1) to force the _IO_lock_lock always call lll_lock
More information about the Libc-alpha
mailing list