fix race condition when parsing config files (#2521)

This commit is contained in:
Guillaume Hugues
2026-04-27 00:49:18 +02:00
committed by Cal Stephens
parent 8b998f030d
commit 3e5bdf2cf3
2 changed files with 49 additions and 8 deletions
+16 -8
View File
@@ -346,10 +346,22 @@ func gatherOptions(_ options: inout Options, for inputURL: URL, with logger: Log
private var configCache = [URL: [[String: String]]]()
private let configQueue = DispatchQueue(label: "swiftformat.config", qos: .userInteractive)
private func processDirectory(_ inputURL: URL, with options: inout Options, logger: Logger?) throws {
if let args = configQueue.sync(execute: { configCache[inputURL] }) {
try options.addArguments(args, in: inputURL.path)
return
let inputURL = inputURL.standardizedFileURL
let args = try configQueue.sync { () throws -> [[String: String]] in
if let args = configCache[inputURL] {
return args
}
let args = try parseConfigArguments(in: inputURL, options: options, logger: logger)
configCache[inputURL] = args
return args
}
assert(options.formatOptions != nil)
try options.addArguments(args, in: inputURL.path)
}
private func parseConfigArguments(in inputURL: URL, options: Options, logger: Logger?) throws -> [[String: String]] {
var args = [[String: String]]()
let manager = FileManager.default
let configFile = inputURL.appendingPathComponent(swiftFormatConfigurationFile)
@@ -394,11 +406,7 @@ private func processDirectory(_ inputURL: URL, with options: inout Options, logg
}
}
}
configQueue.async {
configCache[inputURL] = args
}
assert(options.formatOptions != nil)
try options.addArguments(args, in: inputURL.standardizedFileURL.path)
return args
}
/// Line and column offset in source
+33
View File
@@ -646,6 +646,39 @@ final class CommandLineTests: XCTestCase {
}
}
func testConfigFileIsReadOnceWhenGatheringOptionsConcurrently() throws {
try withTmpFiles([
".swiftformat": """
--rules indent
""",
"File.swift": """
let value = 0
""",
]) { url in
guard url.pathExtension == "swift" else { return }
let configFile = url.deletingLastPathComponent().appendingPathComponent(".swiftformat")
var logMessages = [String]()
var errors = [Error]()
let logQueue = DispatchQueue(label: "swiftformat.test.config-log")
DispatchQueue.concurrentPerform(iterations: 50) { _ in
var options = Options.default
do {
try gatherOptions(&options, for: url, with: { message in
logQueue.sync { logMessages.append(message) }
})
} catch {
logQueue.sync { errors.append(error) }
}
}
let messages = logMessages.filter { $0 == "Reading config file at \(configFile.path)" }
XCTAssertEqual(messages.count, 1, "\(messages)")
XCTAssertTrue(errors.isEmpty, "\(errors)")
}
}
func testLintCommandOutputsOrganizeDeclarationOrderingViolations() {
var output: [String] = []
CLI.print = { message, _ in