Skip to content

Commit e8eb0a9

Browse files
committed
Finish moving package collections to async/await
1 parent 9a718f6 commit e8eb0a9

7 files changed

+462
-595
lines changed

Sources/Basics/Concurrency/ConcurrencyHelpers.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,32 @@ extension DispatchQueue {
4444
label: "swift.org.swiftpm.shared.concurrent",
4545
attributes: .concurrent
4646
)
47+
48+
/// Runs a sync closure within a DispatchQueue and returns the result by means of a continuation
49+
/// - Parameter operation: The closure to run on the queue
50+
/// - Returns: The result of closure
51+
public func awaitingAsync<T>(_ operation: @Sendable @escaping () throws -> T) async throws -> T {
52+
try await withCheckedThrowingContinuation { continuation in
53+
self.async {
54+
do {
55+
try continuation.resume(returning: operation())
56+
} catch {
57+
continuation.resume(throwing: error)
58+
}
59+
}
60+
}
61+
}
62+
63+
/// Runs a sync closure within a DispatchQueue and returns the result by means of a continuation
64+
/// - Parameter operation: the closure to run
65+
/// - Returns: result of the closure
66+
public func awaitingAsync<T>(_ operation: @Sendable @escaping () -> T) async -> T {
67+
await withCheckedContinuation { continuation in
68+
self.async {
69+
continuation.resume(returning: operation())
70+
}
71+
}
72+
}
4773
}
4874

4975
/// Bridges between potentially blocking methods that take a result completion closure and async/await

Sources/Basics/SQLiteBackedCache.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ package final class SQLiteBackedCache<Value: Codable>: Closable {
8484
}
8585
}
8686

