Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-118362: Fix thread safety around lookups from the type cache in the face of concurrent mutators#118454

Merged
DinoV merged 4 commits intopython:mainfrom
DinoV:nogil_type_crash
May 6, 2024
Merged

gh-118362: Fix thread safety around lookups from the type cache in the face of concurrent mutators#118454
DinoV merged 4 commits intopython:mainfrom
DinoV:nogil_type_crash

Conversation

@DinoV
Copy link
Contributor

@DinoVDinoV commentedMay 1, 2024
edited by bedevere-appbot
Loading

Lookups from the type cache can be exposed to some races. One of those is that_PyType_Lookup isn't increfing the result, so the resulting value can become invalid at any point. This adds a new_PyType_Fetch API which does the incref. Most places are updated to use the new API except for the specializer which isn't safe in free-threaded builds yet.

But there are also issues around when we do a store into the dict and signal that the type is modified. A race can occur where the old value is removed from the dict and before we invalidate the type version. This can be seen today w/ the GIL when the value in the dict has a finalizer - accessing the type cache and the type's mapping proxy return inconsistent results.

This fixes these issues by making the update to the dict and the type modified update an atomic operation. We also capture the previous value in the dictionary and don't free it until after we've made the atomic update. This basically inlines a much simplified version of_PyObject_GenericSetAttrWithDict intotype_setattro which can lock mutation against types as well as against the type's dict.

When updating we first clear the type version so concurrent reads of the type cache won't hit. We then replace the existing value and finish the type modified process.

Makes setting an attribute on a class and signaling type modified atomicAvoid adding re-entrancy exposing the type cache in an inconsistent state by decrefing after type is updated
Copy link
Contributor

@colesburycolesbury left a comment

Choose a reason for hiding this comment

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

Some questions comments below. Biggest questions is that now that we are accessing thedict directly instead of through_PyObject_GenericSetAttrWithDict, how do we know that the other cases handled by_PyObject_GenericSetAttrWithDict are not necessary in_Py_type_getattro?

Also, I think this merits a NEWS entry.

@DinoV
Copy link
ContributorAuthor

Some questions comments below. Biggest questions is that now that we are accessing thedict directly instead of through_PyObject_GenericSetAttrWithDict, how do we know that the other cases handled by_PyObject_GenericSetAttrWithDict are not necessary in_Py_type_getattro?

This is part of the reason why I assert:

    assert(!_PyType_HasFeature(metatype, Py_TPFLAGS_INLINE_VALUES));    assert(!_PyType_HasFeature(metatype, Py_TPFLAGS_MANAGED_DICT));

In type_setattro. Type objects will always have a dictptr, so we'll either go to_PyObjectDict_SetItem if the dict doesn't exist yet or do the get/set if they do._PyObjectDict_SetItem is just going to do a get/set as well. And because types don't have a managed dict they don't have shared keys.

Also, I think this merits a NEWS entry.

@DinoVDinoV marked this pull request as ready for reviewMay 1, 2024 20:39
Fix some formattingAdd news blurbUse PyDictObject * moreRename _PyDict_GetItemRef_LockHeld to _PyDict_GetItemRef_Unicode_LockHeldReduce iterations in testExpose _PyObject_SetAttributeErrorContext for attaching context
@DinoV
Copy link
ContributorAuthor

Also, I think this merits a NEWS entry.
I opened#118492 to track the issue that is fixed when the GIL is present and blurb'd it.

@colesburycolesbury changed the title[gh-118362] Fix thread safety around lookups from the type cache in the face of concurrent mutatorsgh-118362: Fix thread safety around lookups from the type cache in the face of concurrent mutatorsMay 2, 2024
extern PyTypeObject* _PyType_CalculateMetaclass(PyTypeObject *, PyObject *);
extern PyObject* _PyType_GetDocFromInternalDoc(const char *, const char *);
extern PyObject* _PyType_GetTextSignatureFromInternalDoc(const char *, const char *, int);
extern int _PyObject_SetAttributeErrorContext(PyObject* v, PyObject* name);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
externint_PyObject_SetAttributeErrorContext(PyObject*v,PyObject*name);
externint_PyObject_SetAttributeErrorContext(PyObject*v,PyObject*name);

