From 250a3c55aba2bbf39154a9587839b04bd38a5d73 Mon Sep 17 00:00:00 2001 From: fumoboy007 Date: Wed, 26 Feb 2020 18:26:31 -0800 Subject: [PATCH] Return success from `FileManager` recursive directory creation even if a directory was created by another thread concurrently. If another thread creates one of the same directories in the target path, the `!fileExists` check may pass while the `mkdir` call fails with `EEXIST`. However, if the existing file is a directory, then that should still be a success. Fixes [SR-12272](https://bugs.swift.org/browse/SR-12272). (cherry picked from commit b6eea02e84ea46e8bc36588f250d36930fe2ee80) --- Sources/Foundation/FileManager+POSIX.swift | 12 ++++- Tests/Foundation/Tests/TestFileManager.swift | 46 ++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) 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)