Skip to content

Commit 516b83a

Browse files
committed
Use existing AsyncThrowingValueMemoizer in favour of AsyncMemoizableThreadSafeBox
1 parent 3f6c519 commit 516b83a

File tree

5 files changed

+460
-149
lines changed

5 files changed

+460
-149
lines changed

Sources/Basics/Concurrency/ThreadSafeBox.swift

Lines changed: 0 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -222,92 +222,3 @@ extension ThreadSafeBox where Value == String {
222222
}
223223

224224
extension ThreadSafeBox: @unchecked Sendable where Value: Sendable {}
225-
226-
/// Thread-safe value boxing structure that provides synchronized asynchronous memoization of a wrapped value.
227-
public final class AsyncMemoizableThreadSafeBox<Value: Sendable>: @unchecked Sendable {
228-
private var underlying: Value?
229-
private let lock = NSLock()
230-
private let asyncCoordination = AsyncMemoizationCoordinator<Value>()
231-
232-
public init() {}
233-
234-
/// Atomically retrieves the current value from the box.
235-
///
236-
/// - Returns: The current value stored in the box, or nil if none is present.
237-
public func get() -> Value? {
238-
self.lock.withLock {
239-
self.underlying
240-
}
241-
}
242-
243-
/// Atomically computes and caches a value produced by an async function, if not already present.
244-
///
245-
/// If the box already contains a non-nil value that value is returned immediately.
246-
/// Otherwise, the provided async closure is executed to compute the value, which is then
247-
/// stored and returned.
248-
///
249-
/// Concurrent calls to memoize will wait for the first call to complete and receive its result.
250-
/// If the body throws an error, all pending calls receive that error and the state is reset.
251-
///
252-
/// - Parameter body: An async closure that computes the value to store if none exists.
253-
/// - Returns: The cached value or the newly computed value.
254-
/// - Throws: Any error thrown by the computation closure.
255-
@discardableResult
256-
public func memoize(body: @Sendable @escaping () async throws -> Value) async throws -> Value {
257-
if let value = self.get() {
258-
return value
259-
}
260-
261-
// Try to register as the executor, or get the existing task
262-
let task: Task<Value, Error> = await self.asyncCoordination.getOrCreateTask {
263-
// This closure is only called by the first caller
264-
Task<Value, Error> {
265-
// Double-check after acquiring coordination
266-
if let value = self.get() {
267-
return value
268-
}
269-
270-
let value = try await body()
271-
272-
// Store the value
273-
self.lock.withLock {
274-
self.underlying = value
275-
}
276-
return value
277-
}
278-
}
279-
280-
// Everyone (including the first caller) awaits the same task
281-
do {
282-
let result = try await task.value
283-
await self.asyncCoordination.clearTask()
284-
return result
285-
} catch {
286-
await self.asyncCoordination.clearTask()
287-
throw error
288-
}
289-
}
290-
291-
// Actor for coordinating async memoization within a thread safe box
292-
private actor AsyncMemoizationCoordinator<T: Sendable>: Sendable {
293-
private var inProgressTask: Task<T, Error>?
294-
295-
/// Returns an existing task if one is in progress, or creates and stores a new one
296-
func getOrCreateTask(_ createTask: @Sendable () -> Task<T, Error>) -> Task<T, Error> {
297-
if let existingTask = inProgressTask {
298-
return existingTask
299-
}
300-
301-
// We're the first - create and store the task
302-
let task = createTask()
303-
inProgressTask = task
304-
return task
305-
}
306-
307-
/// Clears the current task
308-
func clearTask() {
309-
inProgressTask = nil
310-
}
311-
}
312-
}
313-

Sources/Build/BuildOperation.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,10 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
171171
}
172172

173173
/// The build description resulting from planing.
174-
private let buildDescription = AsyncMemoizableThreadSafeBox<BuildDescription>()
174+
private let buildDescription = AsyncThrowingValueMemoizer<BuildDescription>()
175175

176176
/// The loaded package graph.
177-
private let packageGraph = AsyncMemoizableThreadSafeBox<ModulesGraph>()
177+
private let packageGraph = AsyncThrowingValueMemoizer<ModulesGraph>()
178178

179179
/// File system to operate on.
180180
private var fileSystem: Basics.FileSystem {

Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public struct FileSystemPackageContainer: PackageContainer {
3939
private let observabilityScope: ObservabilityScope
4040

4141
/// cached version of the manifest
42-
private let manifest = AsyncMemoizableThreadSafeBox<Manifest>()
42+
private let manifest = AsyncThrowingValueMemoizer<Manifest>()
4343

4444
public init(
4545
package: PackageReference,

Sources/Workspace/PackageContainer/RegistryPackageContainer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class RegistryPackageContainer: PackageContainer {
3131
private let currentToolsVersion: ToolsVersion
3232
private let observabilityScope: ObservabilityScope
3333

34-
private var knownVersionsCache = AsyncMemoizableThreadSafeBox<[Version]>()
34+
private var knownVersionsCache = AsyncThrowingValueMemoizer<[Version]>()
3535
private var toolsVersionsCache = ThrowingAsyncKeyValueMemoizer<Version, ToolsVersion>()
3636
private var validToolsVersionsCache = AsyncKeyValueMemoizer<Version, Bool>()
3737
private var manifestsCache = ThrowingAsyncKeyValueMemoizer<Version, Manifest>()

0 commit comments

Comments
 (0)