Skip to content

Commit b4baace

Browse files
bpo-30814: Fixed a race condition when import a submodule from a package. (#2580)
1 parent 1ccbad9 commit b4baace

File tree

7 files changed

+352
-337
lines changed

7 files changed

+352
-337
lines changed

Lib/importlib/_bootstrap.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -956,9 +956,19 @@ def _find_and_load_unlocked(name, import_):
956956

957957

958958
def _find_and_load(name, import_):
959-
"""Find and load the module, and release the import lock."""
960-
with _ModuleLockManager(name):
961-
return _find_and_load_unlocked(name, import_)
959+
"""Find and load the module."""
960+
_imp.acquire_lock()
961+
if name not in sys.modules:
962+
with _ModuleLockManager(name):
963+
return _find_and_load_unlocked(name, import_)
964+
module = sys.modules[name]
965+
if module is None:
966+
_imp.release_lock()
967+
message = ('import of {} halted; '
968+
'None in sys.modules'.format(name))
969+
raise ModuleNotFoundError(message, name=name)
970+
_lock_unlock_module(name)
971+
return module
962972

963973

964974
def _gcd_import(name, package=None, level=0):
@@ -973,17 +983,7 @@ def _gcd_import(name, package=None, level=0):
973983
_sanity_check(name, package, level)
974984
if level > 0:
975985
name = _resolve_name(name, package, level)
976-
_imp.acquire_lock()
977-
if name not in sys.modules:
978-
return _find_and_load(name, _gcd_import)
979-
module = sys.modules[name]
980-
if module is None:
981-
_imp.release_lock()
982-
message = ('import of {} halted; '
983-
'None in sys.modules'.format(name))
984-
raise ModuleNotFoundError(message, name=name)
985-
_lock_unlock_module(name)
986-
return module
986+
return _find_and_load(name, _gcd_import)
987987

988988

989989
def _handle_fromlist(module, fromlist, import_):

Lib/test/test_import/__init__.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import random
1111
import stat
1212
import sys
13+
import threading
14+
import time
1315
import unittest
1416
import unittest.mock as mock
1517
import textwrap
@@ -380,6 +382,32 @@ def __getattr__(self, _):
380382
self.assertEqual(str(cm.exception),
381383
"cannot import name 'does_not_exist' from '<unknown module name>' (unknown location)")
382384

385+
def test_concurrency(self):
386+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'data'))
387+
try:
388+
exc = None
389+
def run():
390+
event.wait()
391+
try:
392+
import package
393+
except BaseException as e:
394+
nonlocal exc
395+
exc = e
396+
397+
for i in range(10):
398+
event = threading.Event()
399+
threads = [threading.Thread(target=run) for x in range(2)]
400+
try:
401+
with test.support.start_threads(threads, event.set):
402+
time.sleep(0)
403+
finally:
404+
sys.modules.pop('package', None)
405+
sys.modules.pop('package.submodule', None)
406+
if exc is not None:
407+
raise exc
408+
finally:
409+
del sys.path[0]
410+
383411

384412
@skip_if_dont_write_bytecode
385413
class FilePermissionTests(unittest.TestCase):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import package.submodule
2+
package.submodule

Lib/test/test_import/data/package/submodule.py

Whitespace-only changes.

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ What's New in Python 3.7.0 alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- bpo-30814: Fixed a race condition when import a submodule from a package.
14+
1315
- bpo-30736: The internal unicodedata database has been upgraded to Unicode
1416
10.0.
1517

Python/import.c

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1532,18 +1532,7 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
15321532
}
15331533

15341534
mod = PyDict_GetItem(interp->modules, abs_name);
1535-
if (mod == Py_None) {
1536-
PyObject *msg = PyUnicode_FromFormat("import of %R halted; "
1537-
"None in sys.modules", abs_name);
1538-
if (msg != NULL) {
1539-
PyErr_SetImportErrorSubclass(PyExc_ModuleNotFoundError, msg,
1540-
abs_name, NULL);
1541-
Py_DECREF(msg);
1542-
}
1543-
mod = NULL;
1544-
goto error;
1545-
}
1546-
else if (mod != NULL) {
1535+
if (mod != NULL && mod != Py_None) {
15471536
_Py_IDENTIFIER(__spec__);
15481537
_Py_IDENTIFIER(_initializing);
15491538
_Py_IDENTIFIER(_lock_unlock_module);
@@ -1584,10 +1573,6 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
15841573
}
15851574
}
15861575
else {
1587-
#ifdef WITH_THREAD
1588-
_PyImport_AcquireLock();
1589-
#endif
1590-
/* _bootstrap._find_and_load() releases the import lock */
15911576
mod = _PyObject_CallMethodIdObjArgs(interp->importlib,
15921577
&PyId__find_and_load, abs_name,
15931578
interp->import_func, NULL);

0 commit comments

Comments
 (0)