87+
@available(*, noasync, message: "Use DispatchQueue.sharedConcurrent.awaitingAsync to run this outside of the swift concurrency pool")
8788
private func put(
8889
rawKey key: SQLite.SQLiteValue,
8990
value: Value,
@@ -140,6 +141,7 @@ package final class SQLiteBackedCache<Value: Codable>: Closable {
140141
try self.put(rawKey: .string(key), value: value, replace: replace, observabilityScope: observabilityScope)
141142
}
142143

144+
@available(*, noasync, message: "Use DispatchQueue.sharedConcurrent.awaitingAsync to run this outside of the swift concurrency pool")
143145
package func get(key: Key) throws -> Value? {
144146
let query = "SELECT value FROM \(self.tableName) WHERE key = ? LIMIT 1;"
145147
return try self.executeStatement(query) { statement -> Value? in
@@ -151,6 +153,7 @@ package final class SQLiteBackedCache<Value: Codable>: Closable {
151153
}
152154
}
153155

156+
@available(*, noasync, message: "Use DispatchQueue.sharedConcurrent.awaitingAsync to run this outside of the swift concurrency pool")
154157
package func get(blobKey key: some Sequence<UInt8>) throws -> Value? {
155158
let query = "SELECT value FROM \(self.tableName) WHERE key = ? LIMIT 1;"
156159
return try self.executeStatement(query) { statement -> Value? in
@@ -162,6 +165,7 @@ package final class SQLiteBackedCache<Value: Codable>: Closable {
162165
}
163166
}
164167

168+
@available(*, noasync, message: "Use DispatchQueue.sharedConcurrent.awaitingAsync to run this outside of the swift concurrency pool")
165169
package func remove(key: Key) throws {
166170
let query = "DELETE FROM \(self.tableName) WHERE key = ?;"
167171
try self.executeStatement(query) { statement in
@@ -170,6 +174,7 @@ package final class SQLiteBackedCache<Value: Codable>: Closable {
170174
}
171175
}
172176

177+
@available(*, noasync, message: "Use DispatchQueue.sharedConcurrent.awaitingAsync to run this outside of the swift concurrency pool")
173178
@discardableResult
174179
private func executeStatement<T>(_ query: String, _ body: (SQLite.PreparedStatement) throws -> T) throws -> T {
175180
try self.withDB { db in

Sources/PackageCollections/PackageIndex.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ struct PackageIndex: PackageIndexProtocol, Closable {
6666
location: String?
6767
) async throws -> PackageCollectionsModel.PackageMetadata {
6868
let url = try await self.urlIfConfigured()
69-
if let cached = try? self.cache?.get(key: identity.description),
69+
let cached = await DispatchQueue.sharedConcurrent.awaitingAsync {
70+
try? self.cache?.get(key: identity.description)
71+
}
72+
if let cached,
7073
cached.dispatchTime + DispatchTimeInterval.seconds(self.configuration.cacheTTLInSeconds) > DispatchTime.now() {
7174
return (package: cached.package, collections: [], provider: self.createContext(host: url.host, error: nil))
7275
}

Sources/PackageCollections/Providers/GitHubPackageMetadataProvider.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ struct GitHubPackageMetadataProvider: PackageMetadataProvider, Closable {
6565
}
6666
// TODO: baseURL.host is used / unwrapped repeatedly does it make sense to proceed if baseURL.host is nil?
6767

68-
if let cached = try? self.cache?.get(key: identity.description) {
68+
let cached = await DispatchQueue.sharedConcurrent.awaitingAsync {
69+
try? self.cache?.get(key: identity.description)
70+
}
71+
if let cached {
6972
if cached.dispatchTime + DispatchTimeInterval.seconds(self.configuration.cacheTTLInSeconds) > DispatchTime.now() {
7073
return (.success(cached.package), self.createContext(apiHost: baseURL.host, error: nil))
7174
}

Sources/PackageCollections/Storage/FilePackageCollectionsSourcesStorage.swift

Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,6 @@ import class Foundation.JSONDecoder
1818
import class Foundation.JSONEncoder
1919
import struct Foundation.URL
2020

21-
extension DispatchQueue {
22-
func awaitingAsync<T>(_ closure: @escaping () throws -> T) async throws -> T {
23-
try await withCheckedThrowingContinuation { continuation in
24-
self.async {
25-
do {
26-
try continuation.resume(returning: closure())
27-
} catch {
28-
continuation.resume(throwing: error)
29-
}
30-
}
31-
}
32-
}
33-
}
34-
3521
struct FilePackageCollectionsSourcesStorage: PackageCollectionsSourcesStorage {
3622
let fileSystem: FileSystem
3723
let path: AbsolutePath
@@ -47,64 +33,58 @@ struct FilePackageCollectionsSourcesStorage: PackageCollectionsSourcesStorage {
4733
self.decoder = JSONDecoder.makeWithDefaults()
4834
}
4935

50-
func list() async throws -> [PackageCollectionsModel.CollectionSource] {
36+
func asyncWithLock<T>(_ operation: @escaping () throws -> T) async throws -> T {
5137
try await DispatchQueue.sharedConcurrent.awaitingAsync {
52-
try self.withLock {
53-
try self.loadFromDisk()
54-
}
38+
try self.withLock(operation)
39+
}
40+
}
41+
42+
func list() async throws -> [PackageCollectionsModel.CollectionSource] {
43+
try await self.asyncWithLock {
44+
try self.loadFromDisk()
5545
}
5646
}
5747

5848
func add(source: PackageCollectionsModel.CollectionSource, order: Int? = nil) async throws {
59-
try await DispatchQueue.sharedConcurrent.awaitingAsync {
60-
try self.withLock {
61-
var sources = try self.loadFromDisk()
62-
sources = sources.filter { $0 != source }
63-
let order = order.flatMap { $0 >= 0 && $0 < sources.endIndex ? order : sources.endIndex } ?? sources.endIndex
64-
sources.insert(source, at: order)
65-
try self.saveToDisk(sources)
66-
}
49+
try await self.asyncWithLock {
50+
var sources = try self.loadFromDisk()
51+
sources = sources.filter { $0 != source }
52+
let order = order.flatMap { $0 >= 0 && $0 < sources.endIndex ? order : sources.endIndex } ?? sources.endIndex
53+
sources.insert(source, at: order)
54+
try self.saveToDisk(sources)
6755
}
6856
}
6957

7058
func remove(source: PackageCollectionsModel.CollectionSource) async throws {
71-
try await DispatchQueue.sharedConcurrent.awaitingAsync {
72-
try self.withLock {
73-
var sources = try self.loadFromDisk()
74-
sources = sources.filter { $0 != source }
75-
try self.saveToDisk(sources)
76-
}
59+
try await self.asyncWithLock {
60+
var sources = try self.loadFromDisk()
61+
sources = sources.filter { $0 != source }
62+
try self.saveToDisk(sources)
7763
}
7864
}
7965

8066
func move(source: PackageCollectionsModel.CollectionSource, to order: Int) async throws {
81-
try await DispatchQueue.sharedConcurrent.awaitingAsync {
82-
try self.withLock {
83-
var sources = try self.loadFromDisk()
84-
sources = sources.filter { $0 != source }
85-
let order = order >= 0 && order < sources.endIndex ? order : sources.endIndex
86-
sources.insert(source, at: order)
87-
try self.saveToDisk(sources)
88-
}
67+
try await self.asyncWithLock {
68+
var sources = try self.loadFromDisk()
69+
sources = sources.filter { $0 != source }
70+
let order = order >= 0 && order < sources.endIndex ? order : sources.endIndex
71+
sources.insert(source, at: order)
72+
try self.saveToDisk(sources)
8973
}
9074
}
9175

9276
func exists(source: PackageCollectionsModel.CollectionSource) async throws -> Bool {
93-
try await DispatchQueue.sharedConcurrent.awaitingAsync {
94-
try self.withLock {
95-
try self.loadFromDisk()
96-
}.contains(source)
97-
}
77+
try await self.asyncWithLock {
78+
try self.loadFromDisk()
79+
}.contains(source)
9880
}
9981

10082
func update(source: PackageCollectionsModel.CollectionSource) async throws {
101-
try await DispatchQueue.sharedConcurrent.awaitingAsync {
102-
try self.withLock {
103-
var sources = try self.loadFromDisk()
104-
if let index = sources.firstIndex(where: { $0 == source }) {
105-
sources[index] = source
106-
try self.saveToDisk(sources)
107-
}
83+
try await self.asyncWithLock {
84+
var sources = try self.loadFromDisk()
85+
if let index = sources.firstIndex(where: { $0 == source }) {
86+
sources[index] = source
87+
try self.saveToDisk(sources)
10888
}
10989
}
11090
}

Sources/PackageCollections/Storage/PackageCollectionsStorage.swift

Lines changed: 14 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -18,48 +18,35 @@ public protocol PackageCollectionsStorage {
1818
///
1919
/// - Parameters:
2020
/// - collection: The `PackageCollection`
21-
/// - callback: The closure to invoke when result becomes available
22-
@available(*, noasync, message: "Use the async alternative")
23-
func put(collection: PackageCollectionsModel.Collection,
24-
callback: @escaping (Result<PackageCollectionsModel.Collection, Error>) -> Void)
21+
func put(collection: PackageCollectionsModel.Collection) async throws -> PackageCollectionsModel.Collection
2522

2623
/// Removes `PackageCollection` from storage.
2724
///
2825
/// - Parameters:
2926
/// - identifier: The identifier of the `PackageCollection`
30-
/// - callback: The closure to invoke when result becomes available
31-
@available(*, noasync, message: "Use the async alternative")
32-
func remove(identifier: PackageCollectionsModel.CollectionIdentifier,
33-
callback: @escaping (Result<Void, Error>) -> Void)
27+
func remove(identifier: PackageCollectionsModel.CollectionIdentifier) async throws
3428

3529
/// Returns `PackageCollection` for the given identifier.
3630
///
3731
/// - Parameters:
3832
/// - identifier: The identifier of the `PackageCollection`
39-
/// - callback: The closure to invoke when result becomes available
40-
@available(*, noasync, message: "Use the async alternative")
41-
func get(identifier: PackageCollectionsModel.CollectionIdentifier,
42-
callback: @escaping (Result<PackageCollectionsModel.Collection, Error>) -> Void)
33+
func get(identifier: PackageCollectionsModel.CollectionIdentifier) async throws -> PackageCollectionsModel.Collection
4334

4435
/// Returns `PackageCollection`s for the given identifiers, or all if none specified.
4536
///
4637
/// - Parameters:
4738
/// - identifiers: Optional. The identifiers of the `PackageCollection`
48-
/// - callback: The closure to invoke when result becomes available
49-
@available(*, noasync, message: "Use the async alternative")
50-
func list(identifiers: [PackageCollectionsModel.CollectionIdentifier]?,
51-
callback: @escaping (Result<[PackageCollectionsModel.Collection], Error>) -> Void)
39+
func list(identifiers: [PackageCollectionsModel.CollectionIdentifier]?) async throws -> [PackageCollectionsModel.Collection]
5240

5341
/// Returns `PackageSearchResult` for the given search criteria.
5442
///
5543
/// - Parameters:
5644
/// - identifiers: Optional. The identifiers of the `PackageCollection`s
5745
/// - query: The search query expression
58-
/// - callback: The closure to invoke when result becomes available
59-
@available(*, noasync, message: "Use the async alternative")
60-
func searchPackages(identifiers: [PackageCollectionsModel.CollectionIdentifier]?,
61-
query: String,
62-
callback: @escaping (Result<PackageCollectionsModel.PackageSearchResult, Error>) -> Void)
46+
func searchPackages(
47+
identifiers: [PackageCollectionsModel.CollectionIdentifier]?,
48+
query: String
49+
) async throws -> PackageCollectionsModel.PackageSearchResult
6350

6451
/// Returns packages for the given package identity.
6552
///
@@ -68,72 +55,20 @@ public protocol PackageCollectionsStorage {
6855
/// - Parameters:
6956
/// - identifier: The package identifier
7057
/// - collectionIdentifiers: Optional. The identifiers of the `PackageCollection`s
71-
/// - callback: The closure to invoke when result becomes available
72-
@available(*, noasync, message: "Use the async alternative")
73-
func findPackage(identifier: PackageIdentity,
74-
collectionIdentifiers: [PackageCollectionsModel.CollectionIdentifier]?,
75-
callback: @escaping (Result<(packages: [PackageCollectionsModel.Package], collections: [PackageCollectionsModel.CollectionIdentifier]), Error>) -> Void)
58+
func findPackage(
59+
identifier: PackageIdentity,
60+
collectionIdentifiers: [PackageCollectionsModel.CollectionIdentifier]?
61+
) async throws -> (packages: [PackageCollectionsModel.Package], collections: [PackageCollectionsModel.CollectionIdentifier])
7662

7763
/// Returns `TargetSearchResult` for the given search criteria.
7864
///
7965
/// - Parameters:
8066
/// - identifiers: Optional. The identifiers of the `PackageCollection`
8167
/// - query: The search query expression
8268
/// - type: The search type
83-
/// - callback: The closure to invoke when result becomes available
84-
@available(*, noasync, message: "Use the async alternative")
85-
func searchTargets(identifiers: [PackageCollectionsModel.CollectionIdentifier]?,
86-
query: String,
87-
type: PackageCollectionsModel.TargetSearchType,
88-
callback: @escaping (Result<PackageCollectionsModel.TargetSearchResult, Error>) -> Void)
89-
}
90-
91-
public extension PackageCollectionsStorage {
92-
func put(collection: PackageCollectionsModel.Collection) async throws -> PackageCollectionsModel.Collection {
93-
try await safe_async {
94-
self.put(collection: collection, callback: $0)
95-
}
96-
}
97-
func remove(identifier: PackageCollectionsModel.CollectionIdentifier) async throws {
98-
try await safe_async {
99-
self.remove(identifier: identifier, callback: $0)
100-
}
101-
}
102-
func get(identifier: PackageCollectionsModel.CollectionIdentifier) async throws -> PackageCollectionsModel.Collection {
103-
try await safe_async {
104-
self.get(identifier: identifier, callback: $0)
105-
}
106-
}
107-
func list(identifiers: [PackageCollectionsModel.CollectionIdentifier]? = nil) async throws -> [PackageCollectionsModel.Collection] {
108-
try await safe_async {
109-
self.list(identifiers: identifiers, callback: $0)
110-
}
111-
}
112-
113-
func searchPackages(
114-
identifiers: [PackageCollectionsModel.CollectionIdentifier]? = nil,
115-
query: String
116-
) async throws -> PackageCollectionsModel.PackageSearchResult {
117-
try await safe_async {
118-
self.searchPackages(identifiers: identifiers, query: query, callback: $0)
119-
}
120-
}
121-
func findPackage(
122-
identifier: PackageIdentity,
123-
collectionIdentifiers: [PackageCollectionsModel.CollectionIdentifier]? = nil
124-
) async throws -> (packages: [PackageCollectionsModel.Package], collections: [PackageCollectionsModel.CollectionIdentifier]) {
125-
try await safe_async {
126-
self.findPackage(identifier: identifier, collectionIdentifiers: collectionIdentifiers, callback: $0)
127-
}
128-
}
129-
13069
func searchTargets(
131-
identifiers: [PackageCollectionsModel.CollectionIdentifier]? = nil,
70+
identifiers: [PackageCollectionsModel.CollectionIdentifier]?,
13271
query: String,
13372
type: PackageCollectionsModel.TargetSearchType
134-
) async throws -> PackageCollectionsModel.TargetSearchResult {
135-
try await safe_async {
136-
self.searchTargets(identifiers: identifiers, query: query, type: type, callback: $0)
137-
}
138-
}
73+
) async throws -> PackageCollectionsModel.TargetSearchResult
13974
}

0 commit comments

Comments
 (0)