[Concurrency] Add Swift Task Stealers #86082
Open
+608
−141
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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