Skip to content

Conversation

@Bryce-MW
Copy link
Contributor

When Swift Tasks are enqueued onto the cooperative pool, the queue they are placed onto is based on their current priority. If the Task is further escalated, it will be marked as such so that when it runs, it will self-escalate the thread to the required priority, but while it is enqueued, nothing will happen. If the Task was originally enqueued at a low priority, it may not be able to run because it is waiting on other items ahead and the system doesn’t have enough room to do low priority work. This is a priority inversion since this now-higher-priority Task is unable to run.

This commit implements a "stealer" object for Tasks that is similar to the existing Actor stealer called AsyncTaskStealer. These would be used only when Dispatch is the default global executor and priority escalation is enabled. To an executor of any kind (Dispatch or custom), stealer objects would look like any other Job. This avoids edge cases when a Task is enqueued to multiple types of exectutors through its lifetime (e.g. global, Actor, custom). Stealer objects have to be created any time the Task needs to be enqueued while the Task object itself is still enqueued somewhere (which in Dispatch executors is via an intrusive linked list thus only allowing the Task to be enqueued in one place). If it does eventually become dequeued, it may be enqueued directly the next time it needs to be enqueued. In order to ensure that the Task is only run when it should, it needs to contain a counter that increments each time it finishes an execution. Each stealer object contains a field indicating at which value of the counter is it able to run and the Task itself has one in its private data for the direct enqueued copy. If the Task or stealer is run but the Task’s current counter is not equal to the one that is allowed to be run, the invocation will do nothing.

Because flagAsRunning can now fail and a previous commit introduced an opaque return value, there is a split between two cases where flagAsRunning can be called. When the Task is being resumed immediatly after suspention without being enqueued again, flagAsRunning is infailable and will always return zero for the opaque value. When initially running from swift_task_run, it is failable and may return an opaque value. I've refactored flagAsRunning to separate these cases and try my best to reduce some redundant code.

rdar://160967177

When Swift Tasks are enqueued onto the cooperative pool, the queue they are placed onto is based on their current priority. If the Task is further escalated, it will be marked as such so that when it runs, it will self-escalate the thread to the required priority, but while it is enqueued, nothing will happen. If the Task was originally enqueued at a low priority, it may not be able to run because it is waiting on other items ahead and the system doesn’t have enough room to do low priority work. This is a priority inversion since this now-higher-priority Task is unable to run.

This commit implements a "stealer" object for Tasks that is similar to the existing Actor stealer call AsyncTaskStealer. These would be used only when Dispatch is the default global executor and priority escalation is enabled. To an executor of any kind (Dispatch or custom), stealer objects would look like any other Job. This avoids edge cases when a Task is enqueued to multiple types of exectutors through its lifetime (e.g. global, Actor, custom). Stealer objects have to be created any time the Task needs to be enqueued while the Task object itself is still enqueued somewhere (which in Dispatch executors is via an intrusive linked list thus only allowing the Task to be enqueued in one place). If it does eventually become dequeued, it may be enqueued directly the next time it needs to be enqueued.
In order to ensure that the Task is only run when it should, it needs to contain a counter that increments each time it finishes an execution. Each stealer object contains a field indicating at which value of the counter is it able to run and the Task itself has one in its private data for the direct enqueued copy. If the Task or stealer is run but the Task’s current counter is not equal to the one that is allowed to be run, the invocation will do nothing.

Because flagAsRunning can now fail and a previous commit introduced an opaque return value, there is a split between two cases where flagAsRunning can be called. When the Task is being resumed immediatly after suspention without being enqueued again, flagAsRunning is infailable and will always return zero for the opaque value. When initially running from swift_task_run, it is failable and may return an opaque value. I've refactored flagAsRunning to separate these cases and try my best to reduce some redundant code.

rdar://160967177
@Bryce-MW Bryce-MW requested a review from ktoso as a code owner December 16, 2025 01:11
@Bryce-MW
Copy link
Contributor Author

@swift-ci please smoke test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant