Skip to content

Commit b8a8cad

Browse files
committed
Support custom preparationBatchSize defined via SourceKit's options
1 parent 766f2c8 commit b8a8cad

File tree

6 files changed

+37
-11
lines changed

6 files changed

+37
-11
lines changed

Documentation/Configuration File.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,4 @@ The structure of the file is currently not guaranteed to be stable. Options may
5959
- `workDoneProgressDebounceDuration: number`: When a task is started that should be displayed to the client as a work done progress, how many milliseconds to wait before actually starting the work done progress. This prevents flickering of the work done progress in the client for short-lived index tasks which end within this duration.
6060
- `sourcekitdRequestTimeout: number`: The maximum duration that a sourcekitd request should be allowed to execute before being declared as timed out. In general, editors should cancel requests that they are no longer interested in, but in case editors don't cancel requests, this ensures that a long-running non-cancelled request is not blocking sourcekitd and thus most semantic functionality. In particular, VS Code does not cancel the semantic tokens request, which can cause a long-running AST build that blocks sourcekitd.
6161
- `semanticServiceRestartTimeout: number`: If a request to sourcekitd or clangd exceeds this timeout, we assume that the semantic service provider is hanging for some reason and won't recover. To restore semantic functionality, we terminate and restart it.
62+
- `preparationBatchSize: integer`: The number of targets to prepare in parallel. If nil, SourceKit-LSP will choose a batch size.

Sources/SKOptions/SourceKitLSPOptions.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,10 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
433433
return .seconds(300)
434434
}
435435

436+
/// The number of targets to prepare in parallel.
437+
/// If nil, SourceKit-LSP will choose a batch size.
438+
public var preparationBatchSize: Int? = nil
439+
436440
public init(
437441
swiftPM: SwiftPMOptions? = .init(),
438442
fallbackBuildSystem: FallbackBuildSystemOptions? = .init(),
@@ -451,7 +455,8 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
451455
swiftPublishDiagnosticsDebounceDuration: Double? = nil,
452456
workDoneProgressDebounceDuration: Double? = nil,
453457
sourcekitdRequestTimeout: Double? = nil,
454-
semanticServiceRestartTimeout: Double? = nil
458+
semanticServiceRestartTimeout: Double? = nil,
459+
preparationBatchSize: Int? = nil
455460
) {
456461
self.swiftPM = swiftPM
457462
self.fallbackBuildSystem = fallbackBuildSystem
@@ -471,6 +476,7 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
471476
self.workDoneProgressDebounceDuration = workDoneProgressDebounceDuration
472477
self.sourcekitdRequestTimeout = sourcekitdRequestTimeout
473478
self.semanticServiceRestartTimeout = semanticServiceRestartTimeout
479+
self.preparationBatchSize = preparationBatchSize
474480
}
475481

476482
public init?(fromLSPAny lspAny: LSPAny?) throws {
@@ -535,7 +541,8 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
535541
workDoneProgressDebounceDuration: override?.workDoneProgressDebounceDuration
536542
?? base.workDoneProgressDebounceDuration,
537543
sourcekitdRequestTimeout: override?.sourcekitdRequestTimeout ?? base.sourcekitdRequestTimeout,
538-
semanticServiceRestartTimeout: override?.semanticServiceRestartTimeout ?? base.semanticServiceRestartTimeout
544+
semanticServiceRestartTimeout: override?.semanticServiceRestartTimeout ?? base.semanticServiceRestartTimeout,
545+
preparationBatchSize: override?.preparationBatchSize ?? base.preparationBatchSize
539546
)
540547
}
541548

