diff --git a/include/swift/Runtime/Concurrency.h b/include/swift/Runtime/Concurrency.h index 77746cce7f88f..7bc12fc8ddf46 100644 --- a/include/swift/Runtime/Concurrency.h +++ b/include/swift/Runtime/Concurrency.h @@ -560,6 +560,11 @@ SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) JobPriority swift_task_basePriority(AsyncTask *task); +/// Returns the priority of the job. +SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) +JobPriority +swift_concurrency_jobPriority(Job *job); + /// Create and add an cancellation record to the task. SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) CancellationNotificationStatusRecord* diff --git a/stdlib/public/Concurrency/PartialAsyncTask.swift b/stdlib/public/Concurrency/PartialAsyncTask.swift index 75d4b5705176b..be56c1f9e4196 100644 --- a/stdlib/public/Concurrency/PartialAsyncTask.swift +++ b/stdlib/public/Concurrency/PartialAsyncTask.swift @@ -28,11 +28,58 @@ internal func _swiftJobRun(_ job: UnownedJob, public struct UnownedJob: Sendable { private var context: Builtin.Job + /// The priority of this job. + @available(SwiftStdlib 5.9, *) + public var priority: JobPriority { + let raw = _swift_concurrency_jobPriority(self) + return JobPriority(rawValue: raw) + } + @_alwaysEmitIntoClient @inlinable + @available(*, deprecated, renamed: "runSynchronously") public func _runSynchronously(on executor: UnownedSerialExecutor) { _swiftJobRun(self, executor) } + + @_alwaysEmitIntoClient + @inlinable + public func runSynchronously(on executor: UnownedSerialExecutor) { + _swiftJobRun(self, executor) + } +} + +/// The priority of this job. +/// +/// The executor determines how priority information affects the way tasks are scheduled. +/// The behavior varies depending on the executor currently being used. +/// Typically, executors attempt to run tasks with a higher priority +/// before tasks with a lower priority. +/// However, the semantics of how priority is treated are left up to each +/// platform and `Executor` implementation. +/// +/// A Job's priority is roughly equivalent to a `TaskPriority`, +/// however, since not all jobs are tasks, represented as separate type. +/// +/// Conversions between the two priorities are available as initializers on the respective types. +@available(SwiftStdlib 5.9, *) +public struct JobPriority { + public typealias RawValue = UInt8 + + /// The raw priority value. + public var rawValue: RawValue +} + +@available(SwiftStdlib 5.9, *) +extension TaskPriority { + /// Convert a job priority to a task priority. + /// + /// Most values are directly interchangeable, but this initializer reserves the right to fail for certain values. + @available(SwiftStdlib 5.9, *) + public init?(_ p: JobPriority) { + // currently we always convert, but we could consider mapping over only recognized values etc. + self = .init(rawValue: p.rawValue) + } } /// A mechanism to interface @@ -291,3 +338,7 @@ public func withUnsafeThrowingContinuation( public func _abiEnableAwaitContinuation() { fatalError("never use this function") } + +@available(SwiftStdlib 5.9, *) +@_silgen_name("swift_concurrency_jobPriority") +internal func _swift_concurrency_jobPriority(_ job: UnownedJob) -> UInt8 diff --git a/stdlib/public/Concurrency/Task.cpp b/stdlib/public/Concurrency/Task.cpp index 4ca91caf6db0d..813cb1b9293ab 100644 --- a/stdlib/public/Concurrency/Task.cpp +++ b/stdlib/public/Concurrency/Task.cpp @@ -555,20 +555,22 @@ const void *AsyncTask::getResumeFunctionForLogging() { return __ptrauth_swift_runtime_function_entry_strip(result); } -JobPriority swift::swift_task_currentPriority(AsyncTask *task) -{ +JobPriority swift::swift_task_currentPriority(AsyncTask *task) { // This is racey but this is to be used in an API is inherently racey anyways. auto oldStatus = task->_private()._status().load(std::memory_order_relaxed); return oldStatus.getStoredPriority(); } -JobPriority swift::swift_task_basePriority(AsyncTask *task) -{ +JobPriority swift::swift_task_basePriority(AsyncTask *task) { JobPriority pri = task->_private().BasePriority; SWIFT_TASK_DEBUG_LOG("Task %p has base priority = %zu", task, pri); return pri; } +JobPriority swift::swift_concurrency_jobPriority(Job *job) { + return job->getPriority(); +} + static inline bool isUnspecified(JobPriority priority) { return priority == JobPriority::Unspecified; } diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index 8824978b3e1a4..0fd497a2a46a5 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -273,6 +273,26 @@ extension TaskPriority: Comparable { } } + +@available(SwiftStdlib 5.9, *) +extension TaskPriority: CustomStringConvertible { + @available(SwiftStdlib 5.9, *) + public var description: String { + switch self.rawValue { + case Self.low.rawValue: + return "\(Self.self).low" + case Self.medium.rawValue: + return "\(Self.self).medium" + case Self.high.rawValue: + return "\(Self.self).high" + case Self.background.rawValue: + return "\(Self.self).background" + default: + return "\(Self.self)(rawValue: \(self.rawValue))" + } + } +} + @available(SwiftStdlib 5.1, *) extension TaskPriority: Codable { } diff --git a/test/Concurrency/Runtime/custom_executors_priority.swift b/test/Concurrency/Runtime/custom_executors_priority.swift new file mode 100644 index 0000000000000..fcbafd6dba6d9 --- /dev/null +++ b/test/Concurrency/Runtime/custom_executors_priority.swift @@ -0,0 +1,60 @@ +// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s + +// REQUIRES: concurrency +// REQUIRES: executable_test +// UNSUPPORTED: freestanding + +// UNSUPPORTED: back_deployment_runtime +// REQUIRES: concurrency_runtime + +final class InlineExecutor: SerialExecutor { + public func enqueue(_ job: UnownedJob) { + print("\(self): enqueue (priority: \(TaskPriority(job.priority)!))") + job._runSynchronously(on: self.asUnownedSerialExecutor()) + } + + public func asUnownedSerialExecutor() -> UnownedSerialExecutor { + return UnownedSerialExecutor(ordinary: self) + } +} + +let inlineExecutor = InlineExecutor() + +actor Custom { + var count = 0 + + @available(SwiftStdlib 5.1, *) + nonisolated var unownedExecutor: UnownedSerialExecutor { + print("custom unownedExecutor") + return inlineExecutor.asUnownedSerialExecutor() + } + + func report() async { + print("custom.count == \(count)") + count += 1 + } +} + +@available(SwiftStdlib 5.1, *) +@main struct Main { + static func main() async { + print("begin") + let actor = Custom() + await Task(priority: .high) { + await actor.report() + }.value + await Task() { + await actor.report() + }.value + print("end") + } +} + +// CHECK: begin +// CHECK-NEXT: custom unownedExecutor +// CHECK-NEXT: main.InlineExecutor: enqueue (priority: TaskPriority.high) +// CHECK-NEXT: custom.count == 0 +// CHECK-NEXT: custom unownedExecutor +// CHECK-NEXT: main.InlineExecutor: enqueue (priority: TaskPriority.medium) +// CHECK-NEXT: custom.count == 1 +// CHECK-NEXT: end