Skip to content

Commit 5e86832

Browse files
committed
Upgrade SWBUtil to Swift 6
1 parent 2e1cb56 commit 5e86832

File tree

6 files changed

+49
-31
lines changed

6 files changed

+49
-31
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ let package = Package(
209209
.product(name: "SystemPackage", package: "swift-system", condition: .when(platforms: [.linux, .openbsd, .android, .windows, .custom("freebsd")])),
210210
],
211211
exclude: ["CMakeLists.txt"],
212-
swiftSettings: swiftSettings(languageMode: .v5)),
212+
swiftSettings: swiftSettings(languageMode: .v6)),
213213
.target(
214214
name: "SWBCAS",
215215
dependencies: ["SWBUtil", "SWBCSupport"],

Sources/SWBUtil/FSProxy.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,3 +1446,10 @@ extension FileDescriptor {
14461446
return result
14471447
}
14481448
}
1449+
1450+
extension FileManager {
1451+
fileprivate func attributesOfItem(atPath path: String) throws -> [FileAttributeKey: any Sendable] {
1452+
let attributes: [FileAttributeKey: Any] = try attributesOfItem(atPath: path)
1453+
return (attributes as NSDictionary) as! [FileAttributeKey: any Sendable]
1454+
}
1455+
}

Sources/SWBUtil/HeavyCache.swift

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public func withHeavyCacheGlobalState<T>(isolated: Bool = true, _ body: () throw
6262
/// A cache designed for holding few, relatively heavy-weight objects.
6363
///
6464
/// This cache is specifically designed for holding a limited number of objects (usually less than 100) which are expensive enough to merit particular attention in terms of being purgeable under memory pressure, evictable in-mass, or cached with more complex parameters like time-to-live (TTL).
65-
public final class HeavyCache<Key: Hashable & Sendable, Value>: _HeavyCacheBase, KeyValueStorage, @unchecked Sendable {
65+
public final class HeavyCache<Key: Hashable & Sendable, Value: Sendable>: _HeavyCacheBase, KeyValueStorage, @unchecked Sendable {
6666
public typealias HeavyCacheClock = SuspendingClock
6767

6868
/// Controls the non-deterministic eviction policy of the cache. Note that this is distinct from deterministic _pruning_ (due to TTL or size limits).
@@ -74,21 +74,32 @@ public final class HeavyCache<Key: Hashable & Sendable, Value>: _HeavyCacheBase,
7474
case `default`(totalCostLimit: Int?, willEvictCallback: (@Sendable (Value) -> Void)? = nil)
7575
}
7676

77-
fileprivate final class Entry {
77+
fileprivate final class Entry: Sendable {
78+
/// Empty helper type to prove exclusive access to `accessTime` without storing a mutex for each instance.
79+
struct Witness: ~Copyable { }
80+
7881
/// The actual value.
7982
let value: Value
8083

8184
/// The last access timestamp.
82-
var accessTime: HeavyCacheClock.Instant
85+
private nonisolated(unsafe) var accessTime: HeavyCacheClock.Instant
8386

8487
init(_ value: Value, _ accessTime: HeavyCacheClock.Instant) {
8588
self.value = value
8689
self.accessTime = accessTime
8790
}
91+
92+
func accessTime(_ Witness: borrowing Witness) -> HeavyCacheClock.Instant {
93+
self.accessTime
94+
}
95+
96+
func updateAccessTime(_ accessTime: HeavyCacheClock.Instant, _ Witness: borrowing Witness) {
97+
self.accessTime = accessTime
98+
}
8899
}
89100

90101
/// The lock to protect shared instance state.
91-
private let stateLock = SWBMutex(())
102+
private let stateLock = SWBMutex(Entry.Witness())
92103

93104
/// The underlying cache.
94105
private let _cache: any HeavyCacheImpl<Key, Entry>
@@ -132,7 +143,7 @@ public final class HeavyCache<Key: Hashable & Sendable, Value>: _HeavyCacheBase,
132143

133144
deinit {
134145
_timer?.cancel()
135-
stateLock.withLock {
146+
stateLock.withLock { _ in
136147
for waiter in _expirationWaiters {
137148
waiter.resume()
138149
}
@@ -146,12 +157,12 @@ public final class HeavyCache<Key: Hashable & Sendable, Value>: _HeavyCacheBase,
146157
///
147158
/// Due to the implementation details, this may overestimate the number of active items, if some items have been recently evicted.
148159
public var count: Int {
149-
return stateLock.withLock { _keys.count }
160+
return stateLock.withLock { _ in _keys.count }
150161
}
151162

152163
/// Clear all items in the cache.
153164
public func removeAll() {
154-
stateLock.withLock {
165+
stateLock.withLock { _ in
155166
_cache.removeAll()
156167
_keys.removeAll()
157168
}
@@ -161,35 +172,35 @@ public final class HeavyCache<Key: Hashable & Sendable, Value>: _HeavyCacheBase,
161172
///
162173
/// This function is thread-safe, but may allow computing the value multiple times in case of a race.
163174
public func getOrInsert(_ key: Key, _ body: () throws -> Value) rethrows -> Value {
164-
return try stateLock.withLock {
175+
return try stateLock.withLock { witness in
165176
let entry = try _cache.getOrInsert(key) {
166177
return Entry(try body(), currentTime())
167178
}
168179
_keys.insert(key)
169-
entry.accessTime = currentTime()
170-
_pruneCache()
180+
entry.updateAccessTime(currentTime(), witness)
181+
_pruneCache(witness)
171182
return entry.value
172183
}
173184
}
174185

175186
/// Subscript access to the cache.
176187
public subscript(_ key: Key) -> Value? {
177188
get {
178-
return stateLock.withLock {
189+
return stateLock.withLock { witness in
179190
if let entry = _cache[key] {
180-
entry.accessTime = currentTime()
191+
entry.updateAccessTime(currentTime(), witness)
181192
return entry.value
182193
}
183194
return nil
184195
}
185196
}
186197
set {
187-
stateLock.withLock {
198+
stateLock.withLock { witness in
188199
if let newValue {
189200
let entry = Entry(newValue, currentTime())
190201
_cache[key] = entry
191202
_keys.insert(key)
192-
_pruneCache()
203+
_pruneCache(witness)
193204
} else {
194205
_cache.remove(key)
195206
_keys.remove(key)
@@ -206,7 +217,7 @@ public final class HeavyCache<Key: Hashable & Sendable, Value>: _HeavyCacheBase,
206217
/// Prune the cache following an insert.
207218
///
208219
/// This method is expected to be called on `queue`.
209-
private func _pruneCache() {
220+
private func _pruneCache(_ witness: borrowing Entry.Witness) {
210221
// Enforce the cache maximum size.
211222
guard let max = maximumSize else { return }
212223

@@ -223,7 +234,7 @@ public final class HeavyCache<Key: Hashable & Sendable, Value>: _HeavyCacheBase,
223234
_cache.remove(key)
224235
continue whileLoop
225236
}
226-
if oldest == nil || oldest!.entry.accessTime > entry.accessTime {
237+
if oldest == nil || oldest!.entry.accessTime(witness) > entry.accessTime(witness) {
227238
oldest = (key, entry)
228239
}
229240
}
@@ -237,7 +248,7 @@ public final class HeavyCache<Key: Hashable & Sendable, Value>: _HeavyCacheBase,
237248
/// Prune the cache based on the TTL value.
238249
///
239250
/// This method is expected to be called on `queue`.
240-
private func _pruneForTTL() {
251+
private func _pruneForTTL(_ witness: borrowing Entry.Witness) {
241252
guard let ttl = _timeToLive else { return }
242253

243254
let time = currentTime()
@@ -247,7 +258,7 @@ public final class HeavyCache<Key: Hashable & Sendable, Value>: _HeavyCacheBase,
247258
keysToRemove.append(key)
248259
continue
249260
}
250-
if time - entry.accessTime > ttl {
261+
if time - entry.accessTime(witness) > ttl {
251262
keysToRemove.append(key)
252263
}
253264
}
@@ -272,9 +283,9 @@ public final class HeavyCache<Key: Hashable & Sendable, Value>: _HeavyCacheBase,
272283
return _maximumSize
273284
}
274285
set {
275-
stateLock.withLock {
286+
stateLock.withLock { witness in
276287
_maximumSize = newValue
277-
_pruneCache()
288+
_pruneCache(witness)
278289
}
279290
}
280291
}
@@ -287,7 +298,7 @@ public final class HeavyCache<Key: Hashable & Sendable, Value>: _HeavyCacheBase,
287298
return _timeToLive
288299
}
289300
set {
290-
stateLock.withLock {
301+
stateLock.withLock { _ in
291302
_timeToLive = newValue
292303

293304
// Install the TTL timer.
@@ -298,8 +309,8 @@ public final class HeavyCache<Key: Hashable & Sendable, Value>: _HeavyCacheBase,
298309
while !Task.isCancelled {
299310
if let self = self {
300311
self.preventExpiration {
301-
self.stateLock.withLock {
302-
self._pruneForTTL()
312+
self.stateLock.withLock { witness in
313+
self._pruneForTTL(witness)
303314
}
304315
}
305316
}
@@ -326,7 +337,7 @@ public final class HeavyCache<Key: Hashable & Sendable, Value>: _HeavyCacheBase,
326337
extension HeavyCache {
327338
/// Allows freezing the current time as seen by the object, for TTL pruning testing purposes.
328339
@_spi(Testing) @discardableResult public func setTime(instant: HeavyCacheClock.Instant?) -> HeavyCacheClock.Instant {
329-
stateLock.withLock {
340+
stateLock.withLock { _ in
330341
_currentTimeTestingOverride = instant
331342
return instant ?? .now
332343
}
@@ -335,7 +346,7 @@ extension HeavyCache {
335346
/// Waits until the next time pruning for TTL occurs.
336347
@_spi(Testing) public func waitForExpiration() async {
337348
await withCheckedContinuation { continuation in
338-
stateLock.withLock {
349+
stateLock.withLock { _ in
339350
_expirationWaiters.append(continuation)
340351
}
341352
}

Sources/SWBUtil/LazyCache.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public final class Lazy<T: Sendable>: Sendable {
6565
}
6666

6767
/// Wrapper for thread-safe lazily computed values.
68-
public final class LazyCache<Class, T> {
68+
public final class LazyCache<Class, T: Sendable> {
6969
private let body: @Sendable (Class) -> T
7070
private let cachedValue = LockedValue<T?>(nil)
7171

@@ -89,7 +89,7 @@ public final class LazyCache<Class, T> {
8989
extension LazyCache: Sendable where T: Sendable {}
9090

9191
/// Wrapper for thread-safe lazily computed key-value pairs.
92-
public final class LazyKeyValueCache<Class, Key: Hashable & Sendable, Value> {
92+
public final class LazyKeyValueCache<Class, Key: Hashable & Sendable, Value: Sendable> {
9393
private let body: @Sendable (Class, Key) -> Value
9494
private let cachedValues = LockedValue<[Key: Value]>([:])
9595

Sources/SWBUtil/Process.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ extension Process {
152152
}
153153
}
154154

155-
private static func _getOutput<T, U>(url: URL, arguments: [String], currentDirectoryURL: URL?, environment: Environment?, interruptible: Bool, setup: (Process) -> T, collect: (T) async throws -> U) async throws -> (exitStatus: Processes.ExitStatus, output: U) {
155+
private static func _getOutput<T, U>(url: URL, arguments: [String], currentDirectoryURL: URL?, environment: Environment?, interruptible: Bool, setup: (Process) -> T, collect: @Sendable (T) async throws -> U) async throws -> (exitStatus: Processes.ExitStatus, output: U) {
156156
let executableFilePath = try url.standardizedFileURL.filePath
157157

158158
let process = Process()

Sources/SWBUtil/Registry.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import SWBLibc
1414

1515
/// A threadsafe mapping of hashable keys to values. Unlike a Dictionary, which has independent lookup and insertion operations, a registry has an atomic lookup-or-insert operation that takes a constructor block, which is called only if the key isn't already in the registry. The block is guaranteed to be called only once, even if multiple threads lookup-or-insert the same key at the same time (this allows it to have side effects without needing additional checking). Unlike a Cache, a Registry never discards entries based on memory pressure. Unlike a LazyCache, the value creation block is provided for each call and not just once per instance.
1616
// FIXME: We should consider whether we should combine Cache, LazyCache, and Registry, possibly with per-instance options for things like whether background deletion is allowed, whether a value creator block is guaranteed to run only once, etc. At the moment, the various clients in Swift Build depend on the semantics of the various utility types they use.
17-
public final class Registry<K: Hashable, V>: KeyValueStorage {
17+
public final class Registry<K: Hashable & Sendable, V: Sendable>: KeyValueStorage {
1818
public typealias Key = K
1919
public typealias Value = V
2020

2121
/// Underlying dictionary, which is accessed only while holding the lock.
22-
private let dict = LockedValue<Dictionary<K, V>>([:])
22+
private let dict = SWBMutex<Dictionary<K, V>>([:])
2323

2424
/// Public initializer of a new, empty registry.
2525
public init() {

0 commit comments

Comments
 (0)