]> granicus.if.org Git - python/commitdiff
[3.6] bpo-29694: race condition in pathlib mkdir with flags parents=True (GH-1089...
authorMariatta <Mariatta@users.noreply.github.com>
Fri, 14 Apr 2017 02:26:16 +0000 (19:26 -0700)
committerGitHub <noreply@github.com>
Fri, 14 Apr 2017 02:26:16 +0000 (19:26 -0700)
(cherry picked from commit 22a594a0047d7706537ff2ac676cdc0f1dcb329c)

Lib/pathlib.py
Lib/test/test_pathlib.py
Misc/NEWS

index 8c1cb96bad3075575f3873fe20c608ce129d9eff..70f5cba76ffa8aee098f0c5f14bf80bace7f40a3 100644 (file)
@@ -1230,8 +1230,8 @@ class Path(PurePath):
         except FileNotFoundError:
             if not parents or self.parent == self:
                 raise
-            self.parent.mkdir(parents=True)
-            self._accessor.mkdir(self, mode)
+            self.parent.mkdir(parents=True, exist_ok=True)
+            self.mkdir(mode, parents=False, exist_ok=exist_ok)
         except OSError:
             # Cannot rely on checking for EEXIST, since the operating system
             # could give priority to other errors like EACCES or EROFS
index 3ff9726d4359ec5457d9c2efb7d982bb74d84fdf..846f721e8d9abbd8a5da8902716693151bad9502 100644 (file)
@@ -8,6 +8,7 @@ import socket
 import stat
 import tempfile
 import unittest
+from unittest import mock
 
 from test import support
 android_not_root = support.android_not_root
@@ -1816,6 +1817,35 @@ class _BasePathTest(object):
             p.mkdir(exist_ok=True)
         self.assertEqual(cm.exception.errno, errno.EEXIST)
 
+    def test_mkdir_concurrent_parent_creation(self):
+        for pattern_num in range(32):
+            p = self.cls(BASE, 'dirCPC%d' % pattern_num)
+            self.assertFalse(p.exists())
+
+            def my_mkdir(path, mode=0o777):
+                path = str(path)
+                # Emulate another process that would create the directory
+                # just before we try to create it ourselves.  We do it
+                # in all possible pattern combinations, assuming that this
+                # function is called at most 5 times (dirCPC/dir1/dir2,
+                # dirCPC/dir1, dirCPC, dirCPC/dir1, dirCPC/dir1/dir2).
+                if pattern.pop():
+                    os.mkdir(path, mode)      # from another process
+                    concurrently_created.add(path)
+                os.mkdir(path, mode)          # our real call
+
+            pattern = [bool(pattern_num & (1 << n)) for n in range(5)]
+            concurrently_created = set()
+            p12 = p / 'dir1' / 'dir2'
+            try:
+                with mock.patch("pathlib._normal_accessor.mkdir", my_mkdir):
+                    p12.mkdir(parents=True, exist_ok=False)
+            except FileExistsError:
+                self.assertIn(str(p12), concurrently_created)
+            else:
+                self.assertNotIn(str(p12), concurrently_created)
+            self.assertTrue(p.exists())
+
     @with_symlinks
     def test_symlink_to(self):
         P = self.cls(BASE)
index 6e2db58a92f451973c10f901ee65403fe3ef718e..e623457cdd18ce061358f28bef0c3ea06dd18559 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -31,6 +31,10 @@ Core and Builtins
 
 Library
 -------
+
+- bpo-29694: Fixed race condition in pathlib mkdir with flags
+  parents=True.  Patch by Armin Rigo.
+
 - bpo-29692: Fixed arbitrary unchaining of RuntimeError exceptions in 
   contextlib.contextmanager.
   Patch by Siddharth Velankar.