Skip to content

Commit bac062f

Browse files
committed
Merge branch 'main' into 6.0/merge-main-2024-05-28
# Conflicts: # Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift
2 parents d5993b6 + 3356873 commit bac062f

36 files changed

+1283
-737
lines changed

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ let package = Package(
367367
name: "SourceKitLSPTests",
368368
dependencies: [
369369
"BuildServerProtocol",
370+
"CAtomics",
370371
"LSPLogging",
371372
"LSPTestSupport",
372373
"LanguageServerProtocol",

Sources/Diagnose/DiagnoseCommand.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,15 @@ public struct DiagnoseCommand: AsyncParsableCommand {
352352
"""
353353
)
354354

355+
#if os(macOS)
356+
// Reveal the bundle in Finder on macOS
357+
do {
358+
let process = try Process.launch(arguments: ["open", "-R", bundlePath.path], workingDirectory: nil)
359+
try await process.waitUntilExitSendingSigIntOnTaskCancellation()
360+
} catch {
361+
// If revealing the bundle in Finder should fail, we don't care. We still printed the bundle path to stdout.
362+
}
363+
#endif
355364
}
356365

357366
@MainActor

Sources/SKCore/BuildServerBuildSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ extension BuildServerBuildSystem: BuildSystem {
279279
return [ConfiguredTarget(targetID: "dummy", runDestinationID: "dummy")]
280280
}
281281

282-
public func generateBuildGraph() {}
282+
public func generateBuildGraph(allowFileSystemWrites: Bool) {}
283283

284284
public func topologicalSort(of targets: [ConfiguredTarget]) async -> [ConfiguredTarget]? {
285285
return nil

Sources/SKCore/BuildSystem.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,14 @@ public protocol BuildSystem: AnyObject, Sendable {
135135
/// Return the list of targets and run destinations that the given document can be built for.
136136
func configuredTargets(for document: DocumentURI) async -> [ConfiguredTarget]
137137

138-
/// Re-generate the build graph including all the tasks that are necessary for building the entire build graph, like
139-
/// resolving package versions.
140-
func generateBuildGraph() async throws
138+
/// Re-generate the build graph.
139+
///
140+
/// If `allowFileSystemWrites` is `true`, this should include all the tasks that are necessary for building the entire
141+
/// build graph, like resolving package versions.
142+
///
143+
/// If `allowFileSystemWrites` is `false`, no files must be written to disk. This mode is used to determine whether
144+
/// the build system can handle a source file, and decide whether a workspace should be opened with this build system
145+
func generateBuildGraph(allowFileSystemWrites: Bool) async throws
141146

142147
/// Sort the targets so that low-level targets occur before high-level targets.
143148
///

Sources/SKCore/BuildSystemManager.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,8 @@ extension BuildSystemManager {
219219
return settings
220220
}
221221

222-
public func generateBuildGraph() async throws {
223-
try await self.buildSystem?.generateBuildGraph()
222+
public func generateBuildGraph(allowFileSystemWrites: Bool) async throws {
223+
try await self.buildSystem?.generateBuildGraph(allowFileSystemWrites: allowFileSystemWrites)
224224
}
225225

226226
public func topologicalSort(of targets: [ConfiguredTarget]) async throws -> [ConfiguredTarget]? {

Sources/SKCore/CompilationDatabaseBuildSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
132132
throw PrepareNotSupportedError()
133133
}
134134

135-
public func generateBuildGraph() {}
135+
public func generateBuildGraph(allowFileSystemWrites: Bool) {}
136136

137137
public func topologicalSort(of targets: [ConfiguredTarget]) -> [ConfiguredTarget]? {
138138
return nil

Sources/SKCore/TaskScheduler.swift

Lines changed: 53 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@
1313
import CAtomics
1414
import Foundation
1515
import LSPLogging
16+
import SKSupport
1617

1718
/// See comment on ``TaskDescriptionProtocol/dependencies(to:taskPriority:)``
1819
public enum TaskDependencyAction<TaskDescription: TaskDescriptionProtocol> {
1920
case waitAndElevatePriorityOfDependency(TaskDescription)
2021
case cancelAndRescheduleDependency(TaskDescription)
2122
}
2223

24+
private let taskSchedulerSubsystem = "org.swift.sourcekit-lsp.task-scheduler"
25+
2326
public protocol TaskDescriptionProtocol: Identifiable, Sendable, CustomLogStringConvertible {
2427
/// Execute the task.
2528
///
@@ -123,10 +126,6 @@ public actor QueuedTask<TaskDescription: TaskDescriptionProtocol> {
123126
/// Every time `execute` gets called, a new task is placed in this continuation. See comment on `executionTask`.
124127
private let executionTaskCreatedContinuation: AsyncStream<Task<ExecutionTaskFinishStatus, Never>>.Continuation
125128

126-
/// Placing a new value in this continuation will cause `resultTask` to query its priority and set
127-
/// `QueuedTask.priority`.
128-
private let updatePriorityContinuation: AsyncStream<Void>.Continuation
129-
130129
nonisolated(unsafe) private var _priority: AtomicUInt8
131130

132131
/// The latest known priority of the task.
@@ -183,20 +182,14 @@ public actor QueuedTask<TaskDescription: TaskDescriptionProtocol> {
183182
private let executionStateChangedCallback: (@Sendable (QueuedTask, TaskExecutionState) async -> Void)?
184183

185184
init(
186-
priority: TaskPriority? = nil,
185+
priority: TaskPriority,
187186
description: TaskDescription,
188187
executionStateChangedCallback: (@Sendable (QueuedTask, TaskExecutionState) async -> Void)?
189188
) async {
190-
self._priority = .init(initialValue: priority?.rawValue ?? Task.currentPriority.rawValue)
189+
self._priority = AtomicUInt8(initialValue: priority.rawValue)
191190
self.description = description
192191
self.executionStateChangedCallback = executionStateChangedCallback
193192

194-
var updatePriorityContinuation: AsyncStream<Void>.Continuation!
195-
let updatePriorityStream = AsyncStream {
196-
updatePriorityContinuation = $0
197-
}
198-
self.updatePriorityContinuation = updatePriorityContinuation
199-
200193
var executionTaskCreatedContinuation: AsyncStream<Task<ExecutionTaskFinishStatus, Never>>.Continuation!
201194
let executionTaskCreatedStream = AsyncStream {
202195
executionTaskCreatedContinuation = $0
@@ -205,31 +198,24 @@ public actor QueuedTask<TaskDescription: TaskDescriptionProtocol> {
205198

206199
self.resultTask = Task.detached(priority: priority) {
207200
await withTaskCancellationHandler {
208-
await withTaskGroup(of: Void.self) { taskGroup in
209-
taskGroup.addTask {
210-
for await _ in updatePriorityStream {
211-
self.priority = Task.currentPriority
212-
}
213-
}
214-
taskGroup.addTask {
215-
for await task in executionTaskCreatedStream {
216-
switch await task.valuePropagatingCancellation {
217-
case .cancelledToBeRescheduled:
218-
// Break the switch and wait for a new `executionTask` to be placed into `executionTaskCreatedStream`.
219-
break
220-
case .terminated:
221-
// The task finished. We are done with this `QueuedTask`
222-
return
223-
}
201+
await withTaskPriorityChangedHandler(initialPriority: self.priority) {
202+
for await task in executionTaskCreatedStream {
203+
switch await task.valuePropagatingCancellation {
204+
case .cancelledToBeRescheduled:
205+
// Break the switch and wait for a new `executionTask` to be placed into `executionTaskCreatedStream`.
206+
break
207+
case .terminated:
208+
// The task finished. We are done with this `QueuedTask`
209+
return
224210
}
225211
}
226-
// The first (update priority) task never finishes, so this waits for the second (wait for execution) task
227-
// to terminate.
228-
// Afterwards we also cancel the update priority task.
229-
for await _ in taskGroup {
230-
taskGroup.cancelAll()
231-
return
212+
} taskPriorityChanged: {
213+
withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) {
214+
logger.debug(
215+
"Updating priority of \(self.description.forLogging) from \(self.priority.rawValue) to \(Task.currentPriority.rawValue)"
216+
)
232217
}
218+
self.priority = Task.currentPriority
233219
}
234220
} onCancel: {
235221
self.resultTaskCancelled.value = true
@@ -282,20 +268,19 @@ public actor QueuedTask<TaskDescription: TaskDescriptionProtocol> {
282268
self.executionTask = nil
283269
}
284270

285-
/// Trigger `QueuedTask.priority` to be updated with the current priority of the underlying task.
286-
///
287-
/// This is an asynchronous operation that makes no guarantees when the updated priority will be available.
288-
///
289-
/// This is needed because tasks can't subscribe to priority updates (ie. there is no `withPriorityHandler` similar to
290-
/// `withCancellationHandler`, https://github.com/apple/swift/issues/73367).
291-
func triggerPriorityUpdate() {
292-
updatePriorityContinuation.yield()
293-
}
294-
295271
/// If the priority of this task is less than `targetPriority`, elevate the priority to `targetPriority` by spawning
296272
/// a new task that depends on it. Otherwise a no-op.
297273
nonisolated func elevatePriority(to targetPriority: TaskPriority) {
298274
if priority < targetPriority {
275+
withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) {
276+
logger.debug(
277+
"Elevating priority of \(self.description.forLogging) from \(self.priority.rawValue) to \(targetPriority.rawValue)"
278+
)
279+
}
280+
// Awaiting the result task from a higher-priority task will eventually update `priority` through
281+
// `withTaskPriorityChangedHandler` but that might take a while because `withTaskPriorityChangedHandler` polls.
282+
// Since we know that the priority will be elevated, set it now. That way we don't try to elevate it again.
283+
self.priority = targetPriority
299284
Task(priority: targetPriority) {
300285
await self.resultTask.value
301286
}
@@ -371,7 +356,7 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
371356
)? = nil
372357
) async -> QueuedTask<TaskDescription> {
373358
let queuedTask = await QueuedTask(
374-
priority: priority,
359+
priority: priority ?? Task.currentPriority,
375360
description: taskDescription,
376361
executionStateChangedCallback: executionStateChangedCallback
377362
)
@@ -385,16 +370,6 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
385370
return queuedTask
386371
}
387372

388-
/// Trigger all queued tasks to update their priority.
389-
///
390-
/// Should be called occasionally to elevate tasks in the queue whose underlying `Swift.Task` had their priority
391-
/// elevated because a higher-priority task started depending on them.
392-
private func triggerPriorityUpdateOfQueuedTasks() async {
393-
for task in pendingTasks {
394-
await task.triggerPriorityUpdate()
395-
}
396-
}
397-
398373
/// Returns the maximum number of concurrent tasks that are allowed to execute at the given priority.
399374
private func maxConcurrentTasks(at priority: TaskPriority) -> Int {
400375
for (atPriority, maxConcurrentTasks) in maxConcurrentTasksByPriority {
@@ -417,9 +392,8 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
417392
{
418393
// We don't have any execution slots left. Thus, this poker has nothing to do and is done.
419394
// When the next task finishes, it calls `poke` again.
420-
// If the low priority task's priority gets elevated, that will be picked up when the next task in the
421-
// `TaskScheduler` finishes, which causes `triggerPriorityUpdateOfQueuedTasks` to be called, which transfers
422-
// the new elevated priority to `QueuedTask.priority` and which can then be picked up by the next `poke` call.
395+
// If the low priority task's priority gets elevated that task's priority will get elevated and it will be
396+
// picked up on the next `poke` call.
423397
return
424398
}
425399
let dependencies = task.description.dependencies(to: currentlyExecutingTasks.map(\.description))
@@ -428,13 +402,17 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
428402
case .cancelAndRescheduleDependency(let taskDescription):
429403
guard let dependency = self.currentlyExecutingTasks.first(where: { $0.description.id == taskDescription.id })
430404
else {
431-
logger.fault(
432-
"Cannot find task to wait for \(taskDescription.forLogging) in list of currently executing tasks"
433-
)
405+
withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) {
406+
logger.fault(
407+
"Cannot find task to wait for \(taskDescription.forLogging) in list of currently executing tasks"
408+
)
409+
}
434410
return nil
435411
}
436412
if !taskDescription.isIdempotent {
437-
logger.fault("Cannot reschedule task '\(taskDescription.forLogging)' since it is not idempotent")
413+
withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) {
414+
logger.fault("Cannot reschedule task '\(taskDescription.forLogging)' since it is not idempotent")
415+
}
438416
return dependency
439417
}
440418
if dependency.priority > task.priority {
@@ -445,9 +423,11 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
445423
case .waitAndElevatePriorityOfDependency(let taskDescription):
446424
guard let dependency = self.currentlyExecutingTasks.first(where: { $0.description.id == taskDescription.id })
447425
else {
448-
logger.fault(
449-
"Cannot find task to wait for '\(taskDescription.forLogging)' in list of currently executing tasks"
450-
)
426+
withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) {
427+
logger.fault(
428+
"Cannot find task to wait for '\(taskDescription.forLogging)' in list of currently executing tasks"
429+
)
430+
}
451431
return nil
452432
}
453433
return dependency
@@ -465,9 +445,11 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
465445
switch taskDependency {
466446
case .cancelAndRescheduleDependency(let taskDescription):
467447
guard let task = self.currentlyExecutingTasks.first(where: { $0.description.id == taskDescription.id }) else {
468-
logger.fault(
469-
"Cannot find task to reschedule \(taskDescription.forLogging) in list of currently executing tasks"
470-
)
448+
withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) {
449+
logger.fault(
450+
"Cannot find task to reschedule \(taskDescription.forLogging) in list of currently executing tasks"
451+
)
452+
}
471453
return nil
472454
}
473455
return task
@@ -478,6 +460,9 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
478460
if !rescheduleTasks.isEmpty {
479461
Task.detached(priority: task.priority) {
480462
for task in rescheduleTasks {
463+
withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) {
464+
logger.debug("Suspending \(task.description.forLogging)")
465+
}
481466
await task.cancelToBeRescheduled()
482467
}
483468
}
@@ -510,7 +495,6 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
510495
case .terminated: break
511496
case .cancelledToBeRescheduled: pendingTasks.append(task)
512497
}
513-
await self.triggerPriorityUpdateOfQueuedTasks()
514498
self.poke()
515499
}
516500
}

Sources/SKSupport/AsyncUtils.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,28 @@ extension Collection where Element: Sendable {
166166
}
167167
}
168168
}
169+
170+
public struct TimeoutError: Error, CustomStringConvertible {
171+
public var description: String { "Timed out" }
172+
}
173+
174+
/// Executes `body`. If it doesn't finish after `duration`, throws a `TimeoutError`.
175+
public func withTimeout<T: Sendable>(
176+
_ duration: Duration,
177+
_ body: @escaping @Sendable () async throws -> T
178+
) async throws -> T {
179+
try await withThrowingTaskGroup(of: T.self) { taskGroup in
180+
taskGroup.addTask {
181+
try await Task.sleep(for: duration)
182+
throw TimeoutError()
183+
}
184+
taskGroup.addTask {
185+
return try await body()
186+
}
187+
for try await value in taskGroup {
188+
taskGroup.cancelAll()
189+
return value
190+
}
191+
throw CancellationError()
192+
}
193+
}

Sources/SKSupport/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ add_library(SKSupport STATIC
1717
Result.swift
1818
Sequence+AsyncMap.swift
1919
SwitchableProcessResultExitStatus.swift
20+
Task+WithPriorityChangedHandler.swift
2021
ThreadSafeBox.swift
2122
WorkspaceType.swift
2223
)

0 commit comments

Comments
 (0)