From 5cce99b920fcf55ebd9eab02efa830076e7fd4f8 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 9 May 2024 16:52:58 -0700 Subject: [PATCH] Make `IndexTaskDescription` protocol-based instead of enum-based This simplifies the implementation. --- .../SemanticIndex/IndexTaskDescription.swift | 110 +++++++----------- .../PreparationTaskDescription.swift | 4 +- .../SemanticIndex/SemanticIndexManager.swift | 16 +-- .../UpdateIndexStoreTaskDescription.swift | 3 +- Sources/SourceKitLSP/SourceKitLSPServer.swift | 2 +- Sources/SourceKitLSP/Workspace.swift | 8 +- 6 files changed, 62 insertions(+), 81 deletions(-) diff --git a/Sources/SemanticIndex/IndexTaskDescription.swift b/Sources/SemanticIndex/IndexTaskDescription.swift index e2e2c21f..33f032dc 100644 --- a/Sources/SemanticIndex/IndexTaskDescription.swift +++ b/Sources/SemanticIndex/IndexTaskDescription.swift @@ -12,94 +12,72 @@ import SKCore -/// A task that either prepares targets or updates the index store for a set of files. -public enum IndexTaskDescription: TaskDescriptionProtocol { - case updateIndexStore(UpdateIndexStoreTaskDescription) - case preparation(PreparationTaskDescription) +/// Protocol of tasks that are executed on the index task scheduler. +/// +/// It is assumed that `IndexTaskDescription` of different types are allowed to execute in parallel. +protocol IndexTaskDescription: TaskDescriptionProtocol { + /// A string that is unique to this type of `IndexTaskDescription`. It is used to produce unique IDs for tasks of + /// different types in `AnyIndexTaskDescription` + static var idPrefix: String { get } + + var id: UInt32 { get } +} + +extension IndexTaskDescription { + func dependencies( + to currentlyExecutingTasks: [AnyIndexTaskDescription] + ) -> [TaskDependencyAction] { + return self.dependencies(to: currentlyExecutingTasks.compactMap { $0.wrapped as? Self }) + .map { + switch $0 { + case .cancelAndRescheduleDependency(let td): + return .cancelAndRescheduleDependency(AnyIndexTaskDescription(td)) + case .waitAndElevatePriorityOfDependency(let td): + return .waitAndElevatePriorityOfDependency(AnyIndexTaskDescription(td)) + } + } + + } +} + +/// Type-erased wrapper of an `IndexTaskDescription`. +public struct AnyIndexTaskDescription: TaskDescriptionProtocol { + let wrapped: any IndexTaskDescription + + init(_ wrapped: any IndexTaskDescription) { + self.wrapped = wrapped + } public var isIdempotent: Bool { - switch self { - case .updateIndexStore(let taskDescription): return taskDescription.isIdempotent - case .preparation(let taskDescription): return taskDescription.isIdempotent - } + return wrapped.isIdempotent } public var estimatedCPUCoreCount: Int { - switch self { - case .updateIndexStore(let taskDescription): return taskDescription.estimatedCPUCoreCount - case .preparation(let taskDescription): return taskDescription.estimatedCPUCoreCount - } + return wrapped.estimatedCPUCoreCount } public var id: String { - switch self { - case .updateIndexStore(let taskDescription): return "indexing-\(taskDescription.id)" - case .preparation(let taskDescription): return "preparation-\(taskDescription.id)" - } + return "\(type(of: wrapped).idPrefix)-\(wrapped.id)" } public var description: String { - switch self { - case .updateIndexStore(let taskDescription): return taskDescription.description - case .preparation(let taskDescription): return taskDescription.description - } + return wrapped.description } public var redactedDescription: String { - switch self { - case .updateIndexStore(let taskDescription): return taskDescription.redactedDescription - case .preparation(let taskDescription): return taskDescription.redactedDescription - } + return wrapped.redactedDescription } public func execute() async { - switch self { - case .updateIndexStore(let taskDescription): return await taskDescription.execute() - case .preparation(let taskDescription): return await taskDescription.execute() - } + return await wrapped.execute() } /// Forward to the underlying task to compute the dependencies. Preparation and index tasks don't have any /// dependencies that are managed by `TaskScheduler`. `SemanticIndexManager` awaits the preparation of a target before /// indexing files within it. public func dependencies( - to currentlyExecutingTasks: [IndexTaskDescription] - ) -> [TaskDependencyAction] { - switch self { - case .updateIndexStore(let taskDescription): - let currentlyExecutingTasks = - currentlyExecutingTasks - .compactMap { (currentlyExecutingTask) -> UpdateIndexStoreTaskDescription? in - if case .updateIndexStore(let currentlyExecutingTask) = currentlyExecutingTask { - return currentlyExecutingTask - } - return nil - } - return taskDescription.dependencies(to: currentlyExecutingTasks).map { - switch $0 { - case .waitAndElevatePriorityOfDependency(let td): - return .waitAndElevatePriorityOfDependency(.updateIndexStore(td)) - case .cancelAndRescheduleDependency(let td): - return .cancelAndRescheduleDependency(.updateIndexStore(td)) - } - } - case .preparation(let taskDescription): - let currentlyExecutingTasks = - currentlyExecutingTasks - .compactMap { (currentlyExecutingTask) -> PreparationTaskDescription? in - if case .preparation(let currentlyExecutingTask) = currentlyExecutingTask { - return currentlyExecutingTask - } - return nil - } - return taskDescription.dependencies(to: currentlyExecutingTasks).map { - switch $0 { - case .waitAndElevatePriorityOfDependency(let td): - return .waitAndElevatePriorityOfDependency(.preparation(td)) - case .cancelAndRescheduleDependency(let td): - return .cancelAndRescheduleDependency(.preparation(td)) - } - } - } + to currentlyExecutingTasks: [AnyIndexTaskDescription] + ) -> [TaskDependencyAction] { + return wrapped.dependencies(to: currentlyExecutingTasks) } } diff --git a/Sources/SemanticIndex/PreparationTaskDescription.swift b/Sources/SemanticIndex/PreparationTaskDescription.swift index d2348b27..b48e6b93 100644 --- a/Sources/SemanticIndex/PreparationTaskDescription.swift +++ b/Sources/SemanticIndex/PreparationTaskDescription.swift @@ -24,7 +24,9 @@ private var preparationIDForLogging = AtomicUInt32(initialValue: 1) /// Describes a task to prepare a set of targets. /// /// This task description can be scheduled in a `TaskScheduler`. -public struct PreparationTaskDescription: TaskDescriptionProtocol { +public struct PreparationTaskDescription: IndexTaskDescription { + public static let idPrefix = "prepare" + public let id = preparationIDForLogging.fetchAndIncrement() /// The targets that should be prepared. diff --git a/Sources/SemanticIndex/SemanticIndexManager.swift b/Sources/SemanticIndex/SemanticIndexManager.swift index 8069818a..e1aac586 100644 --- a/Sources/SemanticIndex/SemanticIndexManager.swift +++ b/Sources/SemanticIndex/SemanticIndexManager.swift @@ -44,20 +44,20 @@ public final actor SemanticIndexManager { /// The `TaskScheduler` that manages the scheduling of index tasks. This is shared among all `SemanticIndexManager`s /// in the process, to ensure that we don't schedule more index operations than processor cores from multiple /// workspaces. - private let indexTaskScheduler: TaskScheduler + private let indexTaskScheduler: TaskScheduler /// Callback that is called when an index task has finished. /// /// Currently only used for testing. - private let indexTaskDidFinish: (@Sendable (IndexTaskDescription) -> Void)? + private let indexTaskDidFinish: (@Sendable (AnyIndexTaskDescription) -> Void)? // MARK: - Public API public init( index: UncheckedIndex, buildSystemManager: BuildSystemManager, - indexTaskScheduler: TaskScheduler, - indexTaskDidFinish: (@Sendable (IndexTaskDescription) -> Void)? + indexTaskScheduler: TaskScheduler, + indexTaskDidFinish: (@Sendable (AnyIndexTaskDescription) -> Void)? ) { self.index = index.checked(for: .modifiedFiles) self.buildSystemManager = buildSystemManager @@ -133,12 +133,12 @@ public final actor SemanticIndexManager { private func prepare(targets: [ConfiguredTarget], priority: TaskPriority?) async { await self.indexTaskScheduler.schedule( priority: priority, - .preparation( + AnyIndexTaskDescription( PreparationTaskDescription( targetsToPrepare: targets, buildSystemManager: self.buildSystemManager, didFinishCallback: { [weak self] taskDescription in - self?.indexTaskDidFinish?(.preparation(taskDescription)) + self?.indexTaskDidFinish?(AnyIndexTaskDescription(taskDescription)) } ) ) @@ -149,13 +149,13 @@ public final actor SemanticIndexManager { private func updateIndexStore(for files: [DocumentURI], priority: TaskPriority?) async { await self.indexTaskScheduler.schedule( priority: priority, - .updateIndexStore( + AnyIndexTaskDescription( UpdateIndexStoreTaskDescription( filesToIndex: Set(files), buildSystemManager: self.buildSystemManager, index: self.index.unchecked, didFinishCallback: { [weak self] taskDescription in - self?.indexTaskDidFinish?(.updateIndexStore(taskDescription)) + self?.indexTaskDidFinish?(AnyIndexTaskDescription(taskDescription)) } ) ) diff --git a/Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift b/Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift index 46a8b673..6b9968a1 100644 --- a/Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift +++ b/Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift @@ -25,7 +25,8 @@ private nonisolated(unsafe) var updateIndexStoreIDForLogging = AtomicUInt32(init /// Describes a task to index a set of source files. /// /// This task description can be scheduled in a `TaskScheduler`. -public struct UpdateIndexStoreTaskDescription: TaskDescriptionProtocol { +public struct UpdateIndexStoreTaskDescription: IndexTaskDescription { + public static let idPrefix = "update-indexstore" public let id = updateIndexStoreIDForLogging.fetchAndIncrement() /// The files that should be indexed. diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 410a44c7..48431037 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -454,7 +454,7 @@ public actor SourceKitLSPServer { /// /// Shared process-wide to ensure the scheduled index operations across multiple workspaces don't exceed the maximum /// number of processor cores that the user allocated to background indexing. - private let indexTaskScheduler: TaskScheduler + private let indexTaskScheduler: TaskScheduler private var packageLoadingWorkDoneProgress = WorkDoneProgressState( "SourceKitLSP.SourceKitLSPServer.reloadPackage", diff --git a/Sources/SourceKitLSP/Workspace.swift b/Sources/SourceKitLSP/Workspace.swift index 04c63b9b..050671ed 100644 --- a/Sources/SourceKitLSP/Workspace.swift +++ b/Sources/SourceKitLSP/Workspace.swift @@ -89,7 +89,7 @@ public final class Workspace: Sendable { underlyingBuildSystem: BuildSystem?, index uncheckedIndex: UncheckedIndex?, indexDelegate: SourceKitIndexDelegate?, - indexTaskScheduler: TaskScheduler + indexTaskScheduler: TaskScheduler ) async { self.documentManager = documentManager self.buildSetup = options.buildSetup @@ -142,7 +142,7 @@ public final class Workspace: Sendable { options: SourceKitLSPServer.Options, compilationDatabaseSearchPaths: [RelativePath], indexOptions: IndexOptions = IndexOptions(), - indexTaskScheduler: TaskScheduler, + indexTaskScheduler: TaskScheduler, reloadPackageStatusCallback: @Sendable @escaping (ReloadPackageStatus) async -> Void ) async throws { var buildSystem: BuildSystem? = nil @@ -306,7 +306,7 @@ public struct IndexOptions: Sendable { /// A callback that is called when an index task finishes. /// /// Intended for testing purposes. - public var indexTaskDidFinish: (@Sendable (IndexTaskDescription) -> Void)? + public var indexTaskDidFinish: (@Sendable (AnyIndexTaskDescription) -> Void)? public init( indexStorePath: AbsolutePath? = nil, @@ -315,7 +315,7 @@ public struct IndexOptions: Sendable { listenToUnitEvents: Bool = true, enableBackgroundIndexing: Bool = false, maxCoresPercentageToUseForBackgroundIndexing: Double = 1, - indexTaskDidFinish: (@Sendable (IndexTaskDescription) -> Void)? = nil + indexTaskDidFinish: (@Sendable (AnyIndexTaskDescription) -> Void)? = nil ) { self.indexStorePath = indexStorePath self.indexDatabasePath = indexDatabasePath