From b6eea02e84ea46e8bc36588f250d36930fe2ee80 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). --- 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 92cf03ef26..d1c58a78a5 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 ee5d369530..e4d56f1485 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 = "\\" @@ -1870,6 +1872,49 @@ VIDEOS=StopgapVideos try checkPath(path: path) } } + + /** + 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)