Skip to content

Commit ed5655a

Browse files
authored
Merge pull request #1328 from ahoppen/parallel-indexing
2 parents 69a584c + 3f7d9a7 commit ed5655a

14 files changed

+320
-155
lines changed

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ public final class TestSourceKitLSPClient: MessageHandler {
6969
///
7070
/// Conceptually, this is an array of `RequestHandler<any RequestType>` but
7171
/// since we can't express this in the Swift type system, we use `[Any]`.
72-
private nonisolated(unsafe) var requestHandlers = ThreadSafeBox<[Any]>(initialValue: [])
72+
///
73+
/// `isOneShort` if the request handler should only serve a single request and should be removed from
74+
/// `requestHandlers` after it has been called.
75+
private nonisolated(unsafe) var requestHandlers: ThreadSafeBox<[(requestHandler: Any, isOneShot: Bool)]> =
76+
ThreadSafeBox(initialValue: [])
7377

7478
/// A closure that is called when the `TestSourceKitLSPClient` is destructed.
7579
///
@@ -147,7 +151,7 @@ public final class TestSourceKitLSPClient: MessageHandler {
147151
throw ConflictingDiagnosticsError()
148152
}
149153
capabilities.textDocument!.diagnostic = .init(dynamicRegistration: true)
150-
self.handleNextRequest { (request: RegisterCapabilityRequest) in
154+
self.handleSingleRequest { (request: RegisterCapabilityRequest) in
151155
XCTAssertEqual(request.registrations.only?.method, DocumentDiagnosticsRequest.method)
152156
return VoidResponse()
153157
}
@@ -258,22 +262,22 @@ public final class TestSourceKitLSPClient: MessageHandler {
258262
}
259263
}
260264

261-
/// Handle the next request that is sent to the client with the given handler.
262-
///
263-
/// By default, `TestSourceKitLSPClient` emits an `XCTFail` if a request is sent
264-
/// to the client, since it doesn't know how to handle it. This allows the
265-
/// simulation of a single request's handling on the client.
265+
/// Handle the next request of the given type that is sent to the client.
266266
///
267-
/// If the next request that is sent to the client is of a different kind than
268-
/// the given handler, `TestSourceKitLSPClient` will emit an `XCTFail`.
269-
public func handleNextRequest<R: RequestType>(_ requestHandler: @escaping RequestHandler<R>) {
270-
requestHandlers.value.append(requestHandler)
267+
/// The request handler will only handle a single request. If the request is called again, the request handler won't
268+
/// call again
269+
public func handleSingleRequest<R: RequestType>(_ requestHandler: @escaping RequestHandler<R>) {
270+
requestHandlers.value.append((requestHandler: requestHandler, isOneShot: true))
271+
}
272+
273+
/// Handle all requests of the given type that are sent to the client.
274+
public func handleMultipleRequests<R: RequestType>(_ requestHandler: @escaping RequestHandler<R>) {
275+
requestHandlers.value.append((requestHandler: requestHandler, isOneShot: false))
271276
}
272277

273278
// MARK: - Conformance to MessageHandler
274279

275-
/// - Important: Implementation detail of `TestSourceKitLSPServer`. Do not call
276-
/// from tests.
280+
/// - Important: Implementation detail of `TestSourceKitLSPServer`. Do not call from tests.
277281
public func handle(_ params: some NotificationType) {
278282
notificationYielder.yield(params)
279283
}
@@ -285,19 +289,21 @@ public final class TestSourceKitLSPClient: MessageHandler {
285289
reply: @escaping (LSPResult<Request.Response>) -> Void
286290
) {
287291
requestHandlers.withLock { requestHandlers in
288-
let requestHandlerAndIndex = requestHandlers.enumerated().compactMap {
289-
(index, handler) -> (RequestHandler<Request>, Int)? in
290-
guard let handler = handler as? RequestHandler<Request> else {
292+
let requestHandlerIndexAndIsOneShot = requestHandlers.enumerated().compactMap {
293+
(index, handlerAndIsOneShot) -> (RequestHandler<Request>, Int, Bool)? in
294+
guard let handler = handlerAndIsOneShot.requestHandler as? RequestHandler<Request> else {
291295
return nil
292296
}
293-
return (handler, index)
297+
return (handler, index, handlerAndIsOneShot.isOneShot)
294298
}.first
295-
guard let (requestHandler, index) = requestHandlerAndIndex else {
299+
guard let (requestHandler, index, isOneShot) = requestHandlerIndexAndIsOneShot else {
296300
reply(.failure(.methodNotFound(Request.method)))
297301
return
298302
}
299303
reply(.success(requestHandler(params)))
300-
requestHandlers.remove(at: index)
304+
if isOneShot {
305+
requestHandlers.remove(at: index)
306+
}
301307
}
302308
}
303309

Sources/SemanticIndex/PreparationTaskDescription.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ public struct PreparationTaskDescription: IndexTaskDescription {
7878
subsystem: "org.swift.sourcekit-lsp.indexing",
7979
scope: "preparation-\(id % 100)"
8080
) {
81-
await testHooks.preparationTaskDidStart?(self)
8281
let targetsToPrepare = await targetsToPrepare.asyncFilter {
8382
await !preparationUpToDateStatus.isUpToDate($0)
8483
}.sorted(by: {
@@ -87,6 +86,7 @@ public struct PreparationTaskDescription: IndexTaskDescription {
8786
if targetsToPrepare.isEmpty {
8887
return
8988
}
89+
await testHooks.preparationTaskDidStart?(self)
9090

9191
let targetsToPrepareDescription =
9292
targetsToPrepare

Sources/SemanticIndex/TestHooks.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,20 @@ public struct IndexTestHooks: Sendable {
1616

1717
public var preparationTaskDidFinish: (@Sendable (PreparationTaskDescription) async -> Void)?
1818

19+
public var updateIndexStoreTaskDidStart: (@Sendable (UpdateIndexStoreTaskDescription) async -> Void)?
20+
1921
/// A callback that is called when an index task finishes.
2022
public var updateIndexStoreTaskDidFinish: (@Sendable (UpdateIndexStoreTaskDescription) async -> Void)?
2123

2224
public init(
2325
preparationTaskDidStart: (@Sendable (PreparationTaskDescription) async -> Void)? = nil,
2426
preparationTaskDidFinish: (@Sendable (PreparationTaskDescription) async -> Void)? = nil,
27+
updateIndexStoreTaskDidStart: (@Sendable (UpdateIndexStoreTaskDescription) async -> Void)? = nil,
2528
updateIndexStoreTaskDidFinish: (@Sendable (UpdateIndexStoreTaskDescription) async -> Void)? = nil
2629
) {
2730
self.preparationTaskDidStart = preparationTaskDidStart
2831
self.preparationTaskDidFinish = preparationTaskDidFinish
32+
self.updateIndexStoreTaskDidStart = updateIndexStoreTaskDidStart
2933
self.updateIndexStoreTaskDidFinish = updateIndexStoreTaskDidFinish
3034
}
3135
}

Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import class TSCBasic.Process
2222

2323
private nonisolated(unsafe) var updateIndexStoreIDForLogging = AtomicUInt32(initialValue: 1)
2424

25-
enum FileToIndex: CustomLogStringConvertible {
25+
public enum FileToIndex: CustomLogStringConvertible {
2626
/// A non-header file
2727
case indexableFile(DocumentURI)
2828

@@ -33,7 +33,7 @@ enum FileToIndex: CustomLogStringConvertible {
3333
///
3434
/// This file might be a header file that doesn't have build settings associated with it. For the actual compiler
3535
/// invocation that updates the index store, the `mainFile` should be used.
36-
var sourceFile: DocumentURI {
36+
public var sourceFile: DocumentURI {
3737
switch self {
3838
case .indexableFile(let uri): return uri
3939
case .headerFile(header: let header, mainFile: _): return header
@@ -51,7 +51,7 @@ enum FileToIndex: CustomLogStringConvertible {
5151
}
5252
}
5353

54-
var description: String {
54+
public var description: String {
5555
switch self {
5656
case .indexableFile(let uri):
5757
return uri.description
@@ -60,7 +60,7 @@ enum FileToIndex: CustomLogStringConvertible {
6060
}
6161
}
6262

63-
var redactedDescription: String {
63+
public var redactedDescription: String {
6464
switch self {
6565
case .indexableFile(let uri):
6666
return uri.redactedDescription
@@ -71,9 +71,9 @@ enum FileToIndex: CustomLogStringConvertible {
7171
}
7272

7373
/// A file to index and the target in which the file should be indexed.
74-
struct FileAndTarget {
75-
let file: FileToIndex
76-
let target: ConfiguredTarget
74+
public struct FileAndTarget: Sendable {
75+
public let file: FileToIndex
76+
public let target: ConfiguredTarget
7777
}
7878

7979
/// Describes a task to index a set of source files.
@@ -84,7 +84,7 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
8484
public let id = updateIndexStoreIDForLogging.fetchAndIncrement()
8585

8686
/// The files that should be indexed.
87-
private let filesToIndex: [FileAndTarget]
87+
public let filesToIndex: [FileAndTarget]
8888

8989
/// The build system manager that is used to get the toolchain and build settings for the files to index.
9090
private let buildSystemManager: BuildSystemManager
@@ -140,6 +140,8 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
140140
) {
141141
let startDate = Date()
142142

143+
await testHooks.updateIndexStoreTaskDidStart?(self)
144+
143145
let filesToIndexDescription = filesToIndex.map {
144146
$0.file.sourceFile.fileURL?.lastPathComponent ?? $0.file.sourceFile.stringValue
145147
}
@@ -166,9 +168,7 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
166168
) -> [TaskDependencyAction<UpdateIndexStoreTaskDescription>] {
167169
let selfMainFiles = Set(filesToIndex.map(\.file.mainFile))
168170
return currentlyExecutingTasks.compactMap { (other) -> TaskDependencyAction<UpdateIndexStoreTaskDescription>? in
169-
guard
170-
!other.filesToIndex.lazy.map(\.file.mainFile).contains(where: { selfMainFiles.contains($0) })
171-
else {
171+
if !other.filesToIndex.lazy.map(\.file.mainFile).contains(where: { selfMainFiles.contains($0) }) {
172172
// Disjoint sets of files can be indexed concurrently.
173173
return nil
174174
}

Sources/sourcekit-lsp/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ target_link_libraries(sourcekit-lsp PRIVATE
55
Diagnose
66
LanguageServerProtocol
77
LanguageServerProtocolJSONRPC
8+
SemanticIndex
89
SKCore
910
SKSupport
1011
SourceKitLSP

Tests/SourceKitDTests/CrashRecoveryTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ final class CrashRecoveryTests: XCTestCase {
7575
"Sanity check failed. The Hover response did not contain foo(), even before crashing sourcekitd. Received response: \(String(describing: preCrashHoverResponse))"
7676
)
7777

78-
testClient.handleNextRequest { (request: CreateWorkDoneProgressRequest) -> VoidResponse in
78+
testClient.handleSingleRequest { (request: CreateWorkDoneProgressRequest) -> VoidResponse in
7979
return VoidResponse()
8080
}
8181

0 commit comments

Comments
 (0)