Skip to content

Commit 03b0e83

Browse files
[3.6] bpo-30814: Fixed a race condition when import a submodule from a package. (GH-2580). (#2598)
(cherry picked from commit b4baace)
1 parent aaa4f99 commit 03b0e83

File tree

7 files changed

+340
-325
lines changed

7 files changed

+340
-325
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
@@ -350,6 +352,32 @@ def __getattr__(self, _):
350352
with self.assertRaises(ImportError):
351353
from test_from_import_AttributeError import does_not_exist
352354

355+
def test_concurrency(self):
356+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'data'))
357+
try:
358+
exc = None
359+
def run():
360+
event.wait()
361+
try:
362+
import package
363+
except BaseException as e:
364+
nonlocal exc
365+
exc = e
366+
367+
for i in range(10):
368+
event = threading.Event()
369+
threads = [threading.Thread(target=run) for x in range(2)]
370+
try:
371+
with test.support.start_threads(threads, event.set):
372+
time.sleep(0)
373+
finally:
374+
sys.modules.pop('package', None)
375+
sys.modules.pop('package.submodule', None)
376+
if exc is not None:
377+
raise exc
378+
finally:
379+
del sys.path[0]
380+
353381

354382
@skip_if_dont_write_bytecode
355383
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.6.3 release candidate 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- bpo-30814: Fixed a race condition when import a submodule from a package.
14+
1315
- bpo-30597: ``print`` now shows expected input in custom error message when
1416
used as a Python 2 statement. Patch by Sanyam Khurana.
1517

Python/import.c

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

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

0 commit comments

Comments
 (0)