Skip to content

Commit 274f115

Browse files
committed
Make lazy properties in UserToolchain thread safe
`runtimeLibraryPaths`, `swiftCompilerVersion` and `targetInfo` were all marked as lazy on the UserToolchain class which could lead to race conditions if callers on multiple threads attempted to initialize them simultaneously. Use an `NSRecursiveLock` to guard access while initialization occurs, preventing crashes.
1 parent b520573 commit 274f115

File tree

1 file changed

+61
-11
lines changed

1 file changed

+61
-11
lines changed

Sources/PackageModel/UserToolchain.swift

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,30 @@ public final class UserToolchain: Toolchain {
4242
/// An array of paths to search for libraries at link time.
4343
public let librarySearchPaths: [AbsolutePath]
4444

45+
/// Lock for thread-safe access to lazy properties (recursive to handle property dependencies)
46+
private let lazyPropertiesLock = NSRecursiveLock()
47+
48+
/// Cached runtime library paths
49+
private var _runtimeLibraryPaths: [AbsolutePath]? = nil
50+
4551
/// An array of paths to use with binaries produced by this toolchain at run time.
46-
public lazy var runtimeLibraryPaths: [AbsolutePath] = {
47-
guard let targetInfo else { return [] }
48-
return (try? Self.computeRuntimeLibraryPaths(targetInfo: targetInfo)) ?? []
49-
}()
52+
public var runtimeLibraryPaths: [AbsolutePath] {
53+
lazyPropertiesLock.lock()
54+
defer { lazyPropertiesLock.unlock() }
55+
56+
if let cached = _runtimeLibraryPaths {
57+
return cached
58+
}
59+
60+
guard let targetInfo = self.targetInfo else {
61+
_runtimeLibraryPaths = []
62+
return []
63+
}
64+
65+
let paths = (try? Self.computeRuntimeLibraryPaths(targetInfo: targetInfo)) ?? []
66+
_runtimeLibraryPaths = paths
67+
return paths
68+
}
5069

5170
/// Path containing Swift resources for dynamic linking.
5271
public var swiftResourcesPath: AbsolutePath? {
@@ -82,16 +101,47 @@ public final class UserToolchain: Toolchain {
82101
public let targetTriple: Basics.Triple
83102

84103
private let _targetInfo: JSON?
85-
private lazy var targetInfo: JSON? = {
104+
105+
/// Cached target info, deliberately doublely optional, one
106+
/// for "not yet cached", one for "failed to get target info".
107+
private var _cachedTargetInfo: Optional<JSON?> = nil
108+
109+
private var targetInfo: JSON? {
110+
lazyPropertiesLock.lock()
111+
defer { lazyPropertiesLock.unlock() }
112+
113+
if let cached = _cachedTargetInfo {
114+
return cached
115+
}
116+
86117
// Only call out to the swift compiler to fetch the target info when necessary
87-
try? _targetInfo ?? Self.getTargetInfo(swiftCompiler: swiftCompilerPath)
88-
}()
118+
let info = try? _targetInfo ?? Self.getTargetInfo(swiftCompiler: swiftCompilerPath)
119+
_cachedTargetInfo = info
120+
return info
121+
}
122+
123+
/// Cached swift compiler version, deliberately doublely optional, one
124+
/// for "not yet cached", one for "failed to get target info".
125+
private var _swiftCompilerVersion: Optional<String?> = nil
89126

90127
// A version string that can be used to identify the swift compiler version
91-
public lazy var swiftCompilerVersion: String? = {
92-
guard let targetInfo else { return nil }
93-
return Self.computeSwiftCompilerVersion(targetInfo: targetInfo)
94-
}()
128+
public var swiftCompilerVersion: String? {
129+
lazyPropertiesLock.lock()
130+
defer { lazyPropertiesLock.unlock() }
131+
132+
if let cached = _swiftCompilerVersion {
133+
return cached
134+
}
135+
136+
guard let targetInfo = self.targetInfo else {
137+
_swiftCompilerVersion = nil
138+
return nil
139+
}
140+
141+
let version = Self.computeSwiftCompilerVersion(targetInfo: targetInfo)
142+
_swiftCompilerVersion = version
143+
return version
144+
}
95145

96146
/// The list of CPU architectures to build for.
97147
public let architectures: [String]?

0 commit comments

Comments
 (0)