From bb36b41818b99079372dbb55b272465820d58de3 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Wed, 11 Jul 2018 09:51:44 -0400 Subject: Add test for cache_path race condition This mocks out the isdir call so that the directory is created immediately after you determine whether or not it exists, thus simulating a race condition between two threads or processes creating the same directory. --- pkg_resources/tests/test_pkg_resources.py | 33 ++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py index 4e2cac94..62a39b8f 100644 --- a/pkg_resources/tests/test_pkg_resources.py +++ b/pkg_resources/tests/test_pkg_resources.py @@ -12,6 +12,11 @@ import stat import distutils.dist import distutils.command.install_egg_info +try: + from unittest import mock +except ImportError: + import mock + from pkg_resources.extern.six.moves import map from pkg_resources.extern.six import text_type, string_types @@ -138,8 +143,34 @@ class TestResourceManager: message = "Unexpected type from get_cache_path: " + type_ assert isinstance(path, string_types), message + def test_get_cache_path_race(self, tmpdir): + # Patch to os.path.isdir to create a race condition + def patched_isdir(dirname, unpatched_isdir=pkg_resources.isdir): + patched_isdir.dirnames.append(dirname) + + was_dir = unpatched_isdir(dirname) + if not was_dir: + os.makedirs(dirname) + return was_dir + + patched_isdir.dirnames = [] + + # Get a cache path with a "race condition" + mgr = pkg_resources.ResourceManager() + mgr.set_extraction_path(str(tmpdir)) + + archive_name = os.sep.join(('foo', 'bar', 'baz')) + with mock.patch.object(pkg_resources, 'isdir', new=patched_isdir): + mgr.get_cache_path(archive_name) + + # Because this test relies on the implementation details of this + # function, these assertions are a sentinel to ensure that the + # test suite will not fail silently if the implementation changes. + called_dirnames = patched_isdir.dirnames + assert len(called_dirnames) == 2 + assert called_dirnames[0].split(os.sep)[-2:] == ['foo', 'bar'] + assert called_dirnames[1].split(os.sep)[-1:] == ['foo'] -class TestIndependence: """ Tests to ensure that pkg_resources runs independently from setuptools. """ -- cgit v1.2.1 From 342443f5034851bbd55823f2c900d7ebabe1b306 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Wed, 11 Jul 2018 10:23:55 -0400 Subject: Fix race condition in _bypass_ensure_directory This fixes a race condition in _bypass_ensure_directory where two threads or processes may erroneously fail because they are both creating the same directory. A more robust implementation of this may involve exposing the un-wrapped os.makedirs. Originally reported with proposed patch by @JonKohler in github PR #1412. This patch came out of discussions on that thread. --- pkg_resources/__init__.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 67015408..4f42156d 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -47,6 +47,11 @@ except ImportError: # Python 3.2 compatibility import imp as _imp +try: + FileExistsError +except NameError: + FileExistsError = OSError + from pkg_resources.extern import six from pkg_resources.extern.six.moves import urllib, map, filter @@ -3030,7 +3035,10 @@ def _bypass_ensure_directory(path): dirname, filename = split(path) if dirname and filename and not isdir(dirname): _bypass_ensure_directory(dirname) - mkdir(dirname, 0o755) + try: + mkdir(dirname, 0o755) + except FileExistsError: + pass def split_sections(s): -- cgit v1.2.1 From 67f190adf28d433f0d43ad5014a3fe66f43eb551 Mon Sep 17 00:00:00 2001 From: Jon Kohler Date: Mon, 9 Jul 2018 13:14:33 -0400 Subject: Changelog for github PR #1418 --- changelog.d/1418.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1418.change.rst diff --git a/changelog.d/1418.change.rst b/changelog.d/1418.change.rst new file mode 100644 index 00000000..d7656f57 --- /dev/null +++ b/changelog.d/1418.change.rst @@ -0,0 +1 @@ +Solved race in when creating egg cache directories. -- cgit v1.2.1