mirror of https://github.com/python/cpython
gh-113743: Make the MRO cache thread-safe in free-threaded builds (#113930)
Makes _PyType_Lookup thread safe, including: Thread safety of the underlying cache. Make mutation of mro and type members thread safe Also _PyType_GetMRO and _PyType_GetBases are currently returning borrowed references which aren't safe.
This commit is contained in:
parent
e74fa294c9
commit
ae460d450a
|
@ -469,6 +469,9 @@ _Py_atomic_store_int_release(int *obj, int value);
|
||||||
static inline int
|
static inline int
|
||||||
_Py_atomic_load_int_acquire(const int *obj);
|
_Py_atomic_load_int_acquire(const int *obj);
|
||||||
|
|
||||||
|
static inline uint32_t
|
||||||
|
_Py_atomic_load_uint32_acquire(const uint32_t *obj);
|
||||||
|
|
||||||
|
|
||||||
// --- _Py_atomic_fence ------------------------------------------------------
|
// --- _Py_atomic_fence ------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -495,6 +495,9 @@ static inline int
|
||||||
_Py_atomic_load_int_acquire(const int *obj)
|
_Py_atomic_load_int_acquire(const int *obj)
|
||||||
{ return __atomic_load_n(obj, __ATOMIC_ACQUIRE); }
|
{ return __atomic_load_n(obj, __ATOMIC_ACQUIRE); }
|
||||||
|
|
||||||
|
static inline uint32_t
|
||||||
|
_Py_atomic_load_uint32_acquire(const uint32_t *obj)
|
||||||
|
{ return __atomic_load_n(obj, __ATOMIC_ACQUIRE); }
|
||||||
|
|
||||||
// --- _Py_atomic_fence ------------------------------------------------------
|
// --- _Py_atomic_fence ------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -938,6 +938,17 @@ _Py_atomic_load_int_acquire(const int *obj)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline uint32_t
|
||||||
|
_Py_atomic_load_uint32_acquire(const uint32_t *obj)
|
||||||
|
{
|
||||||
|
#if defined(_M_X64) || defined(_M_IX86)
|
||||||
|
return *(uint32_t volatile *)obj;
|
||||||
|
#elif defined(_M_ARM64)
|
||||||
|
return (int)__ldar32((uint32_t volatile *)obj);
|
||||||
|
#else
|
||||||
|
# error "no implementation of _Py_atomic_load_uint32_acquire"
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// --- _Py_atomic_fence ------------------------------------------------------
|
// --- _Py_atomic_fence ------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -870,6 +870,13 @@ _Py_atomic_load_int_acquire(const int *obj)
|
||||||
memory_order_acquire);
|
memory_order_acquire);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline uint32_t
|
||||||
|
_Py_atomic_load_uint32_acquire(const uint32_t *obj)
|
||||||
|
{
|
||||||
|
_Py_USING_STD;
|
||||||
|
return atomic_load_explicit((const _Atomic(uint32_t)*)obj,
|
||||||
|
memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- _Py_atomic_fence ------------------------------------------------------
|
// --- _Py_atomic_fence ------------------------------------------------------
|
||||||
|
|
|
@ -293,7 +293,8 @@ _PyCriticalSection_SuspendAll(PyThreadState *tstate);
|
||||||
#ifdef Py_GIL_DISABLED
|
#ifdef Py_GIL_DISABLED
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
_PyCriticalSection_AssertHeld(PyMutex *mutex) {
|
_PyCriticalSection_AssertHeld(PyMutex *mutex)
|
||||||
|
{
|
||||||
#ifdef Py_DEBUG
|
#ifdef Py_DEBUG
|
||||||
PyThreadState *tstate = _PyThreadState_GET();
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
uintptr_t prev = tstate->critical_section;
|
uintptr_t prev = tstate->critical_section;
|
||||||
|
|
|
@ -251,6 +251,39 @@ PyAPI_FUNC(void) _PyRWMutex_RUnlock(_PyRWMutex *rwmutex);
|
||||||
PyAPI_FUNC(void) _PyRWMutex_Lock(_PyRWMutex *rwmutex);
|
PyAPI_FUNC(void) _PyRWMutex_Lock(_PyRWMutex *rwmutex);
|
||||||
PyAPI_FUNC(void) _PyRWMutex_Unlock(_PyRWMutex *rwmutex);
|
PyAPI_FUNC(void) _PyRWMutex_Unlock(_PyRWMutex *rwmutex);
|
||||||
|
|
||||||
|
// Similar to linux seqlock: https://en.wikipedia.org/wiki/Seqlock
|
||||||
|
// We use a sequence number to lock the writer, an even sequence means we're unlocked, an odd
|
||||||
|
// sequence means we're locked. Readers will read the sequence before attempting to read the
|
||||||
|
// underlying data and then read the sequence number again after reading the data. If the
|
||||||
|
// sequence has not changed the data is valid.
|
||||||
|
//
|
||||||
|
// Differs a little bit in that we use CAS on sequence as the lock, instead of a seperate spin lock.
|
||||||
|
// The writer can also detect that the undelering data has not changed and abandon the write
|
||||||
|
// and restore the previous sequence.
|
||||||
|
typedef struct {
|
||||||
|
uint32_t sequence;
|
||||||
|
} _PySeqLock;
|
||||||
|
|
||||||
|
// Lock the sequence lock for the writer
|
||||||
|
PyAPI_FUNC(void) _PySeqLock_LockWrite(_PySeqLock *seqlock);
|
||||||
|
|
||||||
|
// Unlock the sequence lock and move to the next sequence number.
|
||||||
|
PyAPI_FUNC(void) _PySeqLock_UnlockWrite(_PySeqLock *seqlock);
|
||||||
|
|
||||||
|
// Abandon the current update indicating that no mutations have occured
|
||||||
|
// and restore the previous sequence value.
|
||||||
|
PyAPI_FUNC(void) _PySeqLock_AbandonWrite(_PySeqLock *seqlock);
|
||||||
|
|
||||||
|
// Begin a read operation and return the current sequence number.
|
||||||
|
PyAPI_FUNC(uint32_t) _PySeqLock_BeginRead(_PySeqLock *seqlock);
|
||||||
|
|
||||||
|
// End the read operation and confirm that the sequence number has not changed.
|
||||||
|
// Returns 1 if the read was successful or 0 if the read should be re-tried.
|
||||||
|
PyAPI_FUNC(uint32_t) _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous);
|
||||||
|
|
||||||
|
// Check if the lock was held during a fork and clear the lock. Returns 1
|
||||||
|
// if the lock was held and any associated datat should be cleared.
|
||||||
|
PyAPI_FUNC(uint32_t) _PySeqLock_AfterFork(_PySeqLock *seqlock);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "pycore_moduleobject.h" // PyModuleObject
|
#include "pycore_moduleobject.h" // PyModuleObject
|
||||||
|
#include "pycore_lock.h" // PyMutex
|
||||||
|
|
||||||
|
|
||||||
/* state */
|
/* state */
|
||||||
|
@ -21,6 +22,7 @@ struct _types_runtime_state {
|
||||||
// bpo-42745: next_version_tag remains shared by all interpreters
|
// bpo-42745: next_version_tag remains shared by all interpreters
|
||||||
// because of static types.
|
// because of static types.
|
||||||
unsigned int next_version_tag;
|
unsigned int next_version_tag;
|
||||||
|
PyMutex type_mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,6 +30,9 @@ struct _types_runtime_state {
|
||||||
// see _PyType_Lookup().
|
// see _PyType_Lookup().
|
||||||
struct type_cache_entry {
|
struct type_cache_entry {
|
||||||
unsigned int version; // initialized from type->tp_version_tag
|
unsigned int version; // initialized from type->tp_version_tag
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
_PySeqLock sequence;
|
||||||
|
#endif
|
||||||
PyObject *name; // reference to exactly a str or None
|
PyObject *name; // reference to exactly a str or None
|
||||||
PyObject *value; // borrowed reference or NULL
|
PyObject *value; // borrowed reference or NULL
|
||||||
};
|
};
|
||||||
|
@ -74,7 +79,7 @@ struct types_state {
|
||||||
extern PyStatus _PyTypes_InitTypes(PyInterpreterState *);
|
extern PyStatus _PyTypes_InitTypes(PyInterpreterState *);
|
||||||
extern void _PyTypes_FiniTypes(PyInterpreterState *);
|
extern void _PyTypes_FiniTypes(PyInterpreterState *);
|
||||||
extern void _PyTypes_Fini(PyInterpreterState *);
|
extern void _PyTypes_Fini(PyInterpreterState *);
|
||||||
|
extern void _PyTypes_AfterFork(void);
|
||||||
|
|
||||||
/* other API */
|
/* other API */
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
#include "pycore_object.h" // _PyType_GetSubclasses()
|
#include "pycore_object.h" // _PyType_GetSubclasses()
|
||||||
#include "pycore_runtime.h" // _Py_ID()
|
#include "pycore_runtime.h" // _Py_ID()
|
||||||
#include "pycore_setobject.h" // _PySet_NextEntry()
|
#include "pycore_setobject.h" // _PySet_NextEntry()
|
||||||
#include "pycore_typeobject.h" // _PyType_GetMRO()
|
|
||||||
#include "pycore_weakref.h" // _PyWeakref_GET_REF()
|
#include "pycore_weakref.h" // _PyWeakref_GET_REF()
|
||||||
#include "clinic/_abc.c.h"
|
#include "clinic/_abc.c.h"
|
||||||
|
|
||||||
|
@ -744,18 +743,12 @@ _abc__abc_subclasscheck_impl(PyObject *module, PyObject *self,
|
||||||
Py_DECREF(ok);
|
Py_DECREF(ok);
|
||||||
|
|
||||||
/* 4. Check if it's a direct subclass. */
|
/* 4. Check if it's a direct subclass. */
|
||||||
PyObject *mro = _PyType_GetMRO((PyTypeObject *)subclass);
|
if (PyType_IsSubtype((PyTypeObject *)subclass, (PyTypeObject *)self)) {
|
||||||
assert(PyTuple_Check(mro));
|
if (_add_to_weak_set(&impl->_abc_cache, subclass) < 0) {
|
||||||
for (pos = 0; pos < PyTuple_GET_SIZE(mro); pos++) {
|
|
||||||
PyObject *mro_item = PyTuple_GET_ITEM(mro, pos);
|
|
||||||
assert(mro_item != NULL);
|
|
||||||
if ((PyObject *)self == mro_item) {
|
|
||||||
if (_add_to_weak_set(&impl->_abc_cache, subclass) < 0) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
result = Py_True;
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
result = Py_True;
|
||||||
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 5. Check if it's a subclass of a registered class (recursive). */
|
/* 5. Check if it's a subclass of a registered class (recursive). */
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "pycore_code.h" // CO_FAST_FREE
|
#include "pycore_code.h" // CO_FAST_FREE
|
||||||
#include "pycore_dict.h" // _PyDict_KeysSize()
|
#include "pycore_dict.h" // _PyDict_KeysSize()
|
||||||
#include "pycore_frame.h" // _PyInterpreterFrame
|
#include "pycore_frame.h" // _PyInterpreterFrame
|
||||||
|
#include "pycore_lock.h" // _PySeqLock_*
|
||||||
#include "pycore_long.h" // _PyLong_IsNegative()
|
#include "pycore_long.h" // _PyLong_IsNegative()
|
||||||
#include "pycore_memoryobject.h" // _PyMemoryView_FromBufferProc()
|
#include "pycore_memoryobject.h" // _PyMemoryView_FromBufferProc()
|
||||||
#include "pycore_modsupport.h" // _PyArg_NoKwnames()
|
#include "pycore_modsupport.h" // _PyArg_NoKwnames()
|
||||||
|
@ -52,6 +53,34 @@ class object "PyObject *" "&PyBaseObject_Type"
|
||||||
#define NEXT_VERSION_TAG(interp) \
|
#define NEXT_VERSION_TAG(interp) \
|
||||||
(interp)->types.next_version_tag
|
(interp)->types.next_version_tag
|
||||||
|
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
|
||||||
|
// There's a global lock for mutation of types. This avoids having to take
|
||||||
|
// additonal locks while doing various subclass processing which may result
|
||||||
|
// in odd behaviors w.r.t. running with the GIL as the outer type lock could
|
||||||
|
// be released and reacquired during a subclass update if there's contention
|
||||||
|
// on the subclass lock.
|
||||||
|
#define BEGIN_TYPE_LOCK() \
|
||||||
|
{ \
|
||||||
|
_PyCriticalSection _cs; \
|
||||||
|
_PyCriticalSection_Begin(&_cs, &_PyRuntime.types.type_mutex); \
|
||||||
|
|
||||||
|
#define END_TYPE_LOCK() \
|
||||||
|
_PyCriticalSection_End(&_cs); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ASSERT_TYPE_LOCK_HELD() \
|
||||||
|
_Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(&_PyRuntime.types.type_mutex)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define BEGIN_TYPE_LOCK()
|
||||||
|
#define END_TYPE_LOCK()
|
||||||
|
#define ASSERT_TYPE_LOCK_HELD()
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
typedef struct PySlot_Offset {
|
typedef struct PySlot_Offset {
|
||||||
short subslot_offset;
|
short subslot_offset;
|
||||||
short slot_offset;
|
short slot_offset;
|
||||||
|
@ -279,8 +308,14 @@ lookup_tp_bases(PyTypeObject *self)
|
||||||
PyObject *
|
PyObject *
|
||||||
_PyType_GetBases(PyTypeObject *self)
|
_PyType_GetBases(PyTypeObject *self)
|
||||||
{
|
{
|
||||||
/* It returns a borrowed reference. */
|
PyObject *res;
|
||||||
return lookup_tp_bases(self);
|
|
||||||
|
BEGIN_TYPE_LOCK();
|
||||||
|
res = lookup_tp_bases(self);
|
||||||
|
Py_INCREF(res);
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
|
@ -330,14 +365,19 @@ clear_tp_bases(PyTypeObject *self)
|
||||||
static inline PyObject *
|
static inline PyObject *
|
||||||
lookup_tp_mro(PyTypeObject *self)
|
lookup_tp_mro(PyTypeObject *self)
|
||||||
{
|
{
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
return self->tp_mro;
|
return self->tp_mro;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
_PyType_GetMRO(PyTypeObject *self)
|
_PyType_GetMRO(PyTypeObject *self)
|
||||||
{
|
{
|
||||||
/* It returns a borrowed reference. */
|
PyObject *mro;
|
||||||
return lookup_tp_mro(self);
|
BEGIN_TYPE_LOCK();
|
||||||
|
mro = lookup_tp_mro(self);
|
||||||
|
Py_INCREF(mro);
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
return mro;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
|
@ -646,9 +686,15 @@ type_cache_clear(struct type_cache *cache, PyObject *value)
|
||||||
{
|
{
|
||||||
for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) {
|
for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) {
|
||||||
struct type_cache_entry *entry = &cache->hashtable[i];
|
struct type_cache_entry *entry = &cache->hashtable[i];
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
_PySeqLock_LockWrite(&entry->sequence);
|
||||||
|
#endif
|
||||||
entry->version = 0;
|
entry->version = 0;
|
||||||
Py_XSETREF(entry->name, _Py_XNewRef(value));
|
Py_XSETREF(entry->name, _Py_XNewRef(value));
|
||||||
entry->value = NULL;
|
entry->value = NULL;
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
_PySeqLock_UnlockWrite(&entry->sequence);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -760,8 +806,10 @@ PyType_Watch(int watcher_id, PyObject* obj)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
// ensure we will get a callback on the next modification
|
// ensure we will get a callback on the next modification
|
||||||
|
BEGIN_TYPE_LOCK()
|
||||||
assign_version_tag(interp, type);
|
assign_version_tag(interp, type);
|
||||||
type->tp_watched |= (1 << watcher_id);
|
type->tp_watched |= (1 << watcher_id);
|
||||||
|
END_TYPE_LOCK()
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -781,8 +829,8 @@ PyType_Unwatch(int watcher_id, PyObject* obj)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
PyType_Modified(PyTypeObject *type)
|
type_modified_unlocked(PyTypeObject *type)
|
||||||
{
|
{
|
||||||
/* Invalidate any cached data for the specified type and all
|
/* Invalidate any cached data for the specified type and all
|
||||||
subclasses. This function is called after the base
|
subclasses. This function is called after the base
|
||||||
|
@ -848,6 +896,22 @@ PyType_Modified(PyTypeObject *type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PyType_Modified(PyTypeObject *type)
|
||||||
|
{
|
||||||
|
// Quick check without the lock held
|
||||||
|
if (!_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN_TYPE_LOCK()
|
||||||
|
type_modified_unlocked(type);
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
is_subtype_unlocked(PyTypeObject *a, PyTypeObject *b);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
type_mro_modified(PyTypeObject *type, PyObject *bases) {
|
type_mro_modified(PyTypeObject *type, PyObject *bases) {
|
||||||
/*
|
/*
|
||||||
|
@ -866,6 +930,7 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
|
||||||
int custom = !Py_IS_TYPE(type, &PyType_Type);
|
int custom = !Py_IS_TYPE(type, &PyType_Type);
|
||||||
int unbound;
|
int unbound;
|
||||||
|
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
if (custom) {
|
if (custom) {
|
||||||
PyObject *mro_meth, *type_mro_meth;
|
PyObject *mro_meth, *type_mro_meth;
|
||||||
mro_meth = lookup_maybe_method(
|
mro_meth = lookup_maybe_method(
|
||||||
|
@ -891,7 +956,7 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
|
||||||
PyObject *b = PyTuple_GET_ITEM(bases, i);
|
PyObject *b = PyTuple_GET_ITEM(bases, i);
|
||||||
PyTypeObject *cls = _PyType_CAST(b);
|
PyTypeObject *cls = _PyType_CAST(b);
|
||||||
|
|
||||||
if (!PyType_IsSubtype(type, cls)) {
|
if (!is_subtype_unlocked(type, cls)) {
|
||||||
goto clear;
|
goto clear;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -913,6 +978,8 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
|
||||||
static int
|
static int
|
||||||
assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
|
assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
|
||||||
{
|
{
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
|
|
||||||
/* Ensure that the tp_version_tag is valid and set
|
/* Ensure that the tp_version_tag is valid and set
|
||||||
Py_TPFLAGS_VALID_VERSION_TAG. To respect the invariant, this
|
Py_TPFLAGS_VALID_VERSION_TAG. To respect the invariant, this
|
||||||
must first be done on all super classes. Return 0 if this
|
must first be done on all super classes. Return 0 if this
|
||||||
|
@ -961,7 +1028,11 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
|
||||||
int PyUnstable_Type_AssignVersionTag(PyTypeObject *type)
|
int PyUnstable_Type_AssignVersionTag(PyTypeObject *type)
|
||||||
{
|
{
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
return assign_version_tag(interp, type);
|
int assigned;
|
||||||
|
BEGIN_TYPE_LOCK()
|
||||||
|
assigned = assign_version_tag(interp, type);
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
return assigned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1183,21 +1254,28 @@ type_set_abstractmethods(PyTypeObject *type, PyObject *value, void *context)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
type_get_bases(PyTypeObject *type, void *context)
|
type_get_bases(PyTypeObject *type, void *context)
|
||||||
{
|
{
|
||||||
PyObject *bases = lookup_tp_bases(type);
|
PyObject *bases = _PyType_GetBases(type);
|
||||||
if (bases == NULL) {
|
if (bases == NULL) {
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
return Py_NewRef(bases);
|
return bases;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
type_get_mro(PyTypeObject *type, void *context)
|
type_get_mro(PyTypeObject *type, void *context)
|
||||||
{
|
{
|
||||||
PyObject *mro = lookup_tp_mro(type);
|
PyObject *mro;
|
||||||
|
|
||||||
|
BEGIN_TYPE_LOCK()
|
||||||
|
mro = lookup_tp_mro(type);
|
||||||
if (mro == NULL) {
|
if (mro == NULL) {
|
||||||
Py_RETURN_NONE;
|
mro = Py_None;
|
||||||
|
} else {
|
||||||
|
Py_INCREF(mro);
|
||||||
}
|
}
|
||||||
return Py_NewRef(mro);
|
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
return mro;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyTypeObject *best_base(PyObject *);
|
static PyTypeObject *best_base(PyObject *);
|
||||||
|
@ -1219,6 +1297,8 @@ static int recurse_down_subclasses(PyTypeObject *type, PyObject *name,
|
||||||
static int
|
static int
|
||||||
mro_hierarchy(PyTypeObject *type, PyObject *temp)
|
mro_hierarchy(PyTypeObject *type, PyObject *temp)
|
||||||
{
|
{
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
|
|
||||||
PyObject *old_mro;
|
PyObject *old_mro;
|
||||||
int res = mro_internal(type, &old_mro);
|
int res = mro_internal(type, &old_mro);
|
||||||
if (res <= 0) {
|
if (res <= 0) {
|
||||||
|
@ -1282,7 +1362,7 @@ mro_hierarchy(PyTypeObject *type, PyObject *temp)
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context)
|
type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, void *context)
|
||||||
{
|
{
|
||||||
// Check arguments
|
// Check arguments
|
||||||
if (!check_set_special_type_attr(type, new_bases, "__bases__")) {
|
if (!check_set_special_type_attr(type, new_bases, "__bases__")) {
|
||||||
|
@ -1313,7 +1393,7 @@ type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context)
|
||||||
}
|
}
|
||||||
PyTypeObject *base = (PyTypeObject*)ob;
|
PyTypeObject *base = (PyTypeObject*)ob;
|
||||||
|
|
||||||
if (PyType_IsSubtype(base, type) ||
|
if (is_subtype_unlocked(base, type) ||
|
||||||
/* In case of reentering here again through a custom mro()
|
/* In case of reentering here again through a custom mro()
|
||||||
the above check is not enough since it relies on
|
the above check is not enough since it relies on
|
||||||
base->tp_mro which would gonna be updated inside
|
base->tp_mro which would gonna be updated inside
|
||||||
|
@ -1418,6 +1498,16 @@ type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
BEGIN_TYPE_LOCK();
|
||||||
|
res = type_set_bases_unlocked(type, new_bases, context);
|
||||||
|
END_TYPE_LOCK();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
type_dict(PyTypeObject *type, void *context)
|
type_dict(PyTypeObject *type, void *context)
|
||||||
{
|
{
|
||||||
|
@ -2156,11 +2246,12 @@ type_is_subtype_base_chain(PyTypeObject *a, PyTypeObject *b)
|
||||||
return (b == &PyBaseObject_Type);
|
return (b == &PyBaseObject_Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
static int
|
||||||
PyType_IsSubtype(PyTypeObject *a, PyTypeObject *b)
|
is_subtype_unlocked(PyTypeObject *a, PyTypeObject *b)
|
||||||
{
|
{
|
||||||
PyObject *mro;
|
PyObject *mro;
|
||||||
|
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
mro = lookup_tp_mro(a);
|
mro = lookup_tp_mro(a);
|
||||||
if (mro != NULL) {
|
if (mro != NULL) {
|
||||||
/* Deal with multiple inheritance without recursion
|
/* Deal with multiple inheritance without recursion
|
||||||
|
@ -2179,6 +2270,16 @@ PyType_IsSubtype(PyTypeObject *a, PyTypeObject *b)
|
||||||
return type_is_subtype_base_chain(a, b);
|
return type_is_subtype_base_chain(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
PyType_IsSubtype(PyTypeObject *a, PyTypeObject *b)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
BEGIN_TYPE_LOCK();
|
||||||
|
res = is_subtype_unlocked(a, b);
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/* Routines to do a method lookup in the type without looking in the
|
/* Routines to do a method lookup in the type without looking in the
|
||||||
instance dictionary (so we can't use PyObject_GetAttr) but still
|
instance dictionary (so we can't use PyObject_GetAttr) but still
|
||||||
binding it to the instance.
|
binding it to the instance.
|
||||||
|
@ -2538,8 +2639,10 @@ pmerge(PyObject *acc, PyObject **to_merge, Py_ssize_t to_merge_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
mro_implementation(PyTypeObject *type)
|
mro_implementation_unlocked(PyTypeObject *type)
|
||||||
{
|
{
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
|
|
||||||
if (!_PyType_IsReady(type)) {
|
if (!_PyType_IsReady(type)) {
|
||||||
if (PyType_Ready(type) < 0)
|
if (PyType_Ready(type) < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -2619,6 +2722,16 @@ mro_implementation(PyTypeObject *type)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
mro_implementation(PyTypeObject *type)
|
||||||
|
{
|
||||||
|
PyObject *mro;
|
||||||
|
BEGIN_TYPE_LOCK()
|
||||||
|
mro = mro_implementation_unlocked(type);
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
return mro;
|
||||||
|
}
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
type.mro
|
type.mro
|
||||||
|
|
||||||
|
@ -2657,7 +2770,7 @@ mro_check(PyTypeObject *type, PyObject *mro)
|
||||||
}
|
}
|
||||||
PyTypeObject *base = (PyTypeObject*)obj;
|
PyTypeObject *base = (PyTypeObject*)obj;
|
||||||
|
|
||||||
if (!PyType_IsSubtype(solid, solid_base(base))) {
|
if (!is_subtype_unlocked(solid, solid_base(base))) {
|
||||||
PyErr_Format(
|
PyErr_Format(
|
||||||
PyExc_TypeError,
|
PyExc_TypeError,
|
||||||
"mro() returned base with unsuitable layout ('%.500s')",
|
"mro() returned base with unsuitable layout ('%.500s')",
|
||||||
|
@ -2688,6 +2801,9 @@ mro_invoke(PyTypeObject *type)
|
||||||
{
|
{
|
||||||
PyObject *mro_result;
|
PyObject *mro_result;
|
||||||
PyObject *new_mro;
|
PyObject *new_mro;
|
||||||
|
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
|
|
||||||
const int custom = !Py_IS_TYPE(type, &PyType_Type);
|
const int custom = !Py_IS_TYPE(type, &PyType_Type);
|
||||||
|
|
||||||
if (custom) {
|
if (custom) {
|
||||||
|
@ -2700,7 +2816,7 @@ mro_invoke(PyTypeObject *type)
|
||||||
Py_DECREF(mro_meth);
|
Py_DECREF(mro_meth);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mro_result = mro_implementation(type);
|
mro_result = mro_implementation_unlocked(type);
|
||||||
}
|
}
|
||||||
if (mro_result == NULL)
|
if (mro_result == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -2747,8 +2863,10 @@ mro_invoke(PyTypeObject *type)
|
||||||
- Returns -1 in case of an error.
|
- Returns -1 in case of an error.
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
mro_internal(PyTypeObject *type, PyObject **p_old_mro)
|
mro_internal_unlocked(PyTypeObject *type, PyObject **p_old_mro)
|
||||||
{
|
{
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
|
|
||||||
PyObject *new_mro, *old_mro;
|
PyObject *new_mro, *old_mro;
|
||||||
int reent;
|
int reent;
|
||||||
|
|
||||||
|
@ -2793,6 +2911,16 @@ mro_internal(PyTypeObject *type, PyObject **p_old_mro)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mro_internal(PyTypeObject *type, PyObject **p_old_mro)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
BEGIN_TYPE_LOCK()
|
||||||
|
res = mro_internal_unlocked(type, p_old_mro);
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/* Calculate the best base amongst multiple base classes.
|
/* Calculate the best base amongst multiple base classes.
|
||||||
This is the first one that's on the path to the "solid base". */
|
This is the first one that's on the path to the "solid base". */
|
||||||
|
|
||||||
|
@ -4631,6 +4759,9 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
|
||||||
{
|
{
|
||||||
assert(PyType_Check(type));
|
assert(PyType_Check(type));
|
||||||
|
|
||||||
|
PyObject *res = NULL;
|
||||||
|
BEGIN_TYPE_LOCK()
|
||||||
|
|
||||||
PyObject *mro = lookup_tp_mro(type);
|
PyObject *mro = lookup_tp_mro(type);
|
||||||
// The type must be ready
|
// The type must be ready
|
||||||
assert(mro != NULL);
|
assert(mro != NULL);
|
||||||
|
@ -4650,15 +4781,19 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
|
||||||
PyHeapTypeObject *ht = (PyHeapTypeObject*)super;
|
PyHeapTypeObject *ht = (PyHeapTypeObject*)super;
|
||||||
PyObject *module = ht->ht_module;
|
PyObject *module = ht->ht_module;
|
||||||
if (module && _PyModule_GetDef(module) == def) {
|
if (module && _PyModule_GetDef(module) == def) {
|
||||||
return module;
|
res = module;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
|
||||||
PyErr_Format(
|
if (res == NULL) {
|
||||||
PyExc_TypeError,
|
PyErr_Format(
|
||||||
"PyType_GetModuleByDef: No superclass of '%s' has the given module",
|
PyExc_TypeError,
|
||||||
type->tp_name);
|
"PyType_GetModuleByDef: No superclass of '%s' has the given module",
|
||||||
return NULL;
|
type->tp_name);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *
|
void *
|
||||||
|
@ -4696,6 +4831,8 @@ PyObject_GetItemData(PyObject *obj)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
find_name_in_mro(PyTypeObject *type, PyObject *name, int *error)
|
find_name_in_mro(PyTypeObject *type, PyObject *name, int *error)
|
||||||
{
|
{
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
|
|
||||||
Py_hash_t hash;
|
Py_hash_t hash;
|
||||||
if (!PyUnicode_CheckExact(name) ||
|
if (!PyUnicode_CheckExact(name) ||
|
||||||
(hash = _PyASCIIObject_CAST(name)->hash) == -1)
|
(hash = _PyASCIIObject_CAST(name)->hash) == -1)
|
||||||
|
@ -4765,6 +4902,62 @@ is_dunder_name(PyObject *name)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
update_cache(struct type_cache_entry *entry, PyObject *name, unsigned int version_tag, PyObject *value)
|
||||||
|
{
|
||||||
|
entry->version = version_tag;
|
||||||
|
entry->value = value; /* borrowed */
|
||||||
|
assert(_PyASCIIObject_CAST(name)->hash != -1);
|
||||||
|
OBJECT_STAT_INC_COND(type_cache_collisions, entry->name != Py_None && entry->name != name);
|
||||||
|
// We're releasing this under the lock for simplicity sake because it's always a
|
||||||
|
// exact unicode object or Py_None so it's safe to do so.
|
||||||
|
Py_SETREF(entry->name, Py_NewRef(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if Py_GIL_DISABLED
|
||||||
|
|
||||||
|
#define TYPE_CACHE_IS_UPDATING(sequence) (sequence & 0x01)
|
||||||
|
|
||||||
|
static void
|
||||||
|
update_cache_gil_disabled(struct type_cache_entry *entry, PyObject *name,
|
||||||
|
unsigned int version_tag, PyObject *value)
|
||||||
|
{
|
||||||
|
_PySeqLock_LockWrite(&entry->sequence);
|
||||||
|
|
||||||
|
// update the entry
|
||||||
|
if (entry->name == name &&
|
||||||
|
entry->value == value &&
|
||||||
|
entry->version == version_tag) {
|
||||||
|
// We raced with another update, bail and restore previous sequence.
|
||||||
|
_PySeqLock_AbandonWrite(&entry->sequence);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_cache(entry, name, version_tag, value);
|
||||||
|
|
||||||
|
// Then update sequence to the next valid value
|
||||||
|
_PySeqLock_UnlockWrite(&entry->sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void
|
||||||
|
_PyTypes_AfterFork()
|
||||||
|
{
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
struct type_cache *cache = get_type_cache();
|
||||||
|
for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) {
|
||||||
|
struct type_cache_entry *entry = &cache->hashtable[i];
|
||||||
|
if (_PySeqLock_AfterFork(&entry->sequence)) {
|
||||||
|
// Entry was in the process of updating while forking, clear it...
|
||||||
|
entry->value = NULL;
|
||||||
|
Py_SETREF(entry->name, Py_None);
|
||||||
|
entry->version = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/* Internal API to look for a name through the MRO.
|
/* Internal API to look for a name through the MRO.
|
||||||
This returns a borrowed reference, and doesn't set an exception! */
|
This returns a borrowed reference, and doesn't set an exception! */
|
||||||
PyObject *
|
PyObject *
|
||||||
|
@ -4777,6 +4970,27 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
|
||||||
unsigned int h = MCACHE_HASH_METHOD(type, name);
|
unsigned int h = MCACHE_HASH_METHOD(type, name);
|
||||||
struct type_cache *cache = get_type_cache();
|
struct type_cache *cache = get_type_cache();
|
||||||
struct type_cache_entry *entry = &cache->hashtable[h];
|
struct type_cache_entry *entry = &cache->hashtable[h];
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
// synchronize-with other writing threads by doing an acquire load on the sequence
|
||||||
|
while (1) {
|
||||||
|
int sequence = _PySeqLock_BeginRead(&entry->sequence);
|
||||||
|
if (_Py_atomic_load_uint32_relaxed(&entry->version) == type->tp_version_tag &&
|
||||||
|
_Py_atomic_load_ptr_relaxed(&entry->name) == name) {
|
||||||
|
assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG));
|
||||||
|
OBJECT_STAT_INC_COND(type_cache_hits, !is_dunder_name(name));
|
||||||
|
OBJECT_STAT_INC_COND(type_cache_dunder_hits, is_dunder_name(name));
|
||||||
|
PyObject *value = _Py_atomic_load_ptr_relaxed(&entry->value);
|
||||||
|
|
||||||
|
// If the sequence is still valid then we're done
|
||||||
|
if (_PySeqLock_EndRead(&entry->sequence, sequence)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// cache miss
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
if (entry->version == type->tp_version_tag &&
|
if (entry->version == type->tp_version_tag &&
|
||||||
entry->name == name) {
|
entry->name == name) {
|
||||||
assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG));
|
assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG));
|
||||||
|
@ -4784,13 +4998,27 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
|
||||||
OBJECT_STAT_INC_COND(type_cache_dunder_hits, is_dunder_name(name));
|
OBJECT_STAT_INC_COND(type_cache_dunder_hits, is_dunder_name(name));
|
||||||
return entry->value;
|
return entry->value;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
OBJECT_STAT_INC_COND(type_cache_misses, !is_dunder_name(name));
|
OBJECT_STAT_INC_COND(type_cache_misses, !is_dunder_name(name));
|
||||||
OBJECT_STAT_INC_COND(type_cache_dunder_misses, is_dunder_name(name));
|
OBJECT_STAT_INC_COND(type_cache_dunder_misses, is_dunder_name(name));
|
||||||
|
|
||||||
/* We may end up clearing live exceptions below, so make sure it's ours. */
|
/* We may end up clearing live exceptions below, so make sure it's ours. */
|
||||||
assert(!PyErr_Occurred());
|
assert(!PyErr_Occurred());
|
||||||
|
|
||||||
|
// We need to atomically do the lookup and capture the version before
|
||||||
|
// anyone else can modify our mro or mutate the type.
|
||||||
|
|
||||||
|
int has_version = 0;
|
||||||
|
int version = 0;
|
||||||
|
BEGIN_TYPE_LOCK()
|
||||||
res = find_name_in_mro(type, name, &error);
|
res = find_name_in_mro(type, name, &error);
|
||||||
|
if (MCACHE_CACHEABLE_NAME(name)) {
|
||||||
|
has_version = assign_version_tag(interp, type);
|
||||||
|
version = type->tp_version_tag;
|
||||||
|
assert(!has_version || _PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG));
|
||||||
|
}
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
|
||||||
/* Only put NULL results into cache if there was no error. */
|
/* Only put NULL results into cache if there was no error. */
|
||||||
if (error) {
|
if (error) {
|
||||||
/* It's not ideal to clear the error condition,
|
/* It's not ideal to clear the error condition,
|
||||||
|
@ -4807,15 +5035,12 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(interp, type)) {
|
if (has_version) {
|
||||||
h = MCACHE_HASH_METHOD(type, name);
|
#if Py_GIL_DISABLED
|
||||||
struct type_cache_entry *entry = &cache->hashtable[h];
|
update_cache_gil_disabled(entry, name, version, res);
|
||||||
entry->version = type->tp_version_tag;
|
#else
|
||||||
entry->value = res; /* borrowed */
|
update_cache(entry, name, version, res);
|
||||||
assert(_PyASCIIObject_CAST(name)->hash != -1);
|
#endif
|
||||||
OBJECT_STAT_INC_COND(type_cache_collisions, entry->name != Py_None && entry->name != name);
|
|
||||||
assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG));
|
|
||||||
Py_SETREF(entry->name, Py_NewRef(name));
|
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -4978,6 +5203,8 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
|
||||||
/* Will fail in _PyObject_GenericSetAttrWithDict. */
|
/* Will fail in _PyObject_GenericSetAttrWithDict. */
|
||||||
Py_INCREF(name);
|
Py_INCREF(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BEGIN_TYPE_LOCK()
|
||||||
res = _PyObject_GenericSetAttrWithDict((PyObject *)type, name, value, NULL);
|
res = _PyObject_GenericSetAttrWithDict((PyObject *)type, name, value, NULL);
|
||||||
if (res == 0) {
|
if (res == 0) {
|
||||||
/* Clear the VALID_VERSION flag of 'type' and all its
|
/* Clear the VALID_VERSION flag of 'type' and all its
|
||||||
|
@ -4985,13 +5212,15 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
|
||||||
update_subclasses() recursion in update_slot(), but carefully:
|
update_subclasses() recursion in update_slot(), but carefully:
|
||||||
they each have their own conditions on which to stop
|
they each have their own conditions on which to stop
|
||||||
recursing into subclasses. */
|
recursing into subclasses. */
|
||||||
PyType_Modified(type);
|
type_modified_unlocked(type);
|
||||||
|
|
||||||
if (is_dunder_name(name)) {
|
if (is_dunder_name(name)) {
|
||||||
res = update_slot(type, name);
|
res = update_slot(type, name);
|
||||||
}
|
}
|
||||||
assert(_PyType_CheckConsistency(type));
|
assert(_PyType_CheckConsistency(type));
|
||||||
}
|
}
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
|
||||||
Py_DECREF(name);
|
Py_DECREF(name);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -6796,28 +7025,28 @@ inherit_special(PyTypeObject *type, PyTypeObject *base)
|
||||||
#undef COPYVAL
|
#undef COPYVAL
|
||||||
|
|
||||||
/* Setup fast subclass flags */
|
/* Setup fast subclass flags */
|
||||||
if (PyType_IsSubtype(base, (PyTypeObject*)PyExc_BaseException)) {
|
if (is_subtype_unlocked(base, (PyTypeObject*)PyExc_BaseException)) {
|
||||||
type->tp_flags |= Py_TPFLAGS_BASE_EXC_SUBCLASS;
|
type->tp_flags |= Py_TPFLAGS_BASE_EXC_SUBCLASS;
|
||||||
}
|
}
|
||||||
else if (PyType_IsSubtype(base, &PyType_Type)) {
|
else if (is_subtype_unlocked(base, &PyType_Type)) {
|
||||||
type->tp_flags |= Py_TPFLAGS_TYPE_SUBCLASS;
|
type->tp_flags |= Py_TPFLAGS_TYPE_SUBCLASS;
|
||||||
}
|
}
|
||||||
else if (PyType_IsSubtype(base, &PyLong_Type)) {
|
else if (is_subtype_unlocked(base, &PyLong_Type)) {
|
||||||
type->tp_flags |= Py_TPFLAGS_LONG_SUBCLASS;
|
type->tp_flags |= Py_TPFLAGS_LONG_SUBCLASS;
|
||||||
}
|
}
|
||||||
else if (PyType_IsSubtype(base, &PyBytes_Type)) {
|
else if (is_subtype_unlocked(base, &PyBytes_Type)) {
|
||||||
type->tp_flags |= Py_TPFLAGS_BYTES_SUBCLASS;
|
type->tp_flags |= Py_TPFLAGS_BYTES_SUBCLASS;
|
||||||
}
|
}
|
||||||
else if (PyType_IsSubtype(base, &PyUnicode_Type)) {
|
else if (is_subtype_unlocked(base, &PyUnicode_Type)) {
|
||||||
type->tp_flags |= Py_TPFLAGS_UNICODE_SUBCLASS;
|
type->tp_flags |= Py_TPFLAGS_UNICODE_SUBCLASS;
|
||||||
}
|
}
|
||||||
else if (PyType_IsSubtype(base, &PyTuple_Type)) {
|
else if (is_subtype_unlocked(base, &PyTuple_Type)) {
|
||||||
type->tp_flags |= Py_TPFLAGS_TUPLE_SUBCLASS;
|
type->tp_flags |= Py_TPFLAGS_TUPLE_SUBCLASS;
|
||||||
}
|
}
|
||||||
else if (PyType_IsSubtype(base, &PyList_Type)) {
|
else if (is_subtype_unlocked(base, &PyList_Type)) {
|
||||||
type->tp_flags |= Py_TPFLAGS_LIST_SUBCLASS;
|
type->tp_flags |= Py_TPFLAGS_LIST_SUBCLASS;
|
||||||
}
|
}
|
||||||
else if (PyType_IsSubtype(base, &PyDict_Type)) {
|
else if (is_subtype_unlocked(base, &PyDict_Type)) {
|
||||||
type->tp_flags |= Py_TPFLAGS_DICT_SUBCLASS;
|
type->tp_flags |= Py_TPFLAGS_DICT_SUBCLASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7256,6 +7485,8 @@ type_ready_preheader(PyTypeObject *type)
|
||||||
static int
|
static int
|
||||||
type_ready_mro(PyTypeObject *type)
|
type_ready_mro(PyTypeObject *type)
|
||||||
{
|
{
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
|
|
||||||
if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
||||||
if (!_Py_IsMainInterpreter(_PyInterpreterState_GET())) {
|
if (!_Py_IsMainInterpreter(_PyInterpreterState_GET())) {
|
||||||
assert(lookup_tp_mro(type) != NULL);
|
assert(lookup_tp_mro(type) != NULL);
|
||||||
|
@ -7265,7 +7496,7 @@ type_ready_mro(PyTypeObject *type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Calculate method resolution order */
|
/* Calculate method resolution order */
|
||||||
if (mro_internal(type, NULL) < 0) {
|
if (mro_internal_unlocked(type, NULL) < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
PyObject *mro = lookup_tp_mro(type);
|
PyObject *mro = lookup_tp_mro(type);
|
||||||
|
@ -7329,6 +7560,8 @@ inherit_patma_flags(PyTypeObject *type, PyTypeObject *base) {
|
||||||
static int
|
static int
|
||||||
type_ready_inherit(PyTypeObject *type)
|
type_ready_inherit(PyTypeObject *type)
|
||||||
{
|
{
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
|
|
||||||
/* Inherit special flags from dominant base */
|
/* Inherit special flags from dominant base */
|
||||||
PyTypeObject *base = type->tp_base;
|
PyTypeObject *base = type->tp_base;
|
||||||
if (base != NULL) {
|
if (base != NULL) {
|
||||||
|
@ -7521,6 +7754,8 @@ type_ready_post_checks(PyTypeObject *type)
|
||||||
static int
|
static int
|
||||||
type_ready(PyTypeObject *type, int rerunbuiltin)
|
type_ready(PyTypeObject *type, int rerunbuiltin)
|
||||||
{
|
{
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
|
|
||||||
_PyObject_ASSERT((PyObject *)type, !is_readying(type));
|
_PyObject_ASSERT((PyObject *)type, !is_readying(type));
|
||||||
start_readying(type);
|
start_readying(type);
|
||||||
|
|
||||||
|
@ -7608,7 +7843,16 @@ PyType_Ready(PyTypeObject *type)
|
||||||
type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE;
|
type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return type_ready(type, 0);
|
int res;
|
||||||
|
BEGIN_TYPE_LOCK()
|
||||||
|
if (!(type->tp_flags & Py_TPFLAGS_READY)) {
|
||||||
|
res = type_ready(type, 0);
|
||||||
|
} else {
|
||||||
|
res = 0;
|
||||||
|
assert(_PyType_CheckConsistency(type));
|
||||||
|
}
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
@ -7638,7 +7882,10 @@ _PyStaticType_InitBuiltin(PyInterpreterState *interp, PyTypeObject *self)
|
||||||
|
|
||||||
static_builtin_state_init(interp, self);
|
static_builtin_state_init(interp, self);
|
||||||
|
|
||||||
int res = type_ready(self, !ismain);
|
int res;
|
||||||
|
BEGIN_TYPE_LOCK();
|
||||||
|
res = type_ready(self, !ismain);
|
||||||
|
END_TYPE_LOCK()
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
static_builtin_state_clear(interp, self);
|
static_builtin_state_clear(interp, self);
|
||||||
}
|
}
|
||||||
|
@ -8040,9 +8287,12 @@ wrap_delitem(PyObject *self, PyObject *args, void *wrapped)
|
||||||
https://mail.python.org/pipermail/python-dev/2003-April/034535.html
|
https://mail.python.org/pipermail/python-dev/2003-April/034535.html
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
hackcheck(PyObject *self, setattrofunc func, const char *what)
|
hackcheck_unlocked(PyObject *self, setattrofunc func, const char *what)
|
||||||
{
|
{
|
||||||
PyTypeObject *type = Py_TYPE(self);
|
PyTypeObject *type = Py_TYPE(self);
|
||||||
|
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
|
|
||||||
PyObject *mro = lookup_tp_mro(type);
|
PyObject *mro = lookup_tp_mro(type);
|
||||||
if (!mro) {
|
if (!mro) {
|
||||||
/* Probably ok not to check the call in this case. */
|
/* Probably ok not to check the call in this case. */
|
||||||
|
@ -8084,6 +8334,20 @@ hackcheck(PyObject *self, setattrofunc func, const char *what)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
hackcheck(PyObject *self, setattrofunc func, const char *what)
|
||||||
|
{
|
||||||
|
if (!PyType_Check(self)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int res;
|
||||||
|
BEGIN_TYPE_LOCK();
|
||||||
|
res = hackcheck_unlocked(self, func, what);
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
wrap_setattr(PyObject *self, PyObject *args, void *wrapped)
|
wrap_setattr(PyObject *self, PyObject *args, void *wrapped)
|
||||||
{
|
{
|
||||||
|
@ -9252,13 +9516,13 @@ fail:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static releasebufferproc
|
||||||
releasebuffer_maybe_call_super(PyObject *self, Py_buffer *buffer)
|
releasebuffer_maybe_call_super_unlocked(PyObject *self, Py_buffer *buffer)
|
||||||
{
|
{
|
||||||
PyTypeObject *self_type = Py_TYPE(self);
|
PyTypeObject *self_type = Py_TYPE(self);
|
||||||
PyObject *mro = lookup_tp_mro(self_type);
|
PyObject *mro = lookup_tp_mro(self_type);
|
||||||
if (mro == NULL) {
|
if (mro == NULL) {
|
||||||
return -1;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(PyTuple_Check(mro));
|
assert(PyTuple_Check(mro));
|
||||||
|
@ -9272,9 +9536,8 @@ releasebuffer_maybe_call_super(PyObject *self, Py_buffer *buffer)
|
||||||
}
|
}
|
||||||
i++; /* skip self_type */
|
i++; /* skip self_type */
|
||||||
if (i >= n)
|
if (i >= n)
|
||||||
return -1;
|
return NULL;
|
||||||
|
|
||||||
releasebufferproc base_releasebuffer = NULL;
|
|
||||||
for (; i < n; i++) {
|
for (; i < n; i++) {
|
||||||
PyObject *obj = PyTuple_GET_ITEM(mro, i);
|
PyObject *obj = PyTuple_GET_ITEM(mro, i);
|
||||||
if (!PyType_Check(obj)) {
|
if (!PyType_Check(obj)) {
|
||||||
|
@ -9284,15 +9547,25 @@ releasebuffer_maybe_call_super(PyObject *self, Py_buffer *buffer)
|
||||||
if (base_type->tp_as_buffer != NULL
|
if (base_type->tp_as_buffer != NULL
|
||||||
&& base_type->tp_as_buffer->bf_releasebuffer != NULL
|
&& base_type->tp_as_buffer->bf_releasebuffer != NULL
|
||||||
&& base_type->tp_as_buffer->bf_releasebuffer != slot_bf_releasebuffer) {
|
&& base_type->tp_as_buffer->bf_releasebuffer != slot_bf_releasebuffer) {
|
||||||
base_releasebuffer = base_type->tp_as_buffer->bf_releasebuffer;
|
return base_type->tp_as_buffer->bf_releasebuffer;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
releasebuffer_maybe_call_super(PyObject *self, Py_buffer *buffer)
|
||||||
|
{
|
||||||
|
releasebufferproc base_releasebuffer;
|
||||||
|
|
||||||
|
BEGIN_TYPE_LOCK();
|
||||||
|
base_releasebuffer = releasebuffer_maybe_call_super_unlocked(self, buffer);
|
||||||
|
END_TYPE_LOCK();
|
||||||
|
|
||||||
if (base_releasebuffer != NULL) {
|
if (base_releasebuffer != NULL) {
|
||||||
base_releasebuffer(self, buffer);
|
base_releasebuffer(self, buffer);
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -9369,11 +9642,7 @@ static void
|
||||||
slot_bf_releasebuffer(PyObject *self, Py_buffer *buffer)
|
slot_bf_releasebuffer(PyObject *self, Py_buffer *buffer)
|
||||||
{
|
{
|
||||||
releasebuffer_call_python(self, buffer);
|
releasebuffer_call_python(self, buffer);
|
||||||
if (releasebuffer_maybe_call_super(self, buffer) < 0) {
|
releasebuffer_maybe_call_super(self, buffer);
|
||||||
if (PyErr_Occurred()) {
|
|
||||||
PyErr_WriteUnraisable(self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -9828,6 +10097,8 @@ resolve_slotdups(PyTypeObject *type, PyObject *name)
|
||||||
static pytype_slotdef *
|
static pytype_slotdef *
|
||||||
update_one_slot(PyTypeObject *type, pytype_slotdef *p)
|
update_one_slot(PyTypeObject *type, pytype_slotdef *p)
|
||||||
{
|
{
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
|
|
||||||
PyObject *descr;
|
PyObject *descr;
|
||||||
PyWrapperDescrObject *d;
|
PyWrapperDescrObject *d;
|
||||||
|
|
||||||
|
@ -9876,7 +10147,7 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p)
|
||||||
d = (PyWrapperDescrObject *)descr;
|
d = (PyWrapperDescrObject *)descr;
|
||||||
if ((specific == NULL || specific == d->d_wrapped) &&
|
if ((specific == NULL || specific == d->d_wrapped) &&
|
||||||
d->d_base->wrapper == p->wrapper &&
|
d->d_base->wrapper == p->wrapper &&
|
||||||
PyType_IsSubtype(type, PyDescr_TYPE(d)))
|
is_subtype_unlocked(type, PyDescr_TYPE(d)))
|
||||||
{
|
{
|
||||||
specific = d->d_wrapped;
|
specific = d->d_wrapped;
|
||||||
}
|
}
|
||||||
|
@ -9941,6 +10212,8 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p)
|
||||||
static int
|
static int
|
||||||
update_slots_callback(PyTypeObject *type, void *data)
|
update_slots_callback(PyTypeObject *type, void *data)
|
||||||
{
|
{
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
|
|
||||||
pytype_slotdef **pp = (pytype_slotdef **)data;
|
pytype_slotdef **pp = (pytype_slotdef **)data;
|
||||||
for (; *pp; pp++) {
|
for (; *pp; pp++) {
|
||||||
update_one_slot(type, *pp);
|
update_one_slot(type, *pp);
|
||||||
|
@ -9957,6 +10230,7 @@ update_slot(PyTypeObject *type, PyObject *name)
|
||||||
pytype_slotdef **pp;
|
pytype_slotdef **pp;
|
||||||
int offset;
|
int offset;
|
||||||
|
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
assert(PyUnicode_CheckExact(name));
|
assert(PyUnicode_CheckExact(name));
|
||||||
assert(PyUnicode_CHECK_INTERNED(name));
|
assert(PyUnicode_CHECK_INTERNED(name));
|
||||||
|
|
||||||
|
@ -9990,10 +10264,17 @@ update_slot(PyTypeObject *type, PyObject *name)
|
||||||
static void
|
static void
|
||||||
fixup_slot_dispatchers(PyTypeObject *type)
|
fixup_slot_dispatchers(PyTypeObject *type)
|
||||||
{
|
{
|
||||||
|
// This lock isn't strictly necessary because the type has not been
|
||||||
|
// exposed to anyone else yet, but update_ont_slot calls find_name_in_mro
|
||||||
|
// where we'd like to assert that the tyep is locked.
|
||||||
|
BEGIN_TYPE_LOCK()
|
||||||
|
|
||||||
assert(!PyErr_Occurred());
|
assert(!PyErr_Occurred());
|
||||||
for (pytype_slotdef *p = slotdefs; p->name; ) {
|
for (pytype_slotdef *p = slotdefs; p->name; ) {
|
||||||
p = update_one_slot(type, p);
|
p = update_one_slot(type, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
END_TYPE_LOCK()
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -10001,6 +10282,8 @@ update_all_slots(PyTypeObject* type)
|
||||||
{
|
{
|
||||||
pytype_slotdef *p;
|
pytype_slotdef *p;
|
||||||
|
|
||||||
|
ASSERT_TYPE_LOCK_HELD();
|
||||||
|
|
||||||
/* Clear the VALID_VERSION flag of 'type' and all its subclasses. */
|
/* Clear the VALID_VERSION flag of 'type' and all its subclasses. */
|
||||||
PyType_Modified(type);
|
PyType_Modified(type);
|
||||||
|
|
||||||
|
@ -10273,7 +10556,15 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject *
|
||||||
PyObject *mro, *res;
|
PyObject *mro, *res;
|
||||||
Py_ssize_t i, n;
|
Py_ssize_t i, n;
|
||||||
|
|
||||||
|
BEGIN_TYPE_LOCK();
|
||||||
mro = lookup_tp_mro(su_obj_type);
|
mro = lookup_tp_mro(su_obj_type);
|
||||||
|
/* keep a strong reference to mro because su_obj_type->tp_mro can be
|
||||||
|
replaced during PyDict_GetItemRef(dict, name, &res) and because
|
||||||
|
another thread can modify it after we end the critical section
|
||||||
|
below */
|
||||||
|
Py_XINCREF(mro);
|
||||||
|
END_TYPE_LOCK()
|
||||||
|
|
||||||
if (mro == NULL)
|
if (mro == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
@ -10286,12 +10577,11 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject *
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
i++; /* skip su->type (if any) */
|
i++; /* skip su->type (if any) */
|
||||||
if (i >= n)
|
if (i >= n) {
|
||||||
|
Py_DECREF(mro);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* keep a strong reference to mro because su_obj_type->tp_mro can be
|
|
||||||
replaced during PyDict_GetItemRef(dict, name, &res) */
|
|
||||||
Py_INCREF(mro);
|
|
||||||
do {
|
do {
|
||||||
PyObject *obj = PyTuple_GET_ITEM(mro, i);
|
PyObject *obj = PyTuple_GET_ITEM(mro, i);
|
||||||
PyObject *dict = lookup_tp_dict(_PyType_CAST(obj));
|
PyObject *dict = lookup_tp_dict(_PyType_CAST(obj));
|
||||||
|
|
|
@ -459,3 +459,74 @@ _PyRWMutex_Unlock(_PyRWMutex *rwmutex)
|
||||||
_PyParkingLot_UnparkAll(&rwmutex->bits);
|
_PyParkingLot_UnparkAll(&rwmutex->bits);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define SEQLOCK_IS_UPDATING(sequence) (sequence & 0x01)
|
||||||
|
|
||||||
|
void _PySeqLock_LockWrite(_PySeqLock *seqlock)
|
||||||
|
{
|
||||||
|
// lock the entry by setting by moving to an odd sequence number
|
||||||
|
uint32_t prev = _Py_atomic_load_uint32_relaxed(&seqlock->sequence);
|
||||||
|
while (1) {
|
||||||
|
if (SEQLOCK_IS_UPDATING(prev)) {
|
||||||
|
// Someone else is currently updating the cache
|
||||||
|
_Py_yield();
|
||||||
|
prev = _Py_atomic_load_uint32_relaxed(&seqlock->sequence);
|
||||||
|
}
|
||||||
|
else if (_Py_atomic_compare_exchange_uint32(&seqlock->sequence, &prev, prev + 1)) {
|
||||||
|
// We've locked the cache
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_Py_yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _PySeqLock_AbandonWrite(_PySeqLock *seqlock)
|
||||||
|
{
|
||||||
|
uint32_t new_seq = seqlock->sequence - 1;
|
||||||
|
assert(!SEQLOCK_IS_UPDATING(new_seq));
|
||||||
|
_Py_atomic_store_uint32(&seqlock->sequence, new_seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _PySeqLock_UnlockWrite(_PySeqLock *seqlock)
|
||||||
|
{
|
||||||
|
uint32_t new_seq = seqlock->sequence + 1;
|
||||||
|
assert(!SEQLOCK_IS_UPDATING(new_seq));
|
||||||
|
_Py_atomic_store_uint32(&seqlock->sequence, new_seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t _PySeqLock_BeginRead(_PySeqLock *seqlock)
|
||||||
|
{
|
||||||
|
uint32_t sequence = _Py_atomic_load_uint32_acquire(&seqlock->sequence);
|
||||||
|
while (SEQLOCK_IS_UPDATING(sequence)) {
|
||||||
|
_Py_yield();
|
||||||
|
sequence = _Py_atomic_load_uint32_acquire(&seqlock->sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous)
|
||||||
|
{
|
||||||
|
// Synchronize again and validate that the entry hasn't been updated
|
||||||
|
// while we were readying the values.
|
||||||
|
if (_Py_atomic_load_uint32_acquire(&seqlock->sequence) == previous) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Py_yield();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t _PySeqLock_AfterFork(_PySeqLock *seqlock)
|
||||||
|
{
|
||||||
|
// Synchronize again and validate that the entry hasn't been updated
|
||||||
|
// while we were readying the values.
|
||||||
|
if (SEQLOCK_IS_UPDATING(seqlock->sequence)) {
|
||||||
|
seqlock->sequence = 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -395,6 +395,7 @@ _Py_COMP_DIAG_POP
|
||||||
&(runtime)->atexit.mutex, \
|
&(runtime)->atexit.mutex, \
|
||||||
&(runtime)->audit_hooks.mutex, \
|
&(runtime)->audit_hooks.mutex, \
|
||||||
&(runtime)->allocators.mutex, \
|
&(runtime)->allocators.mutex, \
|
||||||
|
&(runtime)->types.type_mutex, \
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -499,6 +500,8 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
|
||||||
_PyMutex_at_fork_reinit(locks[i]);
|
_PyMutex_at_fork_reinit(locks[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_PyTypes_AfterFork();
|
||||||
|
|
||||||
/* bpo-42540: id_mutex is freed by _PyInterpreterState_Delete, which does
|
/* bpo-42540: id_mutex is freed by _PyInterpreterState_Delete, which does
|
||||||
* not force the default allocator. */
|
* not force the default allocator. */
|
||||||
if (_PyThread_at_fork_reinit(&runtime->interpreters.main->id_mutex) < 0) {
|
if (_PyThread_at_fork_reinit(&runtime->interpreters.main->id_mutex) < 0) {
|
||||||
|
|
Loading…
Reference in New Issue