-
-
Notifications
You must be signed in to change notification settings - Fork 33.6k
gh-127266: avoid data races when updating type slots #131174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 7 commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
0e995ad
wip: update type slots, stop-the-world
nascheme b173f17
Remove unneeded atomics for tp_flags.
nascheme d4ce112
Use stop-the-world for tp_flag changes too.
nascheme 44eb332
Remove 'world_stops' and 'sys._get_world_stops'.
nascheme d132fab
Improve code comments.
nascheme 658bcd5
Remove TSAN suppressions that seem unneeded.
nascheme ef2f07b
Add NEWS file.
nascheme ce8536d
Use mutex rather than critical sections.
nascheme b97a4b4
Merge 'origin/main' into gh-127266-type-slots-ts
nascheme ca00e74
Fix non-debug build.
nascheme 6db4542
Improve comments.
nascheme 398ac14
Merge 'origin/main' into gh-127266-type-slots-ts
nascheme 75d6b71
Avoid unused function warning.
nascheme 895a86a
Remove unwanted suppression (bad merge).
nascheme 65e40f4
Fixes based on review feedback.
nascheme b68f1a1
Remove spurious assert().
nascheme 9976b32
Merge 'origin/main' into gh-127266-type-slots-ts
nascheme 1b84486
Omit mutex_tid member from default build.
nascheme 2af9e49
Remove Py_TPFLAGS_EXPOSED flag and related logic.
nascheme f65e87c
Improve comment for TYPE_LOCK.
nascheme 395a6d3
Re-add the ASSERT_NEW_OR_STOPPED() asserts.
nascheme e4f87e5
Further cleanups of the locking in typeobject.
nascheme 2a66555
Fix data race in resolve_slotdups().
nascheme 2efac26
Fix comment.
nascheme f7d2d36
Make the init of tp_dict thread-safe.
nascheme f3fd35a
Do some addtional locking simplification.
nascheme 57c2a44
Avoid acquiring the types mutex if version is set.
nascheme 7c0ccf5
Add check_invalid_reentrancy() call.
nascheme 4fa77bb
Fix additional re-entrancy issues found.
nascheme caf6554
Add comment explaining class_name() code.
nascheme 803d703
Merge 'origin/main' into gh-127266-type-slots-ts
nascheme 90ea541
Use atomic load to avoid thread safety issue.
nascheme 0dc0faf
Fix default debug build.
nascheme 956e5d1
Move declaration to avoid syntax error.
nascheme 2bb710c
Remove _PyType_GetVersionForCurrentState, unused.
nascheme 3a9bc96
Fix for possible re-entrancy in has_custom_mro().
nascheme d742a53
Use correct FT_ATOMIC_ macro.
nascheme e7480c3
Remove TSAN suppression for 'assign_version_tag'.
nascheme e2ea281
Small efficiency fix for types_mutex_set_owned().
nascheme 935bfca
Revert to using critical section with TYPE_LOCK.
nascheme 1cff448
Merge 'origin/main' into gh-127266-type-slots-ts
nascheme a81e9e3
Invalidate type cache before calling watchers.
nascheme f5df0c3
Fixes for type_modified_unlocked().
nascheme 7db281c
Major re-work, TYPE_LOCK protects more things.
nascheme da2a0ad
Merge 'origin/main' into gh-127266-type-slots-ts
nascheme 986f23a
Fix non-debug build.
nascheme c404ed4
Revert unneeded code changes.
nascheme 55af4ba
Merge branch 'origin/main' into gh-127266-type-slots-ts
nascheme 0eb77da
Restore comment
nascheme 16f15b2
Revert more changes.
nascheme 0c328cc
Merge 'origin/main' into gh-127266-type-slots-ts
nascheme 64547e9
Reduce item list size for a few tests.
nascheme fff1bd2
Merge 'origin/main' into gh-127266-type-slots-ts
nascheme 5672352
Minor code tidy.
nascheme File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
Misc/NEWS.d/next/Core_and_Builtins/2025-03-14-13-08-20.gh-issue-127266._tyfBp.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| In the free-threaded build, avoid data races caused by updating type slots | ||
| or type flags after the type was initially created. For those (typically | ||
| rare) cases, use the stop-the-world mechanism. Remove the use of atomics | ||
| when reading or writing type flags. The use of atomics is not sufficient to | ||
| avoid races (since flags are sometimes read without a lock and without | ||
| atomics) and are no longer required. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,6 +84,17 @@ class object "PyObject *" "&PyBaseObject_Type" | |
|
|
||
| #endif | ||
|
|
||
| // Modification of type slots and type flags is protected by the global type | ||
| // lock. However, the slots and flags are read non-atomically without holding | ||
| // the type lock. So, we need to stop-the-world while modifying these, in | ||
| // order to avoid data races. This is unfortunately a bit expensive. | ||
| #ifdef Py_GIL_DISABLED | ||
| // If defined, type slot updates (after initial creation) will stop-the-world. | ||
| #define TYPE_SLOT_UPDATE_NEEDS_STOP | ||
| // If defined, type flag updates (after initial creation) will stop-the-world. | ||
| #define TYPE_FLAGS_UPDATE_NEEDS_STOP | ||
| #endif | ||
|
|
||
| #define PyTypeObject_CAST(op) ((PyTypeObject *)(op)) | ||
|
|
||
| typedef struct PySlot_Offset { | ||
|
|
@@ -353,9 +364,7 @@ type_set_flags(PyTypeObject *tp, unsigned long flags) | |
| // held when flags are modified. | ||
| ASSERT_TYPE_LOCK_HELD(); | ||
| } | ||
| // Since PyType_HasFeature() reads the flags without holding the type | ||
| // lock, we need an atomic store here. | ||
| FT_ATOMIC_STORE_ULONG_RELAXED(tp->tp_flags, flags); | ||
| tp->tp_flags = flags; | ||
| } | ||
|
|
||
| static void | ||
|
|
@@ -1584,9 +1593,10 @@ type_set_abstractmethods(PyObject *tp, PyObject *value, void *Py_UNUSED(closure) | |
| BEGIN_TYPE_LOCK(); | ||
| type_modified_unlocked(type); | ||
| if (abstract) | ||
| type_add_flags(type, Py_TPFLAGS_IS_ABSTRACT); | ||
| _PyType_SetFlags(type, 0, Py_TPFLAGS_IS_ABSTRACT); | ||
| else | ||
| type_clear_flags(type, Py_TPFLAGS_IS_ABSTRACT); | ||
| _PyType_SetFlags(type, Py_TPFLAGS_IS_ABSTRACT, 0); | ||
|
|
||
| END_TYPE_LOCK(); | ||
|
|
||
| return 0; | ||
|
|
@@ -3490,6 +3500,9 @@ static int update_slot(PyTypeObject *, PyObject *); | |
| static void fixup_slot_dispatchers(PyTypeObject *); | ||
| static int type_new_set_names(PyTypeObject *); | ||
| static int type_new_init_subclass(PyTypeObject *, PyObject *); | ||
| #ifdef Py_GIL_DISABLED | ||
| static bool has_slotdef(PyObject *); | ||
| #endif | ||
|
|
||
| /* | ||
| * Helpers for __dict__ descriptor. We don't want to expose the dicts | ||
|
|
@@ -3687,7 +3700,7 @@ type_init(PyObject *cls, PyObject *args, PyObject *kwds) | |
| unsigned long | ||
| PyType_GetFlags(PyTypeObject *type) | ||
| { | ||
| return FT_ATOMIC_LOAD_ULONG_RELAXED(type->tp_flags); | ||
| return type->tp_flags; | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -5779,7 +5792,17 @@ void | |
| _PyType_SetFlags(PyTypeObject *self, unsigned long mask, unsigned long flags) | ||
| { | ||
| BEGIN_TYPE_LOCK(); | ||
| type_set_flags_with_mask(self, mask, flags); | ||
| unsigned long new_flags = (self->tp_flags & ~mask) | flags; | ||
| if (new_flags != self->tp_flags) { | ||
| #ifdef TYPE_FLAGS_UPDATE_NEEDS_STOP | ||
| PyInterpreterState *interp = _PyInterpreterState_GET(); | ||
nascheme marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| _PyEval_StopTheWorld(interp); | ||
| #endif | ||
| self->tp_flags = new_flags; | ||
| #ifdef TYPE_FLAGS_UPDATE_NEEDS_STOP | ||
| _PyEval_StartTheWorld(interp); | ||
| #endif | ||
| } | ||
| END_TYPE_LOCK(); | ||
| } | ||
|
|
||
|
|
@@ -5943,6 +5966,21 @@ _Py_type_getattro(PyObject *tp, PyObject *name) | |
| return _Py_type_getattro_impl(type, name, NULL); | ||
| } | ||
|
|
||
| #ifdef TYPE_SLOT_UPDATE_NEEDS_STOP | ||
| static int | ||
| update_slot_world_stopped(PyTypeObject *type, PyObject *name) | ||
nascheme marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| int ret; | ||
| PyInterpreterState *interp = _PyInterpreterState_GET(); | ||
| _PyEval_StopTheWorld(interp); | ||
| ret = update_slot(type, name); | ||
| _PyEval_StartTheWorld(interp); | ||
| return ret; | ||
| } | ||
| #endif | ||
|
|
||
| // Called by type_setattro(). Updates both the type dict and | ||
| // any type slots that correspond to the modified entry. | ||
| static int | ||
| type_update_dict(PyTypeObject *type, PyDictObject *dict, PyObject *name, | ||
| PyObject *value, PyObject **old_value) | ||
|
|
@@ -5972,9 +6010,15 @@ type_update_dict(PyTypeObject *type, PyDictObject *dict, PyObject *name, | |
| return -1; | ||
| } | ||
|
|
||
| #ifdef TYPE_SLOT_UPDATE_NEEDS_STOP | ||
| if (is_dunder_name(name) && has_slotdef(name)) { | ||
| return update_slot_world_stopped(type, name); | ||
nascheme marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| #else | ||
| if (is_dunder_name(name)) { | ||
| return update_slot(type, name); | ||
| } | ||
| #endif | ||
|
|
||
| return 0; | ||
| } | ||
|
|
@@ -11002,6 +11046,20 @@ resolve_slotdups(PyTypeObject *type, PyObject *name) | |
| #undef ptrs | ||
| } | ||
|
|
||
| #ifdef TYPE_SLOT_UPDATE_NEEDS_STOP | ||
| // Return true if "name" corresponds to at least one slot definition. This is | ||
| // a more accurate but more expensive test compared to is_dunder_name(). | ||
| static bool | ||
| has_slotdef(PyObject *name) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| { | ||
| for (pytype_slotdef *p = slotdefs; p->name_strobj; p++) { | ||
| if (p->name_strobj == name) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| #endif | ||
|
|
||
| /* Common code for update_slots_callback() and fixup_slot_dispatchers(). | ||
| * | ||
|
|
@@ -11241,20 +11299,33 @@ fixup_slot_dispatchers(PyTypeObject *type) | |
| END_TYPE_LOCK(); | ||
| } | ||
|
|
||
| // Called when __bases__ is re-assigned. | ||
| static void | ||
| update_all_slots(PyTypeObject* type) | ||
| { | ||
| pytype_slotdef *p; | ||
|
|
||
| ASSERT_TYPE_LOCK_HELD(); | ||
|
|
||
| #ifdef TYPE_SLOT_UPDATE_NEEDS_STOP | ||
| // Similar to update_slot_world_stopped(), this is required to avoid | ||
| // races. We don't use update_slot_world_stopped() here because we want | ||
| // to stop once rather than once per slot. | ||
| PyInterpreterState *interp = _PyInterpreterState_GET(); | ||
| _PyEval_StopTheWorld(interp); | ||
|
||
| #endif | ||
|
|
||
| /* Clear the VALID_VERSION flag of 'type' and all its subclasses. */ | ||
| type_modified_unlocked(type); | ||
|
|
||
| for (p = slotdefs; p->name; p++) { | ||
| /* update_slot returns int but can't actually fail */ | ||
| update_slot(type, p->name_strobj); | ||
| } | ||
|
|
||
| #ifdef TYPE_SLOT_UPDATE_NEEDS_STOP | ||
| _PyEval_StartTheWorld(interp); | ||
| #endif | ||
| } | ||
|
|
||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.