1313import CAtomics
1414import Foundation
1515import LSPLogging
16+ import SKSupport
1617
1718/// See comment on ``TaskDescriptionProtocol/dependencies(to:taskPriority:)``
1819public 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+
2326public 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.
@@ -187,16 +186,10 @@ public actor QueuedTask<TaskDescription: TaskDescriptionProtocol> {
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 ?? Task . currentPriority. 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
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
212210 }
213211 }
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- }
224- }
225- }
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,15 @@ 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+ }
299280 Task ( priority: targetPriority) {
300281 await self . resultTask. value
301282 }
@@ -385,16 +366,6 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
385366 return queuedTask
386367 }
387368
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-
398369 /// Returns the maximum number of concurrent tasks that are allowed to execute at the given priority.
399370 private func maxConcurrentTasks( at priority: TaskPriority ) -> Int {
400371 for (atPriority, maxConcurrentTasks) in maxConcurrentTasksByPriority {
@@ -417,9 +388,8 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
417388 {
418389 // We don't have any execution slots left. Thus, this poker has nothing to do and is done.
419390 // 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.
391+ // If the low priority task's priority gets elevated that task's priority will get elevated and it will be
392+ // picked up on the next `poke` call.
423393 return
424394 }
425395 let dependencies = task. description. dependencies ( to: currentlyExecutingTasks. map ( \. description) )
@@ -428,13 +398,17 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
428398 case . cancelAndRescheduleDependency( let taskDescription) :
429399 guard let dependency = self . currentlyExecutingTasks. first ( where: { $0. description. id == taskDescription. id } )
430400 else {
431- logger. fault (
432- " Cannot find task to wait for \( taskDescription. forLogging) in list of currently executing tasks "
433- )
401+ withLoggingSubsystemAndScope ( subsystem: taskSchedulerSubsystem, scope: nil ) {
402+ logger. fault (
403+ " Cannot find task to wait for \( taskDescription. forLogging) in list of currently executing tasks "
404+ )
405+ }
434406 return nil
435407 }
436408 if !taskDescription. isIdempotent {
437- logger. fault ( " Cannot reschedule task ' \( taskDescription. forLogging) ' since it is not idempotent " )
409+ withLoggingSubsystemAndScope ( subsystem: taskSchedulerSubsystem, scope: nil ) {
410+ logger. fault ( " Cannot reschedule task ' \( taskDescription. forLogging) ' since it is not idempotent " )
411+ }
438412 return dependency
439413 }
440414 if dependency. priority > task. priority {
@@ -445,9 +419,11 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
445419 case . waitAndElevatePriorityOfDependency( let taskDescription) :
446420 guard let dependency = self . currentlyExecutingTasks. first ( where: { $0. description. id == taskDescription. id } )
447421 else {
448- logger. fault (
449- " Cannot find task to wait for ' \( taskDescription. forLogging) ' in list of currently executing tasks "
450- )
422+ withLoggingSubsystemAndScope ( subsystem: taskSchedulerSubsystem, scope: nil ) {
423+ logger. fault (
424+ " Cannot find task to wait for ' \( taskDescription. forLogging) ' in list of currently executing tasks "
425+ )
426+ }
451427 return nil
452428 }
453429 return dependency
@@ -465,9 +441,11 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
465441 switch taskDependency {
466442 case . cancelAndRescheduleDependency( let taskDescription) :
467443 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- )
444+ withLoggingSubsystemAndScope ( subsystem: taskSchedulerSubsystem, scope: nil ) {
445+ logger. fault (
446+ " Cannot find task to reschedule \( taskDescription. forLogging) in list of currently executing tasks "
447+ )
448+ }
471449 return nil
472450 }
473451 return task
@@ -478,6 +456,9 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
478456 if !rescheduleTasks. isEmpty {
479457 Task . detached ( priority: task. priority) {
480458 for task in rescheduleTasks {
459+ withLoggingSubsystemAndScope ( subsystem: taskSchedulerSubsystem, scope: nil ) {
460+ logger. debug ( " Suspending \( task. description. forLogging) " )
461+ }
481462 await task. cancelToBeRescheduled ( )
482463 }
483464 }
@@ -510,7 +491,6 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
510491 case . terminated: break
511492 case . cancelledToBeRescheduled: pendingTasks. append ( task)
512493 }
513- await self . triggerPriorityUpdateOfQueuedTasks ( )
514494 self . poke ( )
515495 }
516496}
0 commit comments