Skip to content

Commit 8c7d946

Browse files
Retry priors file write with exponential backoff
On Windows, atomic file writes (temp file + rename) can fail intermittently with ERROR_ACCESS_DENIED if updates to the file happens really close to the write of the file underload. This can especially happen to `.priors` file in a tight rebuild loop. Now writes `.priors` file in a retry loop with exponential back off. Assisted-By: Claude
1 parent b4db0d9 commit 8c7d946

3 files changed

Lines changed: 49 additions & 3 deletions

File tree

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ add_library(SwiftDriver
113113
Utilities/DOTModuleDependencyGraphSerializer.swift
114114
Utilities/DateAdditions.swift
115115
Utilities/Diagnostics.swift
116+
Utilities/ExponentialBackoff.swift
116117
Utilities/FileList.swift
117118
Utilities/FileMetadata.swift
118119
Utilities/FileType.swift

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraph.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,9 +1117,9 @@ extension ModuleDependencyGraph {
11171117
mockSerializedGraphVersion ?? Self.serializedGraphVersion)
11181118

11191119
do {
1120-
try fileSystem.writeFileContents(path,
1121-
bytes: data,
1122-
atomically: true)
1120+
try withExponentialBackoff {
1121+
try fileSystem.writeFileContents(path, bytes: data, atomically: true)
1122+
}
11231123
} catch {
11241124
throw IncrementalCompilationState.WriteDependencyGraphError.couldNotWrite(
11251125
path: path, error: error)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import class Foundation.Thread
16+
import struct Foundation.TimeInterval
17+
18+
/// Retries `body` up to `maxAttempts` times with exponential backoff.
19+
///
20+
/// If all attempts fail, the error from the last attempt is thrown.
21+
///
22+
/// - Parameters:
23+
/// - maxAttempts: Maximum number of times to attempt `body` (default: 5).
24+
/// - initialDelay: The delay before the first retry, in seconds (default: 0.1).
25+
/// - body: The throwing operation to attempt.
26+
func withExponentialBackoff<T>(
27+
maxAttempts: Int = 5,
28+
initialDelay: TimeInterval = 0.1,
29+
_ body: () throws -> T
30+
) throws -> T {
31+
// Add a bit randomness to avoid lock stepped back off.
32+
var delay = initialDelay * Double.random(in: 0.9...1.1)
33+
var lastError: Error?
34+
35+
for _ in 0..<maxAttempts {
36+
do {
37+
return try body()
38+
} catch {
39+
lastError = error
40+
Thread.sleep(forTimeInterval: delay)
41+
delay *= 2
42+
}
43+
}
44+
throw lastError!
45+
}

0 commit comments

Comments
 (0)