DinoV reacted with thumbs up emoji
PyObject *dict = lookup_tp_dict(_PyType_CAST(base));
assert(dict && PyDict_Check(dict));
res = _PyDict_GetItem_KnownHash(dict, name, hash);
if (_PyDict_GetItemRef_KnownHash((PyDictObject *)dict, name, hash, &res) < 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think thePyErr_Occurred() check below can be removed now.

DinoV reacted with thumbs up emoji
Copy link
Contributor

@colesburycolesbury left a comment

Choose a reason for hiding this comment

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

LGTM with a few minor issues:

  • We need to handle thePyDict_New() returningNULL case intype_setattro
  • Minor formatting in_PyObject_SetAttributeErrorContext
  • PyErr_Occurred() case is no longer need infind_name_in_mro

@rhettingerrhettinger removed their request for reviewMay 3, 2024 23:18
@DinoVDinoV merged commit5a1618a intopython:mainMay 6, 2024
@bedevere-bot
Copy link

⚠️⚠️⚠️ Buildbot failure⚠️⚠️⚠️

Hi! The buildbotiOS ARM64 Simulator 3.x has failed when building commit5a1618a.

What do you need to do:

  1. Don't panic.
  2. Checkthe buildbot page in the devguide if you don't know what the buildbots are or how they work.
  3. Go to the page of the buildbot that failed (https://buildbot.python.org/all/#builders/1380/builds/238) and take a look at the build logs.
  4. Check if the failure is related to this commit (5a1618a) or if it is a false positive.
  5. If the failure is related to this commit, please, reflect that on the issue and make a new Pull Request with a fix.

You can take a look at the buildbot page here:

https://buildbot.python.org/all/#builders/1380/builds/238

Failed tests:

  • test_free_threading

Failed subtests:

  • test_attr_cache - test.test_free_threading.test_type.TestType.test_attr_cache

Summary of the results of the build (if available):

==

Click to see traceback logs
Traceback (most recent call last):  File"/Users/buildbot/Library/Developer/XCTestDevices/2E72F7FC-E4ED-40AC-B022-F9C50E4B0A1E/data/Containers/Bundle/Application/4CB60694-FBAA-49BE-969E-073C108C659C/iOSTestbed.app/python/lib/python3.13/test/test_free_threading/test_type.py", line37, intest_attr_cachewith Pool(NTHREADS)as pool:~~~~^^^^^^^^^^  File"/Users/buildbot/Library/Developer/XCTestDevices/2E72F7FC-E4ED-40AC-B022-F9C50E4B0A1E/data/Containers/Bundle/Application/4CB60694-FBAA-49BE-969E-073C108C659C/iOSTestbed.app/python/lib/python3.13/multiprocessing/dummy/__init__.py", line124, inPoolreturn ThreadPool(processes, initializer, initargs)  File"/Users/buildbot/Library/Developer/XCTestDevices/2E72F7FC-E4ED-40AC-B022-F9C50E4B0A1E/data/Containers/Bundle/Application/4CB60694-FBAA-49BE-969E-073C108C659C/iOSTestbed.app/python/lib/python3.13/multiprocessing/pool.py", line930, in__init__    Pool.__init__(self, processes, initializer, initargs)~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  File"/Users/buildbot/Library/Developer/XCTestDevices/2E72F7FC-E4ED-40AC-B022-F9C50E4B0A1E/data/Containers/Bundle/Application/4CB60694-FBAA-49BE-969E-073C108C659C/iOSTestbed.app/python/lib/python3.13/multiprocessing/pool.py", line196, in__init__self._change_notifier=self._ctx.SimpleQueue()~~~~~~~~~~~~~~~~~~~~~^^  File"/Users/buildbot/Library/Developer/XCTestDevices/2E72F7FC-E4ED-40AC-B022-F9C50E4B0A1E/data/Containers/Bundle/Application/4CB60694-FBAA-49BE-969E-073C108C659C/iOSTestbed.app/python/lib/python3.13/multiprocessing/context.py", line113, inSimpleQueuereturn SimpleQueue(ctx=self.get_context())  File"/Users/buildbot/Library/Developer/XCTestDevices/2E72F7FC-E4ED-40AC-B022-F9C50E4B0A1E/data/Containers/Bundle/Application/4CB60694-FBAA-49BE-969E-073C108C659C/iOSTestbed.app/python/lib/python3.13/multiprocessing/queues.py", line361, in__init__self._rlock= ctx.Lock()~~~~~~~~^^  File"/Users/buildbot/Library/Developer/XCTestDevices/2E72F7FC-E4ED-40AC-B022-F9C50E4B0A1E/data/Containers/Bundle/Application/4CB60694-FBAA-49BE-969E-073C108C659C/iOSTestbed.app/python/lib/python3.13/multiprocessing/context.py", line67, inLockfrom .synchronizeimport Lock  File"/Users/buildbot/Library/Developer/XCTestDevices/2E72F7FC-E4ED-40AC-B022-F9C50E4B0A1E/data/Containers/Bundle/Application/4CB60694-FBAA-49BE-969E-073C108C659C/iOSTestbed.app/python/lib/python3.13/multiprocessing/synchronize.py", line17, in<module>import _multiprocessingModuleNotFoundError:No module named '_multiprocessing'

class A:
attr = 1

@unittest.skipIf(is_wasi, "WASI has no threads.")
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, probably better to use@threading_helper.requires_working_threading()

Copy link
Member

Choose a reason for hiding this comment

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

iOS and Android do both have working threading, but they don't support subprocesses. In theorymultiprocessing.dummy.Pool should work on these platforms, but in practice it doesn't because it imports too much of the main multiprocessing module.

The simplest solution is probably to useconcurrent.futures.ThreadPoolExecutor instead.

colesbury and DinoV reacted with thumbs up emoji
SonicField pushed a commit to SonicField/cpython that referenced this pull requestMay 8, 2024
… in the face of concurrent mutators (python#118454)Add _PyType_LookupRef and use incref before setting attribute on typeMakes setting an attribute on a class and signaling type modified atomicAvoid adding re-entrancy exposing the type cache in an inconsistent state by decrefing after type is updated
@DinoVDinoV deleted the nogil_type_crash branchMay 31, 2024 18:22
@filmorfilmor mentioned this pull requestDec 7, 2025
3 tasks
filmor added a commit to pythonnet/pythonnet that referenced this pull requestDec 7, 2025
Python 3.14 introduced a new assertion that prevents us from usingPyObject_GenericSetAttr directly in our meta type. To work aroundthis, we manipulate the type dict directly.This workaround is a simplified variant of Cython's workaround fromcython/cython#6325.The relevant Python change is inpython/cpython#118454
filmor added a commit to pythonnet/pythonnet that referenced this pull requestDec 8, 2025
Python 3.14 introduced a new assertion that prevents us from usingPyObject_GenericSetAttr directly in our meta type. To work aroundthis, we manipulate the type dict directly.This workaround is a simplified variant of Cython's workaround fromcython/cython#6325.The relevant Python change is inpython/cpython#118454
filmor added a commit to pythonnet/pythonnet that referenced this pull requestDec 8, 2025
Python 3.14 introduced a new assertion that prevents us from usingPyObject_GenericSetAttr directly in our meta type. To work aroundthis, we manipulate the type dict directly.This workaround is a simplified variant of Cython's workaround fromcython/cython#6325.The relevant Python change is inpython/cpython#118454
filmor added a commit to pythonnet/pythonnet that referenced this pull requestDec 8, 2025
Python 3.14 introduced a new assertion that prevents us from usingPyObject_GenericSetAttr directly in our meta type. To work aroundthis, we manipulate the type dict directly.This workaround is a simplified variant of Cython's workaround fromcython/cython#6325.The relevant Python change is inpython/cpython#118454
filmor added a commit to pythonnet/pythonnet that referenced this pull requestDec 8, 2025
Python 3.14 introduced a new assertion that prevents us from usingPyObject_GenericSetAttr directly in our meta type. To work aroundthis, we manipulate the type dict directly.This workaround is a simplified variant of Cython's workaround fromcython/cython#6325.The relevant Python change is inpython/cpython#118454
filmor added a commit to pythonnet/pythonnet that referenced this pull requestDec 9, 2025
Python 3.14 introduced a new assertion that prevents us from usingPyObject_GenericSetAttr directly in our meta type. To work aroundthis, we manipulate the type dict directly.This workaround is a simplified variant of Cython's workaround fromcython/cython#6325.The relevant Python change is inpython/cpython#118454
filmor added a commit to pythonnet/pythonnet that referenced this pull requestFeb 24, 2026
Python 3.14 introduced a new assertion that prevents us from usingPyObject_GenericSetAttr directly in our meta type. To work aroundthis, we manipulate the type dict directly.This workaround is a simplified variant of Cython's workaround fromcython/cython#6325.The relevant Python change is inpython/cpython#118454
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@mhsmithmhsmithmhsmith left review comments

@colesburycolesburycolesbury approved these changes

@markshannonmarkshannonAwaiting requested review from markshannonmarkshannon is a code owner

@methanemethaneAwaiting requested review from methanemethane is a code owner

Assignees

No one assigned

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

4 participants

@DinoV@bedevere-bot@mhsmith@colesbury

[8]ページ先頭

©2009-2026 Movatter.jp