| #include "CPy.h" |
| |
| struct CPyModuleLockAPI { |
| PyObject *get_module_lock; |
| PyObject *deadlock_error; |
| }; |
| |
| CPyModuleLockAPI *CPyModuleLockAPI_Alloc(void) { |
| CPyModuleLockAPI *api = PyMem_Calloc(1, sizeof(CPyModuleLockAPI)); |
| if (api == NULL) { |
| PyErr_NoMemory(); |
| return NULL; |
| } |
| |
| PyObject *bootstrap = PyImport_ImportModule("importlib._bootstrap"); |
| if (bootstrap == NULL) { |
| PyMem_Free(api); |
| return NULL; |
| } |
| api->get_module_lock = PyObject_GetAttrString(bootstrap, "_get_module_lock"); |
| api->deadlock_error = PyObject_GetAttrString(bootstrap, "_DeadlockError"); |
| Py_DECREF(bootstrap); |
| if (api->get_module_lock == NULL || api->deadlock_error == NULL) { |
| CPyModuleLockAPI_Free(api); |
| return NULL; |
| } |
| return api; |
| } |
| |
| void CPyModuleLockAPI_Free(CPyModuleLockAPI *api) { |
| if (api == NULL) { |
| return; |
| } |
| Py_XDECREF(api->get_module_lock); |
| Py_XDECREF(api->deadlock_error); |
| PyMem_Free(api); |
| } |
| |
| int CPyImport_AcquireLock(CPyModuleLockAPI *api, PyObject *module_name, |
| PyObject **acquired_lock) { |
| *acquired_lock = NULL; |
| if (api == NULL) { |
| return CPY_LOCK_ACQUIRED; |
| } |
| |
| PyObject *module_lock = PyObject_CallOneArg(api->get_module_lock, module_name); |
| if (module_lock == NULL) { |
| return CPY_LOCK_ERROR; |
| } |
| PyObject *result = PyObject_CallMethod(module_lock, "acquire", NULL); |
| if (result == NULL) { |
| if (PyErr_ExceptionMatches(api->deadlock_error)) { |
| PyErr_Clear(); |
| Py_DECREF(module_lock); |
| return CPY_LOCK_DEADLOCK; |
| } |
| Py_DECREF(module_lock); |
| return CPY_LOCK_ERROR; |
| } |
| Py_DECREF(result); |
| *acquired_lock = module_lock; |
| return CPY_LOCK_ACQUIRED; |
| } |
| |
| int CPyImport_ReleaseLock(PyObject *module_lock) { |
| if (module_lock == NULL) { |
| return 0; |
| } |
| |
| PyObject *result = PyObject_CallMethod(module_lock, "release", NULL); |
| Py_DECREF(module_lock); |
| if (result == NULL) { |
| return -1; |
| } |
| Py_DECREF(result); |
| return 0; |
| } |
| |
| // CPython exposes pyatomic.h through Python.h starting in 3.13. Older supported |
| // versions are GIL-only, so plain accesses provide the same serialization there. |
| bool CPyImport_IsInitialized(const CPyImportState *state) { |
| #if PY_VERSION_HEX >= 0x030D0000 |
| return _Py_atomic_load_int32(&state->initialized) != 0; |
| #else |
| return state->initialized != 0; |
| #endif |
| } |
| |
| void CPyImport_SetInitialized(CPyImportState *state, bool initialized) { |
| #if PY_VERSION_HEX >= 0x030D0000 |
| _Py_atomic_store_int32(&state->initialized, initialized); |
| #else |
| state->initialized = initialized; |
| #endif |
| } |
| |
| bool CPyImport_IsExecuted(const CPyImportState *state) { |
| #if PY_VERSION_HEX >= 0x030D0000 |
| return _Py_atomic_load_int32(&state->executed) != 0; |
| #else |
| return state->executed != 0; |
| #endif |
| } |
| |
| void CPyImport_SetExecuted(CPyImportState *state) { |
| #if PY_VERSION_HEX >= 0x030D0000 |
| _Py_atomic_store_int32(&state->executed, true); |
| #else |
| state->executed = true; |
| #endif |
| } |
| |
| static PyObject *CPyImport_DecodeModuleCache(CPyModuleCache cached) { |
| return (PyObject *)(cached & ~CPY_MODULE_CACHE_UNVERIFIED); |
| } |
| |
| static CPyModuleCache CPyImport_EncodeModuleCache(PyObject *module, bool unverified) { |
| CPyModuleCache cached = (CPyModuleCache)module; |
| assert((cached & CPY_MODULE_CACHE_UNVERIFIED) == 0); |
| return cached | (unverified ? CPY_MODULE_CACHE_UNVERIFIED : 0); |
| } |
| |
| static void CPyImport_DecRefOld(PyObject *previous) { |
| if (previous == NULL) { |
| return; |
| } |
| #ifdef Py_GIL_DISABLED |
| // Atomic loads return borrowed references, so defer releasing the old |
| // reference until concurrent readers have passed a quiescent point. |
| if (!_Py_IsImmortal(previous)) { |
| _PyObject_XDecRefDelayed(previous); |
| } |
| #else |
| Py_DECREF(previous); |
| #endif |
| } |
| |
| bool CPyImport_IsModuleInitializing(PyObject *module) { |
| PyObject *spec = PyObject_GetAttrString(module, "__spec__"); |
| if (spec == NULL) { |
| PyErr_Clear(); |
| return false; |
| } |
| PyObject *initializing = PyObject_GetAttrString(spec, "_initializing"); |
| Py_DECREF(spec); |
| if (initializing == NULL) { |
| PyErr_Clear(); |
| return false; |
| } |
| bool result = Py_IsTrue(initializing); |
| Py_DECREF(initializing); |
| return result; |
| } |
| |
| PyObject *CPyImport_GetModuleCacheForImport(CPyModuleCache *cache, PyObject *module_name) { |
| for (;;) { |
| CPyModuleCache cached_value = CPyImport_LoadModuleCache(cache); |
| PyObject *cached = CPyImport_DecodeModuleCache(cached_value); |
| if (cached == Py_None) { |
| return Py_None; |
| } |
| |
| // An untagged module has already been verified. This is the steady-state |
| // fast path and avoids consulting sys.modules or the module spec again. |
| if ((cached_value & CPY_MODULE_CACHE_UNVERIFIED) == 0) { |
| return cached; |
| } |
| |
| // Generic imports may cache a partial module so compiled references after |
| // the import can use the value returned by CPython to break an import-lock |
| // deadlock. Such a module must not take the fast path on a later import. |
| // Comparing with sys.modules also rejects a partial module left behind in |
| // this cache after its initialization failed. |
| PyObject *current = PyImport_GetModule(module_name); |
| if (current == NULL) { |
| return PyErr_Occurred() ? NULL : Py_None; |
| } |
| bool valid = current == cached && !CPyImport_IsModuleInitializing(current); |
| Py_DECREF(current); |
| if (!valid) { |
| return Py_None; |
| } |
| |
| // Clearing the tag changes only the cache metadata and does not transfer |
| // a reference. Retry if another thread replaced the cache: generated code |
| // reloads the cache after this check, so only the value whose tag we |
| // successfully cleared may take the fast path. |
| #if PY_VERSION_HEX >= 0x030D0000 |
| CPyModuleCache expected = cached_value; |
| if (_Py_atomic_compare_exchange_uintptr(cache, &expected, (CPyModuleCache)cached)) { |
| return cached; |
| } |
| #else |
| if (*cache == cached_value) { |
| *cache = (CPyModuleCache)cached; |
| return cached; |
| } |
| #endif |
| } |
| } |
| |
| static void CPyImport_ReplaceModuleCacheValue(CPyModuleCache *cache, PyObject *module, |
| bool unverified) { |
| CPyModuleCache desired = CPyImport_EncodeModuleCache(module, unverified); |
| #if PY_VERSION_HEX >= 0x030D0000 |
| for (;;) { |
| CPyModuleCache previous = CPyImport_LoadModuleCache(cache); |
| if (previous == desired) { |
| return; |
| } |
| CPyModuleCache expected = previous; |
| PyObject *previous_module = CPyImport_DecodeModuleCache(previous); |
| if (previous_module == module) { |
| // Verification is monotonic for a particular module object. A |
| // thread that observed it while it was still initializing must not |
| // re-tag it after another thread has verified completion. |
| if ((previous & CPY_MODULE_CACHE_UNVERIFIED) == 0) { |
| return; |
| } |
| // Only the tag is changing, so the cache retains its existing |
| // reference to module. |
| if (_Py_atomic_compare_exchange_uintptr(cache, &expected, desired)) { |
| return; |
| } |
| } else { |
| Py_INCREF(module); |
| if (_Py_atomic_compare_exchange_uintptr(cache, &expected, desired)) { |
| if (previous_module != NULL && previous_module != Py_None) { |
| CPyImport_DecRefOld(previous_module); |
| } |
| return; |
| } |
| Py_DECREF(module); |
| } |
| } |
| #else |
| CPyModuleCache previous = *cache; |
| PyObject *previous_module = CPyImport_DecodeModuleCache(previous); |
| if (previous_module == module) { |
| if ((previous & CPY_MODULE_CACHE_UNVERIFIED) != 0) { |
| *cache = desired; |
| } |
| return; |
| } |
| Py_INCREF(module); |
| *cache = desired; |
| if (previous_module == NULL || previous_module == Py_None) { |
| return; |
| } |
| CPyImport_DecRefOld(previous_module); |
| #endif |
| } |
| |
| void CPyImport_ReplaceModuleCache(CPyModuleCache *cache, PyObject *module) { |
| CPyImport_ReplaceModuleCacheValue(cache, module, false); |
| } |
| |
| void CPyImport_ReplaceModuleCacheUnverified(CPyModuleCache *cache, PyObject *module) { |
| CPyImport_ReplaceModuleCacheValue(cache, module, true); |
| } |
| |
| void CPyImport_ReplaceModuleCacheForImport(CPyModuleCache *cache, PyObject *module) { |
| if (CPyImport_LoadModuleCache(cache) == (CPyModuleCache)module) { |
| return; |
| } |
| |
| CPyImport_ReplaceModuleCacheValue(cache, module, CPyImport_IsModuleInitializing(module)); |
| } |