Skip to content

Commit

Permalink
[3.6] bpo-30814: Fixed a race condition when import a submodule from …
Browse files Browse the repository at this point in the history
…a package. (GH-2580). (#2598)

(cherry picked from commit b4baace)
  • Loading branch information
serhiy-storchaka committed Jul 6, 2017
1 parent aaa4f99 commit 03b0e83
Show file tree
Hide file tree
Showing 7 changed files with 340 additions and 325 deletions.
28 changes: 14 additions & 14 deletions Lib/importlib/_bootstrap.py
Expand Up @@ -956,9 +956,19 @@ def _find_and_load_unlocked(name, import_):


def _find_and_load(name, import_):
"""Find and load the module, and release the import lock."""
with _ModuleLockManager(name):
return _find_and_load_unlocked(name, import_)
"""Find and load the module."""
_imp.acquire_lock()
if name not in sys.modules:
with _ModuleLockManager(name):
return _find_and_load_unlocked(name, import_)
module = sys.modules[name]
if module is None:
_imp.release_lock()
message = ('import of {} halted; '
'None in sys.modules'.format(name))
raise ModuleNotFoundError(message, name=name)
_lock_unlock_module(name)
return module


def _gcd_import(name, package=None, level=0):
Expand All @@ -973,17 +983,7 @@ def _gcd_import(name, package=None, level=0):
_sanity_check(name, package, level)
if level > 0:
name = _resolve_name(name, package, level)
_imp.acquire_lock()
if name not in sys.modules:
return _find_and_load(name, _gcd_import)
module = sys.modules[name]
if module is None:
_imp.release_lock()
message = ('import of {} halted; '
'None in sys.modules'.format(name))
raise ModuleNotFoundError(message, name=name)
_lock_unlock_module(name)
return module
return _find_and_load(name, _gcd_import)


def _handle_fromlist(module, fromlist, import_):
Expand Down
28 changes: 28 additions & 0 deletions Lib/test/test_import/__init__.py
Expand Up @@ -10,6 +10,8 @@
import random
import stat
import sys
import threading
import time
import unittest
import unittest.mock as mock
import textwrap
Expand Down Expand Up @@ -350,6 +352,32 @@ def __getattr__(self, _):
with self.assertRaises(ImportError):
from test_from_import_AttributeError import does_not_exist

def test_concurrency(self):
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'data'))
try:
exc = None
def run():
event.wait()
try:
import package
except BaseException as e:
nonlocal exc
exc = e

for i in range(10):
event = threading.Event()
threads = [threading.Thread(target=run) for x in range(2)]
try:
with test.support.start_threads(threads, event.set):
time.sleep(0)
finally:
sys.modules.pop('package', None)
sys.modules.pop('package.submodule', None)
if exc is not None:
raise exc
finally:
del sys.path[0]


@skip_if_dont_write_bytecode
class FilePermissionTests(unittest.TestCase):
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_import/data/package/__init__.py
@@ -0,0 +1,2 @@
import package.submodule
package.submodule
Empty file.
2 changes: 2 additions & 0 deletions Misc/NEWS
Expand Up @@ -10,6 +10,8 @@ What's New in Python 3.6.3 release candidate 1?
Core and Builtins
-----------------

- bpo-30814: Fixed a race condition when import a submodule from a package.

- bpo-30597: ``print`` now shows expected input in custom error message when
used as a Python 2 statement. Patch by Sanyam Khurana.

Expand Down
17 changes: 1 addition & 16 deletions Python/import.c
Expand Up @@ -1533,18 +1533,7 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
}

mod = PyDict_GetItem(interp->modules, abs_name);
if (mod == Py_None) {
PyObject *msg = PyUnicode_FromFormat("import of %R halted; "
"None in sys.modules", abs_name);
if (msg != NULL) {
PyErr_SetImportErrorSubclass(PyExc_ModuleNotFoundError, msg,
abs_name, NULL);
Py_DECREF(msg);
}
mod = NULL;
goto error;
}
else if (mod != NULL) {
if (mod != NULL && mod != Py_None) {
_Py_IDENTIFIER(__spec__);
_Py_IDENTIFIER(_initializing);
_Py_IDENTIFIER(_lock_unlock_module);
Expand Down Expand Up @@ -1585,10 +1574,6 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
}
}
else {
#ifdef WITH_THREAD
_PyImport_AcquireLock();
#endif
/* _bootstrap._find_and_load() releases the import lock */
mod = _PyObject_CallMethodIdObjArgs(interp->importlib,
&PyId__find_and_load, abs_name,
interp->import_func, NULL);
Expand Down

0 comments on commit 03b0e83

Please sign in to comment.