diff --git a/Sources/Foundation/FileManager+POSIX.swift b/Sources/Foundation/FileManager+POSIX.swift index 72ed1bc11e..c4d7c2d271 100644 --- a/Sources/Foundation/FileManager+POSIX.swift +++ b/Sources/Foundation/FileManager+POSIX.swift @@ -346,8 +346,16 @@ extension FileManager { try createDirectory(atPath: parent, withIntermediateDirectories: true, attributes: attributes) } if mkdir(pathFsRep, S_IRWXU | S_IRWXG | S_IRWXO) != 0 { - throw _NSErrorWithErrno(errno, reading: false, path: path) - } else if let attr = attributes { + let posixError = errno + if posixError == EEXIST && fileExists(atPath: path, isDirectory: &isDir) && isDir.boolValue { + // Continue; if there is an existing file and it is a directory, that is still a success. + // There can be an existing file if another thread or process concurrently creates the + // same file. + } else { + throw _NSErrorWithErrno(posixError, reading: false, path: path) + } + } + if let attr = attributes { try self.setAttributes(attr, ofItemAtPath: path) } } else if isDir.boolValue { diff --git a/Tests/Foundation/Tests/TestFileManager.swift b/Tests/Foundation/Tests/TestFileManager.swift index 84a70d7ee7..5c79f1672f 100644 --- a/Tests/Foundation/Tests/TestFileManager.swift +++ b/Tests/Foundation/Tests/TestFileManager.swift @@ -15,6 +15,8 @@ #endif #endif +import Dispatch + class TestFileManager : XCTestCase { #if os(Windows) let pathSep = "\\" @@ -1755,6 +1757,49 @@ VIDEOS=StopgapVideos } #endif } + + /** + Tests that we can get an item replacement directory concurrently. + + - Bug: [SR-12272](https://bugs.swift.org/browse/SR-12272) + */ + func test_concurrentGetItemReplacementDirectory() throws { + let fileManager = FileManager.default + + let operationCount = 10 + + var directoryURLs = [URL?](repeating: nil, count: operationCount) + var errors = [Error?](repeating: nil, count: operationCount) + + let dispatchGroup = DispatchGroup() + for operationIndex in 0.. Re-enable Foundation test TestFileManager.test_replacement")), + ("test_concurrentGetItemReplacementDirectory", test_concurrentGetItemReplacementDirectory), ] #if !DEPLOYMENT_RUNTIME_OBJC && NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT && !os(Android)