| /* Internal API to look for a name through the MRO. |
| This returns a borrowed reference, and doesn't set an exception! */ |
| PyObject* |
| _PyType_Lookup(PyTypeObject*type, PyObject*name) |
| { |
| PyObject*res; |
| interror; |
| PyInterpreterState*interp=_PyInterpreterState_GET(); |
| |
| unsigned inth=MCACHE_HASH_METHOD(type, name); |
| structtype_cache*cache=get_type_cache(); |
| structtype_cache_entry*entry=&cache->hashtable[h]; |
| if (entry->version==type->tp_version_tag&& |
| 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)); |
| returnentry->value; |
| } |
| OBJECT_STAT_INC_COND(type_cache_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. */ |
| assert(!PyErr_Occurred()); |
| |
| res=find_name_in_mro(type, name, &error); |
| /* Only put NULL results into cache if there was no error. */ |
| if (error) { |
| /* It's not ideal to clear the error condition, |
| but this function is documented as not setting |
| an exception, and I don't want to change that. |
| E.g., when PyType_Ready() can't proceed, it won't |
| set the "ready" flag, so future attempts to ready |
| the same type will call it again -- hopefully |
| in a context that propagates the exception out. |
| */ |
| if (error==-1) { |
| PyErr_Clear(); |
| } |
| returnNULL; |
| } |
| |
| if (MCACHE_CACHEABLE_NAME(name) &&assign_version_tag(interp, type)) { |
| h=MCACHE_HASH_METHOD(type, name); |
| structtype_cache_entry*entry=&cache->hashtable[h]; |
| entry->version=type->tp_version_tag; |
| entry->value=res; /* borrowed */ |
| assert(_PyASCIIObject_CAST(name)->hash!=-1); |
| 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)); |
| } |
| returnres; |
| } |
Feature or enhancement
The MRO (method resolution order) cache is used to cache lookups on the type hierarchy, such as method lookups. It's currently implemented per-interpreter, which will not be thread-safe in free-threaded builds.
cpython/Objects/typeobject.c
Lines 4750 to 4803 in 5e1916b
The nogil-3.9 and nogil-3.12 forks used different approaches to make the MRO cache thread-safe. The nogil-3.9 fork moved the type cache to the
PyThreadState. The nogil-3.12 fork implemented a thread-safe cache shared across threads 1. The nogil-3.9 approach is much simpler, I think we should start with that.Suggested approach:
Py_GIL_DISABLEDis defined, move the type_cache to the private structPyThreadStateImpl._PyType_Lookup()as necessary to use the correct cacheLinked PRs
Footnotes
For reference, here is the nogil-3.12 implementation: https://github.com/colesbury/nogil-3.12/commit/9c1f7ba1b4↩