diff --git a/Include/pystate.h b/Include/pystate.h
--- a/Include/pystate.h
+++ b/Include/pystate.h
@@ -113,6 +113,7 @@ typedef struct _ts {
PyObject *async_exc; /* Asynchronous exception to raise */
long thread_id; /* Thread id where this tstate was created */
+ PyObject *importing; /* A list of modules (full names) being imported */
/* XXX signal handlers should also be here */
} PyThreadState;
diff --git a/Python/import.c b/Python/import.c
--- a/Python/import.c
+++ b/Python/import.c
@@ -25,6 +25,368 @@ typedef unsigned short mode_t;
#include
#endif
+/* Locking primitives to prevent parallel imports of the same module
+ in different threads to return with a partially loaded module.
+ These calls are serialized by the global interpreter lock. */
+
+#ifdef WITH_THREAD
+#include "pythread.h"
+#endif
+
+struct importlock {
+#ifdef WITH_THREAD
+ PyThread_type_lock lock;
+ PyThreadState *tstate;
+ int waiters;
+#endif
+ int level;
+};
+
+static int
+init_import_lock(struct importlock *lock)
+{
+#ifdef WITH_THREAD
+ lock->lock = PyThread_allocate_lock();
+ if (lock->lock == NULL)
+ return 0; /* Nothing much we can do. */
+ lock->tstate = NULL;
+ lock->waiters = 0;
+#endif
+ lock->level = 0;
+ return 1;
+}
+
+static void
+clear_import_lock(struct importlock *lock)
+{
+#ifdef WITH_THREAD
+ if (lock->lock) {
+ PyThread_free_lock(lock->lock);
+ lock->lock = NULL;
+ }
+#endif
+}
+
+static void
+acquire_import_lock(struct importlock *lock)
+{
+#ifdef WITH_THREAD
+ PyThreadState *tstate = PyThreadState_GET();
+ assert(tstate != NULL);
+ if (lock->tstate == tstate) {
+ lock->level++;
+ return;
+ }
+ if (lock->tstate != NULL || !PyThread_acquire_lock(lock->lock, 0))
+ {
+ PyThreadState *saved;
+ lock->waiters++;
+ saved = PyEval_SaveThread();
+ PyThread_acquire_lock(lock->lock, 1);
+ PyEval_RestoreThread(saved);
+ lock->waiters--;
+ }
+ assert(lock->level == 0);
+ lock->tstate = tstate;
+ lock->level = 1;
+#else
+ lock->level++;
+#endif
+}
+
+/* 1 on success, 0 on failure (unacquired lock) */
+
+static int
+release_import_lock(struct importlock *lock)
+{
+#ifdef WITH_THREAD
+ PyThreadState *tstate = PyThreadState_GET();
+ assert(tstate != NULL);
+ if (lock->lock == NULL)
+ return 0; /* Too bad */
+ if (lock->tstate != tstate)
+ return 0;
+ lock->level--;
+ assert(lock->level >= 0);
+ if (lock->level == 0) {
+ lock->tstate = NULL;
+ PyThread_release_lock(lock->lock);
+ }
+ return 1;
+#else
+ if (lock->level <= 0)
+ return 0; /* Too bad */
+ lock->level--;
+ return 1;
+#endif
+}
+
+/* Module-specific import locks */
+
+/* Dict of module name -> lock */
+static PyObject *module_locks = NULL;
+#define IMPORT_LOCK_CAPSULE_NAME "import.c module lock"
+
+static int
+init_module_locks(void)
+{
+ Py_CLEAR(module_locks);
+ module_locks = PyDict_New();
+ return module_locks != NULL;
+}
+
+static void
+reinit_module_locks(void)
+{
+ PyObject *key, *capsule;
+ Py_ssize_t pos = 0;
+ struct importlock *lock;
+ int fix_level;
+ PyThreadState *tstate = PyThreadState_GET();
+
+ /* We can't remove dict entries while iterating: just keep it simple */
+ while (PyDict_Next(module_locks, &pos, &key, &capsule)) {
+ lock = PyCapsule_GetPointer(capsule,
+ IMPORT_LOCK_CAPSULE_NAME);
+ assert(lock != NULL);
+ fix_level = (lock->tstate == tstate) ? lock->level : 0;
+ init_import_lock(lock);
+ if (fix_level > 0) {
+ acquire_import_lock(lock);
+ lock->level = fix_level;
+ }
+ }
+}
+
+/*
+ Returns the lock structure for the given module name, or NULL if not
+ found.
+ (note: NULL with an exception means another error occurred).
+*/
+static struct importlock *
+_get_module_lock(PyObject *fullname)
+{
+ PyObject *capsule;
+ struct importlock *lock;
+ if (module_locks == NULL)
+ return NULL;
+ capsule = PyDict_GetItemWithError(module_locks, fullname);
+ if (capsule == NULL)
+ return NULL;
+ lock = PyCapsule_GetPointer(capsule,
+ IMPORT_LOCK_CAPSULE_NAME);
+ assert(lock != NULL);
+ return lock;
+}
+
+/*
+ Release the lock structure for the given module name, destroying it
+ if possible.
+*/
+static int
+_release_module_lock(PyObject *fullname)
+{
+ PyObject *capsule;
+ struct importlock *lock;
+ int r;
+ if (module_locks == NULL)
+ return 0;
+ capsule = PyDict_GetItemWithError(module_locks, fullname);
+ if (capsule == NULL)
+ return -1;
+ lock = PyCapsule_GetPointer(capsule,
+ IMPORT_LOCK_CAPSULE_NAME);
+ assert(lock != NULL);
+ Py_INCREF(capsule);
+ if (lock->level == 1 && lock->waiters == 0) {
+ r = PyDict_DelItem(module_locks, fullname);
+ if (r)
+ return r;
+ }
+ r = release_import_lock(lock);
+ Py_DECREF(capsule);
+ return r;
+}
+
+static void
+_destroy_module_lock(PyObject *capsule)
+{
+ struct importlock *lock = PyCapsule_GetPointer(capsule,
+ IMPORT_LOCK_CAPSULE_NAME);
+ assert(lock != NULL);
+ clear_import_lock(lock);
+ PyObject_FREE(lock);
+}
+
+/* NULL if an exception occurred. */
+static struct importlock *
+_create_module_lock(PyObject *fullname)
+{
+ PyObject *capsule;
+ struct importlock *lock;
+ int r;
+ if (module_locks == NULL && !init_module_locks())
+ return NULL;
+ lock = (struct importlock *) PyObject_MALLOC(sizeof(struct importlock));
+ if (lock == NULL)
+ return NULL;
+ capsule = PyCapsule_New(lock, IMPORT_LOCK_CAPSULE_NAME, _destroy_module_lock);
+ if (capsule == NULL) {
+ PyObject_Free(lock);
+ return NULL;
+ }
+ if (!init_import_lock(lock)) {
+ PyErr_Format(PyExc_RuntimeError,
+ "Couldn't initialize import lock for %R",
+ fullname);
+ Py_DECREF(capsule);
+ return NULL;
+ }
+ r = PyDict_SetItem(module_locks, fullname, capsule);
+ Py_DECREF(capsule);
+ if (r < 0)
+ return NULL;
+ return lock;
+}
+
+static int
+detect_circularity(PyThreadState *tstate, struct importlock *lock)
+{
+ PyThreadState *other = tstate;
+ Py_ssize_t n;
+ PyObject *fullname;
+
+ while (1) {
+ if (lock->tstate == NULL || lock->tstate == other)
+ /* Lock can be taken without blocking */
+ return 0;
+ if (lock->tstate == tstate) {
+ fprintf(stderr, "!! circularity detected through %s\n", PyUnicode_AsUTF8(fullname));
+ return 1;
+ }
+ other = lock->tstate;
+ /* What lock is the thread currently trying to take? */
+ assert(other->importing != NULL);
+ n = PyList_GET_SIZE(other->importing);
+ assert(n > 0);
+ fullname = PyList_GET_ITEM(other->importing, n - 1);
+ lock = _get_module_lock(fullname);
+ assert(lock != NULL);
+ }
+ return 0;
+}
+
+/* 1 on success, 0 on deadlock (threaded circular import), -1 on exception */
+
+static int
+acquire_module_lock(PyObject *fullname)
+{
+ struct importlock *lock;
+#ifdef WITH_THREAD
+ PyThreadState *tstate = PyThreadState_GET();
+ assert(tstate != NULL);
+#endif
+ assert(PyUnicode_Check(fullname));
+ _PyImport_AcquireLock();
+ lock = _get_module_lock(fullname);
+ if (!lock && !PyErr_Occurred())
+ lock = _create_module_lock(fullname);
+ if (!lock) {
+ _PyImport_ReleaseLock();
+ return -1;
+ }
+#ifdef WITH_THREAD
+ if (tstate->importing == NULL) {
+ tstate->importing = PyList_New(0);
+ if (tstate->importing == NULL) {
+ _PyImport_ReleaseLock();
+ return -1;
+ }
+ }
+ if (detect_circularity(tstate, lock)) {
+ _PyImport_ReleaseLock();
+ return 0;
+ }
+ /* Register the current thread as importing `fullname` */
+ if (PyList_Append(tstate->importing, fullname)) {
+ _PyImport_ReleaseLock();
+ return -1;
+ }
+#endif
+ _PyImport_ReleaseLock();
+ acquire_import_lock(lock);
+ return 1;
+}
+
+/* 1 on success, 0 on failure (unacquired lock), -1 on exception */
+
+static int
+release_module_lock(PyObject *fullname)
+{
+ int r;
+ r = _release_module_lock(fullname);
+ if (r == 1) {
+#ifdef WITH_THREAD
+ Py_ssize_t n;
+ PyThreadState *tstate = PyThreadState_GET();
+ assert(tstate != NULL);
+ assert(tstate->importing != NULL);
+ n = PyList_GET_SIZE(tstate->importing);
+ assert(n > 0);
+ if (PyList_SetSlice(tstate->importing, n - 1, n, NULL))
+ return -1;
+#endif
+ }
+ return r;
+}
+
+
+/* XXX we could define them for non-threaded builds too */
+#ifdef WITH_THREAD
+
+static struct importlock global_import_lock;
+
+void
+_PyImport_AcquireLock(void)
+{
+ struct importlock *lock = &global_import_lock;
+ /* This function can be called before _PyImport_Init(), therefore
+ we have to init the import lock ourselves */
+ if (lock->lock == NULL) {
+ if (init_import_lock(lock) <= 0)
+ Py_FatalError("Failed to initialized the import lock");
+ }
+ acquire_import_lock(lock);
+}
+
+int
+_PyImport_ReleaseLock(void)
+{
+ struct importlock *lock = &global_import_lock;
+ return release_import_lock(lock) > 0 ? 1 : -1;
+}
+
+/* This function is called from PyOS_AfterFork to ensure that newly
+ created child processes do not share locks with the parent.
+ We now acquire the import lock around fork() calls but on some platforms
+ (Solaris 9 and earlier? see isue7242) that still left us with problems. */
+
+void
+_PyImport_ReInitLock(void)
+{
+ int old_level = global_import_lock.level;
+ init_import_lock(&global_import_lock);
+ if (old_level > 1) {
+ /* Forked as a side effect of import */
+ acquire_import_lock(&global_import_lock);
+ global_import_lock.level = old_level;
+ }
+ reinit_module_locks();
+}
+
+#endif
+
+
/* Magic word to reject .pyc files generated by other Python versions.
It should change for each incompatible change to the bytecode.
@@ -162,6 +524,12 @@ void
struct filedescr *filetab;
int countD = 0;
int countS = 0;
+ struct importlock *lock = &global_import_lock;
+
+ if (lock->lock == NULL && lock->tstate == NULL) {
+ if (init_import_lock(&global_import_lock) <= 0)
+ Py_FatalError("Failed to initialized the import lock");
+ }
initstr = PyUnicode_InternFromString("__init__");
if (initstr == NULL)
@@ -269,89 +637,13 @@ void
Py_DECREF(path_hooks);
}
-/* Locking primitives to prevent parallel imports of the same module
- in different threads to return with a partially loaded module.
- These calls are serialized by the global interpreter lock. */
-
-#ifdef WITH_THREAD
-
-#include "pythread.h"
-
-static PyThread_type_lock import_lock = 0;
-static long import_lock_thread = -1;
-static int import_lock_level = 0;
-
-void
-_PyImport_AcquireLock(void)
-{
- long me = PyThread_get_thread_ident();
- if (me == -1)
- return; /* Too bad */
- if (import_lock == NULL) {
- import_lock = PyThread_allocate_lock();
- if (import_lock == NULL)
- return; /* Nothing much we can do. */
- }
- if (import_lock_thread == me) {
- import_lock_level++;
- return;
- }
- if (import_lock_thread != -1 || !PyThread_acquire_lock(import_lock, 0))
- {
- PyThreadState *tstate = PyEval_SaveThread();
- PyThread_acquire_lock(import_lock, 1);
- PyEval_RestoreThread(tstate);
- }
- import_lock_thread = me;
- import_lock_level = 1;
-}
-
-int
-_PyImport_ReleaseLock(void)
-{
- long me = PyThread_get_thread_ident();
- if (me == -1 || import_lock == NULL)
- return 0; /* Too bad */
- if (import_lock_thread != me)
- return -1;
- import_lock_level--;
- if (import_lock_level == 0) {
- import_lock_thread = -1;
- PyThread_release_lock(import_lock);
- }
- return 1;
-}
-
-/* This function is called from PyOS_AfterFork to ensure that newly
- created child processes do not share locks with the parent.
- We now acquire the import lock around fork() calls but on some platforms
- (Solaris 9 and earlier? see isue7242) that still left us with problems. */
-
-void
-_PyImport_ReInitLock(void)
-{
- if (import_lock != NULL)
- import_lock = PyThread_allocate_lock();
- if (import_lock_level > 1) {
- /* Forked as a side effect of import */
- long me = PyThread_get_thread_ident();
- PyThread_acquire_lock(import_lock, 0);
- /* XXX: can the previous line fail? */
- import_lock_thread = me;
- import_lock_level--;
- } else {
- import_lock_thread = -1;
- import_lock_level = 0;
- }
-}
-
-#endif
+/* Methods of the `imp` module */
static PyObject *
imp_lock_held(PyObject *self, PyObject *noargs)
{
#ifdef WITH_THREAD
- return PyBool_FromLong(import_lock_thread != -1);
+ return PyBool_FromLong(global_import_lock.tstate != NULL);
#else
return PyBool_FromLong(0);
#endif
@@ -388,12 +680,7 @@ void
extensions = NULL;
PyMem_DEL(_PyImport_Filetab);
_PyImport_Filetab = NULL;
-#ifdef WITH_THREAD
- if (import_lock != NULL) {
- PyThread_free_lock(import_lock);
- import_lock = NULL;
- }
-#endif
+ clear_import_lock(&global_import_lock);
}
static void
@@ -2498,7 +2785,11 @@ load_module(PyObject *name, FILE *fp, Py
"import hook without loader");
return NULL;
}
+ /* For backwards compatibility with non-thread safe import hooks.
+ XXX perhaps we should allow hooks to choose this */
+ _PyImport_AcquireLock();
m = _PyObject_CallMethodId(loader, &PyId_load_module, "O", name);
+ _PyImport_ReleaseLock();
break;
}
@@ -2729,14 +3020,15 @@ PyImport_ImportModule(const char *name)
* ImportError instead of blocking.
*
* Returns the module object with incremented ref count.
+ *
+ * XXX this function should be modified in regard of the new module-specific
+ * locks.
*/
PyObject *
PyImport_ImportModuleNoBlock(const char *name)
{
PyObject *nameobj, *modules, *result;
-#ifdef WITH_THREAD
- long me;
-#endif
+ struct importlock *lock = &global_import_lock;
/* Try to get the module from sys.modules[name] */
modules = PyImport_GetModuleDict();
@@ -2757,8 +3049,7 @@ PyImport_ImportModuleNoBlock(const char
/* check the import lock
* me might be -1 but I ignore the error here, the lock function
* takes care of the problem */
- me = PyThread_get_thread_ident();
- if (import_lock_thread == -1 || import_lock_thread == me) {
+ if (lock->tstate == NULL || lock->tstate == PyThreadState_GET()) {
/* no thread or me is holding the lock */
result = PyImport_Import(nameobj);
}
@@ -2891,14 +3182,7 @@ PyImport_ImportModuleLevelObject(PyObjec
int level)
{
PyObject *mod;
- _PyImport_AcquireLock();
mod = import_module_level(name, globals, locals, fromlist, level);
- if (_PyImport_ReleaseLock() < 0) {
- Py_XDECREF(mod);
- PyErr_SetString(PyExc_RuntimeError,
- "not holding the import lock");
- return NULL;
- }
return mod;
}
@@ -3294,16 +3578,22 @@ import_submodule(PyObject *mod, PyObject
PyObject *m = NULL, *bufobj, *path_list, *loader;
struct filedescr *fdp;
FILE *fp;
+ int r, is_circular;
/* Require:
if mod == None: subname == fullname
else: mod.__name__ + "." + subname == fullname
*/
+ r = acquire_module_lock(fullname);
+ if (r < 0)
+ return NULL;
+ is_circular = (r == 0);
if ((m = PyDict_GetItem(modules, fullname)) != NULL) {
Py_INCREF(m);
- return m;
+ goto bail;
}
+ assert(r > 0);
if (mod == Py_None)
path_list = NULL;
@@ -3311,8 +3601,9 @@ import_submodule(PyObject *mod, PyObject
path_list = _PyObject_GetAttrId(mod, &PyId___path__);
if (path_list == NULL) {
PyErr_Clear();
+ m = Py_None;
Py_INCREF(Py_None);
- return Py_None;
+ goto bail;
}
}
@@ -3321,10 +3612,11 @@ import_submodule(PyObject *mod, PyObject
Py_XDECREF(path_list);
if (fdp == NULL) {
if (!PyErr_ExceptionMatches(PyExc_ImportError))
- return NULL;
+ goto bail;
PyErr_Clear();
+ m = Py_None;
Py_INCREF(Py_None);
- return Py_None;
+ goto bail;
}
m = load_module(fullname, fp, bufobj, fdp->type, loader);
Py_XDECREF(bufobj);
@@ -3332,11 +3624,13 @@ import_submodule(PyObject *mod, PyObject
if (fp)
fclose(fp);
if (m == NULL)
- return NULL;
- if (!add_submodule(mod, m, fullname, subname, modules)) {
- Py_XDECREF(m);
- return NULL;
- }
+ goto bail;
+ if (!add_submodule(mod, m, fullname, subname, modules))
+ Py_CLEAR(m);
+
+bail:
+ if (!is_circular)
+ release_module_lock(fullname);
return m;
}
@@ -3427,14 +3721,14 @@ PyImport_ReloadModule(PyObject *m)
Py_DECREF(subname);
Py_XDECREF(path_list);
- if (fdp == NULL) {
- Py_XDECREF(loader);
+ if (fdp == NULL)
goto error;
- }
-
+
+ if (acquire_module_lock(name) < 0)
+ goto error;
newm = load_module(name, fp, bufobj, fdp->type, loader);
- Py_XDECREF(bufobj);
- Py_XDECREF(loader);
+ if (release_module_lock(name) < 0)
+ goto error;
if (fp)
fclose(fp);
@@ -3449,6 +3743,8 @@ PyImport_ReloadModule(PyObject *m)
error:
imp_modules_reloading_clear();
+ Py_XDECREF(bufobj);
+ Py_XDECREF(loader);
Py_DECREF(name);
return newm;
}
diff --git a/Python/pystate.c b/Python/pystate.c
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -191,6 +191,7 @@ new_threadstate(PyInterpreterState *inte
#else
tstate->thread_id = 0;
#endif
+ tstate->importing = NULL;
tstate->dict = NULL;
@@ -285,6 +286,7 @@ PyThreadState_Clear(PyThreadState *tstat
Py_CLEAR(tstate->dict);
Py_CLEAR(tstate->async_exc);
+ Py_CLEAR(tstate->importing);
Py_CLEAR(tstate->curexc_type);
Py_CLEAR(tstate->curexc_value);