Skip to content

Commit 71dd7a6

Browse files
authored
Merge pull request #2833 from spevans/pr_sr_12272_53
[5.3] Return success from `FileManager` recursive directory creation even if a directory was created by another thread concurrently.
2 parents 347b874 + 250a3c5 commit 71dd7a6

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-2
lines changed

Sources/Foundation/FileManager+POSIX.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,16 @@ extension FileManager {
346346
try createDirectory(atPath: parent, withIntermediateDirectories: true, attributes: attributes)
347347
}
348348
if mkdir(pathFsRep, S_IRWXU | S_IRWXG | S_IRWXO) != 0 {
349-
throw _NSErrorWithErrno(errno, reading: false, path: path)
350-
} else if let attr = attributes {
349+
let posixError = errno
350+
if posixError == EEXIST && fileExists(atPath: path, isDirectory: &isDir) && isDir.boolValue {
351+
// Continue; if there is an existing file and it is a directory, that is still a success.
352+
// There can be an existing file if another thread or process concurrently creates the
353+
// same file.
354+
} else {
355+
throw _NSErrorWithErrno(posixError, reading: false, path: path)
356+
}
357+
}
358+
if let attr = attributes {
351359
try self.setAttributes(attr, ofItemAtPath: path)
352360
}
353361
} else if isDir.boolValue {

Tests/Foundation/Tests/TestFileManager.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#endif
1616
#endif
1717

18+
import Dispatch
19+
1820
class TestFileManager : XCTestCase {
1921
#if os(Windows)
2022
let pathSep = "\\"
@@ -1755,6 +1757,49 @@ VIDEOS=StopgapVideos
17551757
}
17561758
#endif
17571759
}
1760+
1761+
/**
1762+
Tests that we can get an item replacement directory concurrently.
1763+
1764+
- Bug: [SR-12272](https://bugs.swift.org/browse/SR-12272)
1765+
*/
1766+
func test_concurrentGetItemReplacementDirectory() throws {
1767+
let fileManager = FileManager.default
1768+
1769+
let operationCount = 10
1770+
1771+
var directoryURLs = [URL?](repeating: nil, count: operationCount)
1772+
var errors = [Error?](repeating: nil, count: operationCount)
1773+
1774+
let dispatchGroup = DispatchGroup()
1775+
for operationIndex in 0..<operationCount {
1776+
DispatchQueue.global().async(group: dispatchGroup) {
1777+
do {
1778+
let directory = try fileManager.url(for: .itemReplacementDirectory,
1779+
in: .userDomainMask,
1780+
appropriateFor: URL(fileURLWithPath: NSTemporaryDirectory(),
1781+
isDirectory: true),
1782+
create: true)
1783+
directoryURLs[operationIndex] = directory
1784+
} catch {
1785+
errors[operationIndex] = error
1786+
}
1787+
}
1788+
}
1789+
dispatchGroup.wait()
1790+
1791+
for directoryURL in directoryURLs {
1792+
if let directoryURL = directoryURL {
1793+
try? fileManager.removeItem(at: directoryURL)
1794+
}
1795+
}
1796+
1797+
for error in errors {
1798+
if let error = error {
1799+
XCTFail("One of the concurrent calls to get the item replacement directory failed: \(error)")
1800+
}
1801+
}
1802+
}
17581803

17591804
// -----
17601805

@@ -1812,6 +1857,7 @@ VIDEOS=StopgapVideos
18121857
("test_contentsEqual", test_contentsEqual),
18131858
/* ⚠️ */ ("test_replacement", testExpectedToFail(test_replacement,
18141859
/* ⚠️ */ "<https://bugs.swift.org/browse/SR-10819> Re-enable Foundation test TestFileManager.test_replacement")),
1860+
("test_concurrentGetItemReplacementDirectory", test_concurrentGetItemReplacementDirectory),
18151861
]
18161862

18171863
#if !DEPLOYMENT_RUNTIME_OBJC && NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT && !os(Android)

0 commit comments

Comments
 (0)