The problem we are trying to solve with this pitch is that Swift Concurrency currently has no mechanism for labeling a Swift Task.
We propose adding APIs to allow for the creation of Swift Tasks with names. These names would be readable by a developer's own code and could be used by runtime-analysis tools, such as a debugger, in order to aid developers.
Are the older "nameless" versions of these APIs going to be deprecated (or made ABI-only entrypoints)?
I think there are corner cases where two non-deprecated APIs that differ only in having or not having an extra defaulted argument becomes ambiguous. Plus, it'd be API surface area that we wouldn't have if we were starting from the beginning.
If, on the other hand, there are important reasons why the "nameless" versions need to continue on as first-class API, then I think these should not have a default value for name (such that omitting it always means that the "nameless" version is invoked unambiguously).
"Current name" implies that there could be other, different past or future names, but as far as I can tell the task name cannot be modified, and certainly not through this get-only property?
For what it's worth we've already had these situations in the past and we kept both declarations so far. When we added taskExecutor we added the new one, with "new" availability.
This example (if I'm not missing something) shows executorPreferencewithout a default value. My specific feedback here is that an overload with additional argument that is defaulted may pose problems with ambiguity.
This is following existing naming convention on Task APIs:
var currentPriority: TaskPriority
public func withUnsafeCurrentTask<T>(body: (UnsafeCurrentTask?) throws -> T) rethrows -> T
where "current" means "take it from the executing (current) context".
Although this convention was broken by basePriority but the introduction of that property had some process issues actually... I'd prefer to stick to the "current" meaning, but it's up for discussion of course.
It's also commonly used in Thing.current in task-local variables.
In "current task," the adjective modifies "task"; in "current name," the adjective modifies "name."
Unless I'm mistaken, the proposed API is an instance property on a specific task, whether current or not. If I have an instance of type Task, can it have a past or future name?
I see, yes that's a good point -- the proposal should be clear that we're adding an overload without a default value which hopefully would not conflict...
It's quite tricky as we keep adding more overloads of those APIs but we'll have to figure it out like that.
I am very much in support of this proposal. While the motivation calls out its usefulness in debugging my use-case is around testing. With the introduction of TaskExecutors it is almost possible to write a deterministic executor that can be used for testing small isolated pieces of code that depend on a precise execution order of jobs. Below is a prototype of such an executor using task IDs for defining the order.
TestExecutor
import Dispatch
final class TestExecutor: TaskExecutor, @unchecked Sendable {
private let queue = DispatchSerialQueue(label: "queue")
var order = [UInt64]()
var jobs = [UInt64: UnownedJob]()
init(taskIDOrder: [UInt64]) {
self.order = Array(taskIDOrder.reversed())
}
func enqueue(_ job: consuming ExecutorJob) {
let unownedJob = UnownedJob(job)
let taskID = _getJobTaskId(unownedJob)
print("Enqueue job \(taskID)")
queue.async {
if self.order.last == taskID {
print("Running job \(taskID)")
self.order.removeLast()
unownedJob.runSynchronously(on: self.asUnownedTaskExecutor())
print("Done running job \(taskID)")
while let nextJobId = self.order.last, let nextJob = self.jobs[nextJobId] {
self.order.removeLast()
self.jobs.removeValue(forKey: nextJobId)
print("Running job \(nextJobId)")
nextJob.runSynchronously(on: self.asUnownedTaskExecutor())
print("Done running job \(taskID)")
}
} else {
print("Queuing job \(taskID)")
self.jobs[taskID] = unownedJob
}
}
}
}
@_silgen_name("swift_task_getJobTaskId")
internal func _getJobTaskId(_ job: UnownedJob) -> UInt64
Now the problem with that is that task IDs are monotonically increasing throughout the lifetime of a process making them really unfit to derive an order. However, the proposed task names here would be a great fit. The only thing that is missing in this proposal is to add APIs to Unowned/ExecutorJob to query for the task name of the job.