8000 bpo-29694: race condition in pathlib mkdir with flags parents=True (G… · python/cpython@22a594a · GitHub
[go: up one dir, main page]

Skip to content

Commit 22a594a

Browse files
arigoMariatta
authored andcommitted
bpo-29694: race condition in pathlib mkdir with flags parents=True (GH-1089)
1 parent 5908300 commit 22a594a

File tree

3 files changed

+36
-2
lines changed

3 files changed

+36
-2
lines changed

Lib/pathlib.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,8 +1217,8 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
12171217
except FileNotFoundError:
12181218
if not parents or self.parent == self:
12191219
raise
1220-
self.parent.mkdir(parents=True)
1221-
self._accessor.mkdir(self, mode)
1220+
self.parent.mkdir(parents=True, exist_ok=True)
1221+
self.mkdir(mode, parents=False, exist_ok=exist_ok)
12221222
except OSError:
12231223
# Cannot rely on checking for EEXIST, since the operating system
12241224
# could give priority to other errors like EACCES or EROFS

Lib/test/test_pathlib.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import stat
99
import tempfile
1010
import unittest
11+
from unittest import mock
1112

1213
from test import support
1314
android_not_root = support.android_not_root
@@ -1801,6 +1802,35 @@ def test_mkdir_no_parents_file(self):
18011802
p.mkdir(exist_ok=True)
18021803
self.assertEqual(cm.exception.errno, errno.EEXIST)
18031804

1805+
def test_mkdir_concurrent_parent_creation(self):
1806+
for pattern_num in range(32):
1807+
p = self.cls(BASE, 'dirCPC%d' % pattern_num)
1808+
self.assertFalse(p.exists())
1809+
1810+
def my_mkdir(path, mode=0o777):
1811+
path = str(path)
1812+
# Emulate another process that would create the directory
1813+
# just before we try to create it ourselves. We do it
1814+
# in all possible pattern combinations, assuming that this
1815+
# function is called at most 5 times (dirCPC/dir1/dir2,
1816+
# dirCPC/dir1, dirCPC, dirCPC/dir1, dirCPC/dir1/dir2).
1817+
if pattern.pop():
1818+
os.mkdir(path, mode) # from another process
1819+
concurrently_created.add(path)
1820+
os.mkdir(path, mode) # our real call
1821+
1822+
pattern = [bool(pattern_num & (1 << n)) for n in range(5)]
1823+
concurrently_created = set()
1824+
p12 = p / 'dir1' / 'dir2'
1825+
try:
1826+
with mock.patch("pathlib._normal_accessor.mkdir", my_mkdir):
1827+
p12.mkdir(parents=True, exist_ok=False)
1828+
except FileExistsError:
1829+
self.assertIn(str(p12), concurrently_created)
1830+
else:
1831+
self.assertNotIn(str(p12), concurrently_created)
1832+
self.assertTrue(p.exists())
1833+
18041834
@support.skip_unless_symlink
18051835
def test_symlink_to(self):
18061836
P = self.cls(BASE)

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,10 @@ Extension Modules
306306

307307
Library
308308
-------
309+
310+
- bpo-29694: Fixed race condition in pathlib mkdir with flags
311+
parents=True. Patch by Armin Rigo.
312+
309313
- bpo-29692: Fixed arbitrary unchaining of RuntimeError exceptions in
310314
contextlib.contextmanager.
311315
Patch by Siddharth Velankar.

0 commit comments

Comments
 (0)
0