13
13
import CAtomics
14
14
import Foundation
15
15
import LSPLogging
16
+ import SKSupport
16
17
17
18
/// See comment on ``TaskDescriptionProtocol/dependencies(to:taskPriority:)``
18
19
public enum TaskDependencyAction < TaskDescription: TaskDescriptionProtocol > {
19
20
case waitAndElevatePriorityOfDependency( TaskDescription )
20
21
case cancelAndRescheduleDependency( TaskDescription )
21
22
}
22
23
24
+ private let taskSchedulerSubsystem = " org.swift.sourcekit-lsp.task-scheduler "
25
+
23
26
public protocol TaskDescriptionProtocol : Identifiable , Sendable , CustomLogStringConvertible {
24
27
/// Execute the task.
25
28
///
@@ -123,10 +126,6 @@ public actor QueuedTask<TaskDescription: TaskDescriptionProtocol> {
123
126
/// Every time `execute` gets called, a new task is placed in this continuation. See comment on `executionTask`.
124
127
private let executionTaskCreatedContinuation : AsyncStream < Task < ExecutionTaskFinishStatus , Never > > . Continuation
125
128
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
-
130
129
nonisolated ( unsafe) private var _priority : AtomicUInt8
131
130
132
131
/// The latest known priority of the task.
@@ -187,16 +186,10 @@ public actor QueuedTask<TaskDescription: TaskDescriptionProtocol> {
187
186
description: TaskDescription ,
188
187
executionStateChangedCallback: ( @Sendable ( QueuedTask, TaskExecutionState) async -> Void ) ?
189
188
) async {
190
- self . _priority = . init ( initialValue: priority? . rawValue ?? Task . currentPriority. rawValue)
189
+ self . _priority = AtomicUInt8 ( initialValue: priority? . rawValue ?? Task . currentPriority. rawValue)
191
190
self . description = description
192
191
self . executionStateChangedCallback = executionStateChangedCallback
193
192
194
- var updatePriorityContinuation : AsyncStream < Void > . Continuation !
195
- let updatePriorityStream = AsyncStream {
196
- updatePriorityContinuation = $0
197
- }
198
- self . updatePriorityContinuation = updatePriorityContinuation
199
-
200
193
var executionTaskCreatedContinuation : AsyncStream < Task < ExecutionTaskFinishStatus , Never > > . Continuation !
201
194
let executionTaskCreatedStream = AsyncStream {
202
195
executionTaskCreatedContinuation = $0
@@ -205,31 +198,24 @@ public actor QueuedTask<TaskDescription: TaskDescriptionProtocol> {
205
198
206
199
self . resultTask = Task . detached ( priority: priority) {
207
200
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
212
210
}
213
211
}
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
+ )
232
217
}
218
+ self . priority = Task . currentPriority
233
219
}
234
220
} onCancel: {
235
221
self . resultTaskCancelled. value = true
@@ -282,20 +268,15 @@ public actor QueuedTask<TaskDescription: TaskDescriptionProtocol> {
282
268
self . executionTask = nil
283
269
}
284
270
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
-
295
271
/// If the priority of this task is less than `targetPriority`, elevate the priority to `targetPriority` by spawning
296
272
/// a new task that depends on it. Otherwise a no-op.
297
273
nonisolated func elevatePriority( to targetPriority: TaskPriority ) {
298
274
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
+ }
299
280
Task ( priority: targetPriority) {
300
281
await self . resultTask. value
301
282
}
@@ -385,16 +366,6 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
385
366
return queuedTask
386
367
}
387
368
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
-
398
369
/// Returns the maximum number of concurrent tasks that are allowed to execute at the given priority.
399
370
private func maxConcurrentTasks( at priority: TaskPriority ) -> Int {
400
371
for (atPriority, maxConcurrentTasks) in maxConcurrentTasksByPriority {
@@ -417,9 +388,8 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
417
388
{
418
389
// We don't have any execution slots left. Thus, this poker has nothing to do and is done.
419
390
// 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.
423
393
return
424
394
}
425
395
let dependencies = task. description. dependencies ( to: currentlyExecutingTasks. map ( \. description) )
@@ -428,13 +398,17 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
428
398
case . cancelAndRescheduleDependency( let taskDescription) :
429
399
guard let dependency = self . currentlyExecutingTasks. first ( where: { $0. description. id == taskDescription. id } )
430
400
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
+ }
434
406
return nil
435
407
}
436
408
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
+ }
438
412
return dependency
439
413
}
440
414
if dependency. priority > task. priority {
@@ -445,9 +419,11 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
445
419
case . waitAndElevatePriorityOfDependency( let taskDescription) :
446
420
guard let dependency = self . currentlyExecutingTasks. first ( where: { $0. description. id == taskDescription. id } )
447
421
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
+ }
451
427
return nil
452
428
}
453
429
return dependency
@@ -465,9 +441,11 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
465
441
switch taskDependency {
466
442
case . cancelAndRescheduleDependency( let taskDescription) :
467
443
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
+ }
471
449
return nil
472
450
}
473
451
return task
@@ -478,6 +456,9 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
478
456
if !rescheduleTasks. isEmpty {
479
457
Task . detached ( priority: task. priority) {
480
458
for task in rescheduleTasks {
459
+ withLoggingSubsystemAndScope ( subsystem: taskSchedulerSubsystem, scope: nil ) {
460
+ logger. debug ( " Suspending \( task. description. forLogging) " )
461
+ }
481
462
await task. cancelToBeRescheduled ( )
482
463
}
483
464
}
@@ -510,7 +491,6 @@ public actor TaskScheduler<TaskDescription: TaskDescriptionProtocol> {
510
491
case . terminated: break
511
492
case . cancelledToBeRescheduled: pendingTasks. append ( task)
512
493
}
513
- await self . triggerPriorityUpdateOfQueuedTasks ( )
514
494
self . poke ( )
515
495
}
516
496
}
0 commit comments