Skip to content

gh-117657: Use an atomic store to set type flags. - #127588

Merged
nascheme merged 4 commits into
python:mainfrom
nascheme:gh-117657-tp-flags-set-atomic
Feb 26, 2025
Merged

gh-117657: Use an atomic store to set type flags.#127588
nascheme merged 4 commits into
python:mainfrom
nascheme:gh-117657-tp-flags-set-atomic

Conversation

@nascheme

@naschemenascheme commented Dec 4, 2024

Copy link
Copy Markdown
Member

The PyType_HasFeature() function reads the flags with a relaxed atomic load and without holding the type lock. To avoid data races, use atomic stores to update the type flags.

This change eliminates some warnings from TSAN. For non-free-threaded builds, there should be no performance impact.

The `PyType_HasFeature()` function reads the flags with a relaxed atomic
load and without holding the type lock. To avoid data races, use atomic
stores if `PyType_Ready()` has already been called.
Comment threadObjects/typeobject.c

@colesburycolesbury left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, looks good to me. I think type_set_flags can be simplified and I have a question below about locking.

Comment threadObjects/typeobject.c Outdated
Comment threadObjects/typeobject.c
Just use FT_ATOMIC_STORE_ULONG_RELAXED() always, not worth the extra
code complexity.
This helps ensure that type flag setting is done in a free-threading
safe way. Fix a few cases were flags were set without holding the type
lock. Use `type_modified_unlocked()` in a couple places where the lock
is already head.
@nascheme

Copy link
Copy Markdown
MemberAuthor

If I add the assert, it fails like this:

_PyCriticalSection_AssertHeld (mutex=<optimized out>) at ./Include/internal/pycore_critical_section.h:222
type_set_flags (tp=tp@entry=0x200006adc10, flags=22016) at Objects/typeobject.c:355
type_clear_flags (tp=tp@entry=0x200006adc10, flag=flag@entry=1048576) at Objects/typeobject.c:379
type_set_abstractmethods (type=0x200006adc10, value=0x20000342010, context=<optimized out>) at Objects/typeobject.c:1570
getset_set (self=0x20000010790, obj=0x200006adc10, value=0x20000342010) at Objects/descrobject.c:249
type_setattro (self=0x200006adc10, name=<optimized out>, value=0x20000342010) at Objects/typeobject.c:5967
PyObject_SetAttr (v=v@entry=0x200006adc10, name=<optimized out>, value=value@entry=0x20000342010) at Objects/object.c:1409

The code in type_set_abstractmethods is:

 PyType_Modified(type);
if (abstract)
type_add_flags(type, Py_TPFLAGS_IS_ABSTRACT);
else
type_clear_flags(type, Py_TPFLAGS_IS_ABSTRACT);

When this runs, the type is already marked ready. The type lock is held inside PyType_Modified(). To me this looks like a potential data race. Another thread could read the flags after PyType_Modified releases the lock and before the next lines of code are executed.

Looking at PyType_Modified(), I wonder if there is another data race there. The code is:

 // Quick check without the lock held
if (type->tp_version_tag == 0) {
return;
}
BEGIN_TYPE_LOCK();
type_modified_unlocked(type);
END_TYPE_LOCK();

The tp_version_tag is set with an atomic store (by set_version_unlocked()). I would think when checking if it's 0, we need an atomic load for it.

A possible solution could be to change the type_set_abstractmethods code to be:

 BEGIN_TYPE_LOCK();
type_modified_unlocked(type);
if (abstract)
type_add_flags(type, Py_TPFLAGS_IS_ABSTRACT);
else
type_clear_flags(type, Py_TPFLAGS_IS_ABSTRACT);
END_TYPE_LOCK();

@nascheme

Copy link
Copy Markdown
MemberAuthor

Using type_modified_unlocked() in couple of places and putting the flag updates inside the critical section seems to have worked well. I only had to modify a couple places and the unit tests now all pass without that lock held assertion failing.

@nascheme
nascheme merged commit baae9cb into python:mainFeb 26, 2025
@nascheme
nascheme deleted the gh-117657-tp-flags-set-atomic branch February 28, 2025 04:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@nascheme@colesbury@ZeroIntensity