GH-129817: thread safety for tp_flags by nascheme · Pull Request #130983 · python/cpython · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Include/cpython/object.h
3 changes: 2 additions & 1 deletion Include/internal/pycore_call.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extern "C" {
#endif

#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_object.h" // _PyType_HaveFeatureSafe()

/* Suggested size (number of positional arguments) for arrays of PyObject*
allocated on a C stack to avoid allocating memory on the heap memory. Such
Expand Down Expand Up @@ -116,7 +117,7 @@ _PyVectorcall_FunctionInline(PyObject *callable)
assert(callable != NULL);

PyTypeObject *tp = Py_TYPE(callable);
if (!PyType_HasFeature(tp, Py_TPFLAGS_HAVE_VECTORCALL)) {
if (!_PyType_HasFeatureSafe(tp, Py_TPFLAGS_HAVE_VECTORCALL)) {
return NULL;
}
assert(PyCallable_Check(callable));
Expand Down
15 changes: 14 additions & 1 deletion Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,20 @@ extern int _PyDict_CheckConsistency(PyObject *mp, int check_content);
// Fast inlined version of PyType_HasFeature()
static inline int
_PyType_HasFeature(PyTypeObject *type, unsigned long feature) {
return ((FT_ATOMIC_LOAD_ULONG_RELAXED(type->tp_flags) & feature) != 0);
return (type->tp_flags & feature) != 0;
}

// Variant of above function that uses safely reads type flags that can be
// toggled after the type is first created.
static inline int
_PyType_HasFeatureSafe(PyTypeObject *type, unsigned long feature) {
#ifdef Py_GIL_DISABLED
if (_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
PyHeapTypeObject *ht = (PyHeapTypeObject*)type;
return (FT_ATOMIC_LOAD_ULONG_RELAXED(ht->ht_flags) & feature) != 0;
}
#endif
return (type->tp_flags & feature) != 0;
}

extern void _PyType_InitCache(PyInterpreterState *interp);
Expand Down
6 changes: 1 addition & 5 deletions Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -774,11 +774,7 @@ PyType_HasFeature(PyTypeObject *type, unsigned long feature)
// PyTypeObject is opaque in the limited C API
flags = PyType_GetFlags(type);
#else
# ifdef Py_GIL_DISABLED
flags = _Py_atomic_load_ulong_relaxed(&type->tp_flags);
# else
flags = type->tp_flags;
# endif
flags = type->tp_flags;
#endif
return ((flags & feature) != 0);
}
Expand Down
39 changes: 39 additions & 0 deletions Lib/test/test_free_threading/test_races.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,45 @@ def mutate():
# with the cell binding being changed).
do_race(access, mutate)

def test_racing_tp_flags_is_abstract(self):
class C:
pass

def access():
obj = C()

def mutate():
C.__abstractmethods__ = set()
time.sleep(0)
del C.__abstractmethods__
time.sleep(0)

# The "mutate" method will set and clear the Py_TPFLAGS_IS_ABSTRACT type flag.
do_race(access, mutate)

def test_racing_tp_flags_vectorcall(self):
class C:
pass

def access():
try:
C()()
except Exception:
pass

def mutate():
nonlocal C
# This will clear the Py_TPFLAGS_VECTORCALL flag.
C.__call__ = lambda self: None
time.sleep(0)
# There is no way to re-set the flag it so we create a new class.
class C:
pass
time.sleep(0)

do_race(access, mutate)


def test_racing_to_bool(self):

seq = [1]
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1750,6 +1750,7 @@ def delx(self): del self.__x
s = vsize(fmt)
check(int, s)
typeid = 'n' if support.Py_GIL_DISABLED else ''
ht_flags = 'l' if support.Py_GIL_DISABLED else ''
# class
s = vsize(fmt + # PyTypeObject
'4P' # PyAsyncMethods
Expand All @@ -1760,6 +1761,7 @@ def delx(self): del self.__x
'7P'
'1PIP' # Specializer cache
+ typeid # heap type id (free-threaded only)
+ ht_flags
)
class newstyleclass(object): pass
# Separate block for PyDictKeysObject with 8 keys and 5 entries
Expand Down
3 changes: 2 additions & 1 deletion Modules/_testcapi/vectorcall.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "parts.h"
#include "clinic/vectorcall.c.h"
#include "pycore_object.h" // _PyType_HasFeatureSafe()


#include <stddef.h> // offsetof
Expand Down Expand Up @@ -255,7 +256,7 @@ static int
_testcapi_has_vectorcall_flag_impl(PyObject *module, PyTypeObject *type)
/*[clinic end generated code: output=3ae8d1374388c671 input=8eee492ac548749e]*/
{
return PyType_HasFeature(type, Py_TPFLAGS_HAVE_VECTORCALL);
return _PyType_HasFeatureSafe(type, Py_TPFLAGS_HAVE_VECTORCALL);
}

static PyMethodDef TestMethods[] = {
Expand Down
117 changes: 90 additions & 27 deletions Objects/typeobject.c
Loading