Sources/SemanticIndex/PreparationTaskDescription.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ package struct PreparationTaskDescription: IndexTaskDescription {
4848
/// Hooks that should be called when the preparation task finishes.
4949
private let hooks: IndexHooks
5050

51+
private let purpose: TargetPreparationPurpose
52+
5153
/// The task is idempotent because preparing the same target twice produces the same result as preparing it once.
5254
package var isIdempotent: Bool { true }
5355

@@ -69,13 +71,15 @@ package struct PreparationTaskDescription: IndexTaskDescription {
6971
@escaping @Sendable (
7072
_ message: String, _ type: WindowMessageType, _ structure: LanguageServerProtocol.StructuredLogKind
7173
) -> Void,
72-
hooks: IndexHooks
74+
hooks: IndexHooks,
75+
purpose: TargetPreparationPurpose
7376
) {
7477
self.targetsToPrepare = targetsToPrepare
7578
self.buildServerManager = buildServerManager
7679
self.preparationUpToDateTracker = preparationUpToDateTracker
7780
self.logMessageToIndexLog = logMessageToIndexLog
7881
self.hooks = hooks
82+
self.purpose = purpose
7983
}
8084

8185
package func execute() async {
@@ -121,11 +125,9 @@ package struct PreparationTaskDescription: IndexTaskDescription {
121125
to currentlyExecutingTasks: [PreparationTaskDescription]
122126
) -> [TaskDependencyAction<PreparationTaskDescription>] {
123127
return currentlyExecutingTasks.compactMap { (other) -> TaskDependencyAction<PreparationTaskDescription>? in
124-
if other.targetsToPrepare.count > self.targetsToPrepare.count {
125-
// If there is an prepare operation with more targets already running, suspend it.
126-
// The most common use case for this is if we prepare all targets simultaneously during the initial preparation
127-
// when a project is opened and need a single target indexed for user interaction. We should suspend the
128-
// workspace-wide preparation and just prepare the currently needed target.
128+
if other.purpose == .forIndexing && self.purpose == .forEditorFunctionality {
129+
// If we're running a background indexing operation but need a target indexed for user interaction,
130+
// we should prioritize the latter.
129131
return .cancelAndRescheduleDependency(other)
130132
}
131133
return .waitAndElevatePriorityOfDependency(other)

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ private struct InProgressPrepareForEditorTask {
154154
}
155155

156156
/// The reason why a target is being prepared. This is used to determine the `IndexProgressStatus`.
157-
private enum TargetPreparationPurpose: Comparable {
157+
package enum TargetPreparationPurpose: Comparable {
158158
/// We are preparing the target so we can index files in it.
159159
case forIndexing
160160

@@ -232,6 +232,9 @@ package final actor SemanticIndexManager {
232232
/// The parameter is the number of files that were scheduled to be indexed.
233233
private let indexTasksWereScheduled: @Sendable (_ numberOfFileScheduled: Int) -> Void
234234

235+
/// The size of the batches in which the `SemanticIndexManager` should dispatch preparation tasks.
236+
private let preparationBatchSize: Int?
237+
235238
/// Callback that is called when `progressStatus` might have changed.
236239
private let indexProgressStatusDidChange: @Sendable () -> Void
237240

@@ -271,6 +274,7 @@ package final actor SemanticIndexManager {
271274
updateIndexStoreTimeout: Duration,
272275
hooks: IndexHooks,
273276
indexTaskScheduler: TaskScheduler<AnyIndexTaskDescription>,
277+
preparationBatchSize: Int?,
274278
logMessageToIndexLog:
275279
@escaping @Sendable (
276280
_ message: String, _ type: WindowMessageType, _ structure: LanguageServerProtocol.StructuredLogKind
@@ -283,6 +287,7 @@ package final actor SemanticIndexManager {
283287
self.updateIndexStoreTimeout = updateIndexStoreTimeout
284288
self.hooks = hooks
285289
self.indexTaskScheduler = indexTaskScheduler
290+
self.preparationBatchSize = preparationBatchSize
286291
self.logMessageToIndexLog = logMessageToIndexLog
287292
self.indexTasksWereScheduled = indexTasksWereScheduled
288293
self.indexProgressStatusDidChange = indexProgressStatusDidChange
@@ -672,7 +677,8 @@ package final actor SemanticIndexManager {
672677
buildServerManager: self.buildServerManager,
673678
preparationUpToDateTracker: preparationUpToDateTracker,
674679
logMessageToIndexLog: logMessageToIndexLog,
675-
hooks: hooks
680+
hooks: hooks,
681+
purpose: purpose
676682
)
677683
)
678684
if Task.isCancelled {
@@ -926,7 +932,11 @@ package final actor SemanticIndexManager {
926932
// TODO: When we can index multiple targets concurrently in SwiftPM, increase the batch size to half the
927933
// processor count, so we can get parallelism during preparation.
928934
// (https://github.com/swiftlang/sourcekit-lsp/issues/1262)
929-
for targetsBatch in sortedTargets.partition(intoBatchesOfSize: 1) {
935+
let defaultBatchSize = 1
936+
let batchSize = max(preparationBatchSize ?? defaultBatchSize, 1)
937+
let partitionedTargets = sortedTargets.partition(intoBatchesOfSize: batchSize)
938+
939+
for targetsBatch in partitionedTargets {
930940
let preparationTaskID = UUID()
931941
let filesToIndex = targetsBatch.flatMap({ filesByTarget[$0]! })
932942

Sources/SourceKitLSP/Workspace.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ package final class Workspace: Sendable, BuildServerManagerDelegate {
157157
updateIndexStoreTimeout: options.indexOrDefault.updateIndexStoreTimeoutOrDefault,
158158
hooks: hooks.indexHooks,
159159
indexTaskScheduler: indexTaskScheduler,
160+
preparationBatchSize: options.preparationBatchSize,
160161
logMessageToIndexLog: { [weak sourceKitLSPServer] in
161162
sourceKitLSPServer?.logMessageToIndexLog(message: $0, type: $1, structure: $2)
162163
},

config.schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@
181181
},
182182
"type" : "object"
183183
},
184+
"preparationBatchSize" : {
185+
"description" : "The number of targets to prepare in parallel. If nil, SourceKit-LSP will choose a batch size.",
186+
"markdownDescription" : "The number of targets to prepare in parallel. If nil, SourceKit-LSP will choose a batch size.",
187+
"type" : "integer"
188+
},
184189
"semanticServiceRestartTimeout" : {
185190
"description" : "If a request to sourcekitd or clangd exceeds this timeout, we assume that the semantic service provider is hanging for some reason and won't recover. To restore semantic functionality, we terminate and restart it.",
186191
"markdownDescription" : "If a request to sourcekitd or clangd exceeds this timeout, we assume that the semantic service provider is hanging for some reason and won't recover. To restore semantic functionality, we terminate and restart it.",

0 commit comments

Comments
 (0)