[3.6] bpo-29694: race condition in pathlib mkdir with flags parents=True (GH-1089). (GH-1126)

(cherry picked from commit 22a594a004)
This commit is contained in:
Mariatta 2017-04-13 19:26:16 -07:00 committed by GitHub
parent 2cdf087d1f
commit cbc46afa59
3 changed files with 36 additions and 2 deletions

View file

@ -1230,8 +1230,8 @@ class Path(PurePath):
except FileNotFoundError: except FileNotFoundError:
if not parents or self.parent == self: if not parents or self.parent == self:
raise raise
self.parent.mkdir(parents=True) self.parent.mkdir(parents=True, exist_ok=True)
self._accessor.mkdir(self, mode) self.mkdir(mode, parents=False, exist_ok=exist_ok)
except OSError: except OSError:
# Cannot rely on checking for EEXIST, since the operating system # Cannot rely on checking for EEXIST, since the operating system
# could give priority to other errors like EACCES or EROFS # could give priority to other errors like EACCES or EROFS

View file

@ -8,6 +8,7 @@ import socket
import stat import stat
import tempfile import tempfile
import unittest import unittest
from unittest import mock
from test import support from test import support
android_not_root = support.android_not_root android_not_root = support.android_not_root
@ -1816,6 +1817,35 @@ class _BasePathTest(object):
p.mkdir(exist_ok=True) p.mkdir(exist_ok=True)
self.assertEqual(cm.exception.errno, errno.EEXIST) 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 @with_symlinks
def test_symlink_to(self): def test_symlink_to(self):
P = self.cls(BASE) P = self.cls(BASE)

View file

@ -31,6 +31,10 @@ Core and Builtins
Library 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 - bpo-29692: Fixed arbitrary unchaining of RuntimeError exceptions in
contextlib.contextmanager. contextlib.contextmanager.
Patch by Siddharth Velankar. Patch by Siddharth Velankar.