//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2021 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// import Swift /// A service that can execute jobs. @available(SwiftStdlib 5.1, *) public protocol Executor: AnyObject, Sendable { // Since lack move-only type support in the SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY configuration // Do not deprecate the UnownedJob enqueue in that configuration just yet - as we cannot introduce the replacements. #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(SwiftStdlib 5.1, *) #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY func enqueue(_ job: UnownedJob) // Cannot introduce these methods in SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY // since it lacks move-only type support. #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(SwiftStdlib 5.9, *) @available(*, deprecated, message: "Implement 'enqueue(_: consuming ExecutorJob)' instead") func enqueue(_ job: consuming Job) #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(SwiftStdlib 5.9, *) func enqueue(_ job: consuming ExecutorJob) #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY } /// A service that executes jobs. /// /// ### Custom Actor Executors /// By default, all actor types execute tasks on a shared global concurrent pool. /// The global pool does not guarantee any thread (or dispatch queue) affinity, /// so actors are free to use different threads as they execute tasks. /// /// > The runtime may perform various optimizations to minimize un-necessary /// > thread switching. /// /// Sometimes it is important to be able to customize the execution behavior /// of an actor. For example, when an actor is known to perform heavy blocking /// operations (such as IO), and we would like to keep this work *off* the global /// shared pool, as blocking it may prevent other actors from being responsive. /// /// You can implement a custom executor, by conforming a type to the /// ``SerialExecutor`` protocol, and implementing the ``enqueue(_:)`` method. /// /// Once implemented, you can configure an actor to use such executor by /// implementing the actor's ``Actor/unownedExecutor`` computed property. /// For example, you could accept an executor in the actor's initializer, /// store it as a variable (in order to retain it for the duration of the /// actor's lifetime), and return it from the `unownedExecutor` computed /// property like this: /// /// ``` /// actor MyActor { /// let myExecutor: MyExecutor /// /// // accepts an executor to run this actor on. /// init(executor: MyExecutor) { /// self.myExecutor = executor /// } /// /// nonisolated var unownedExecutor: UnownedSerialExecutor { /// self.myExecutor.asUnownedSerialExecutor() /// } /// } /// ``` /// /// It is also possible to use a form of shared executor, either created as a /// global or static property, which you can then re-use for every MyActor /// instance: /// /// ``` /// actor MyActor { /// // Serial executor reused by *all* instances of MyActor! /// static let sharedMyActorsExecutor = MyExecutor() // implements SerialExecutor /// /// /// nonisolated var unownedExecutor: UnownedSerialExecutor { /// Self.sharedMyActorsExecutor.asUnownedSerialExecutor() /// } /// } /// ``` /// /// In the example above, *all* "MyActor" instances would be using the same /// serial executor, which would result in only one of such actors ever being /// run at the same time. This may be useful if some of your code has some /// "specific thread" requirement when interoperating with non-Swift runtimes /// for example. /// /// Since the ``UnownedSerialExecutor`` returned by the `unownedExecutor` /// property *does not* retain the executor, you must make sure the lifetime of /// it extends beyond the lifetime of any actor or task using it, as otherwise /// it may attempt to enqueue work on a released executor object, causing a crash. /// The executor returned by unownedExecutor *must* always be the same object, /// and returning different executors can lead to unexpected behavior. /// /// Alternatively, you can also use existing serial executor implementations, /// such as Dispatch's `DispatchSerialQueue` or others. @available(SwiftStdlib 5.1, *) public protocol SerialExecutor: Executor { // This requirement is repeated here as a non-override so that we // get a redundant witness-table entry for it. This allows us to // avoid drilling down to the base conformance just for the basic // work-scheduling operation. @_nonoverride #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(SwiftStdlib 5.1, *) @available(*, deprecated, message: "Implement 'enqueue(_: consuming ExecutorJob)' instead") #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY func enqueue(_ job: UnownedJob) #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY // This requirement is repeated here as a non-override so that we // get a redundant witness-table entry for it. This allows us to // avoid drilling down to the base conformance just for the basic // work-scheduling operation. @_nonoverride @available(SwiftStdlib 5.9, *) @available(*, deprecated, message: "Implement 'enqueue(_: consuming ExecutorJob)' instead") func enqueue(_ job: consuming Job) #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY // This requirement is repeated here as a non-override so that we // get a redundant witness-table entry for it. This allows us to // avoid drilling down to the base conformance just for the basic // work-scheduling operation. @_nonoverride @available(SwiftStdlib 5.9, *) func enqueue(_ job: consuming ExecutorJob) #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY /// Convert this executor value to the optimized form of borrowed /// executor references. @unsafe func asUnownedSerialExecutor() -> UnownedSerialExecutor /// If this executor has complex equality semantics, and the runtime needs to /// compare two executors, it will first attempt the usual pointer-based /// equality / check, / and if it fails it will compare the types of both /// executors, if they are the same, / it will finally invoke this method, /// in an /// attempt to let the executor itself decide / if this and the `other` /// executor represent the same serial, exclusive, isolation context. /// /// This method must be implemented with great care, as wrongly returning /// `true` would allow / code from a different execution context (e.g. thread) /// to execute code which was intended to be isolated by another actor. /// /// This check is not used when performing executor switching. /// /// This check is used when performing ``Actor/assertIsolated()``, /// ``Actor/preconditionIsolated()``, ``Actor/assumeIsolated()`` and similar /// APIs which assert about the same "exclusive serial execution context". /// /// - Parameter other: the executor to compare with. /// - Returns: `true`, if `self` and the `other` executor actually are /// mutually exclusive and it is safe–from a concurrency /// perspective–to execute code assuming one on the other. @available(SwiftStdlib 5.9, *) func isSameExclusiveExecutionContext(other: Self) -> Bool /// Last resort "fallback" isolation check, called when the concurrency runtime /// is comparing executors e.g. during ``assumeIsolated()`` and is unable to prove /// serial equivalence between the expected (this object), and the current executor. /// /// During executor comparison, the Swift concurrency runtime attempts to compare /// current and expected executors in a few ways (including "complex" equality /// between executors (see ``isSameExclusiveExecutionContext(other:)``), and if all /// those checks fail, this method is invoked on the expected executor. /// /// This method MUST crash if it is unable to prove that the current execution /// context belongs to this executor. At this point usual executor comparison would /// have already failed, though the executor may have some external tracking of /// threads it owns, and may be able to prove isolation nevertheless. /// /// A default implementation is provided that unconditionally crashes the /// program, and prevents calling code from proceeding with potentially /// not thread-safe execution. /// /// - Warning: This method must crash and halt program execution if unable /// to prove the isolation of the calling context. @available(SwiftStdlib 6.0, *) func checkIsolated() @available(SwiftStdlib 6.2, *) func isIsolatingCurrentContext() -> Bool } @available(SwiftStdlib 6.0, *) extension SerialExecutor { @available(SwiftStdlib 6.0, *) public func checkIsolated() { #if !$Embedded fatalError("Unexpected isolation context, expected to be executing on \(Self.self)") #else Builtin.int_trap() #endif } } @available(SwiftStdlib 6.2, *) extension SerialExecutor { @available(SwiftStdlib 6.2, *) public func isIsolatingCurrentContext() -> Bool { self.checkIsolated() return true } } /// An executor that may be used as preferred executor by a task. /// /// ### Impact of setting a task executor preference /// By default, without setting a task executor preference, nonisolated /// asynchronous functions, as well as methods declared on default actors -- /// that is actors which do not require a specific executor -- execute on /// Swift's default global concurrent executor. This is an executor shared by /// the entire runtime to execute any work which does not have strict executor /// requirements. /// /// By setting a task executor preference, either with a /// ``withTaskExecutorPreference(_:operation:)``, creating a task with a preference /// (`Task(executorPreference:)`, or `group.addTask(executorPreference:)`), the task and all of its child /// tasks (unless a new preference is set) will be preferring to execute on /// the provided task executor. /// /// Unstructured tasks do not inherit the task executor. @_unavailableInEmbedded @available(SwiftStdlib 6.0, *) public protocol TaskExecutor: Executor { // This requirement is repeated here as a non-override so that we // get a redundant witness-table entry for it. This allows us to // avoid drilling down to the base conformance just for the basic // work-scheduling operation. @_nonoverride func enqueue(_ job: UnownedJob) #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY // This requirement is repeated here as a non-override so that we // get a redundant witness-table entry for it. This allows us to // avoid drilling down to the base conformance just for the basic // work-scheduling operation. @_nonoverride @available(*, deprecated, message: "Implement 'enqueue(_: consuming ExecutorJob)' instead") func enqueue(_ job: consuming Job) #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY // This requirement is repeated here as a non-override so that we // get a redundant witness-table entry for it. This allows us to // avoid drilling down to the base conformance just for the basic // work-scheduling operation. @_nonoverride func enqueue(_ job: consuming ExecutorJob) #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY func asUnownedTaskExecutor() -> UnownedTaskExecutor } @_unavailableInEmbedded @available(SwiftStdlib 6.0, *) extension TaskExecutor { public func asUnownedTaskExecutor() -> UnownedTaskExecutor { UnownedTaskExecutor(ordinary: self) } } #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(SwiftStdlib 5.9, *) extension Executor { // Delegation goes like this: // Unowned Job -> Executor Job -> Job -> ---||--- public func enqueue(_ job: UnownedJob) { self.enqueue(ExecutorJob(job)) } public func enqueue(_ job: consuming ExecutorJob) { self.enqueue(Job(job)) } public func enqueue(_ job: consuming Job) { self.enqueue(UnownedJob(job)) } } #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(SwiftStdlib 5.9, *) extension SerialExecutor { @available(SwiftStdlib 5.9, *) public func asUnownedSerialExecutor() -> UnownedSerialExecutor { unsafe UnownedSerialExecutor(ordinary: self) } } @available(SwiftStdlib 5.9, *) extension SerialExecutor { @available(SwiftStdlib 5.9, *) public func isSameExclusiveExecutionContext(other: Self) -> Bool { return self === other } } /// An unowned reference to a serial executor (a `SerialExecutor` /// value). /// /// This is an optimized type used internally by the core scheduling /// operations. It is an unowned reference to avoid unnecessary /// reference-counting work even when working with actors abstractly. /// Generally there are extra constraints imposed on core operations /// in order to allow this. For example, keeping an actor alive must /// also keep the actor's associated executor alive; if they are /// different objects, the executor must be referenced strongly by the /// actor. @available(SwiftStdlib 5.1, *) @unsafe @frozen public struct UnownedSerialExecutor: Sendable { @usableFromInline internal var executor: Builtin.Executor /// SPI: Do not use. Cannot be marked @_spi, since we need to use it from Distributed module /// which needs to reach for this from an @_transparent function which prevents @_spi use. @available(SwiftStdlib 5.9, *) public var _executor: Builtin.Executor { unsafe self.executor } @inlinable public init(_ executor: Builtin.Executor) { unsafe self.executor = executor } @inlinable public init(ordinary executor: __shared E) { unsafe self.executor = Builtin.buildOrdinarySerialExecutorRef(executor) } /// Opts the executor into complex "same exclusive execution context" equality checks. /// /// This means what when asserting or assuming executors, and the current and expected /// executor are not the same instance (by object equality), the runtime may invoke /// `isSameExclusiveExecutionContext` in order to compare the executors for equality. /// /// Implementing such complex equality can be useful if multiple executor instances /// actually use the same underlying serialization context and can be therefore /// safely treated as the same serial exclusive execution context (e.g. multiple /// dispatch queues targeting the same serial queue). @available(SwiftStdlib 5.9, *) @inlinable public init(complexEquality executor: __shared E) { unsafe self.executor = Builtin.buildComplexEqualitySerialExecutorRef(executor) } @_spi(ConcurrencyExecutors) @available(SwiftStdlib 5.9, *) public var _isComplexEquality: Bool { unsafe _executor_isComplexEquality(self) } } @_unavailableInEmbedded @available(SwiftStdlib 6.0, *) @frozen public struct UnownedTaskExecutor: Sendable { @usableFromInline internal var executor: Builtin.Executor /// SPI: Do not use. Cannot be marked @_spi, since we need to use it from Distributed module /// which needs to reach for this from an @_transparent function which prevents @_spi use. @available(SwiftStdlib 6.0, *) public var _executor: Builtin.Executor { self.executor } @inlinable public init(_ executor: Builtin.Executor) { self.executor = executor } @inlinable public init(ordinary executor: __shared E) { self.executor = Builtin.buildOrdinaryTaskExecutorRef(executor) } } @_unavailableInEmbedded @available(SwiftStdlib 6.0, *) extension UnownedTaskExecutor: Equatable { @inlinable public static func == (_ lhs: UnownedTaskExecutor, _ rhs: UnownedTaskExecutor) -> Bool { unsafe unsafeBitCast(lhs.executor, to: (Int, Int).self) == unsafeBitCast(rhs.executor, to: (Int, Int).self) } } /// Returns either `true` or will CRASH if called from a different executor /// than the passed `executor`. /// /// This method will attempt to verify the current executor against `executor`, /// and as a last-resort call through to `SerialExecutor.checkIsolated`. /// /// This method will never return `false`. It either can verify we're on the /// correct executor, or will crash the program. It should be used in /// isolation correctness guaranteeing APIs. /// /// Generally, Swift programs should be constructed such that it is statically /// known that a specific executor is used, for example by using global actors or /// custom executors. However, in some APIs it may be useful to provide an /// additional runtime check for this, especially when moving towards Swift /// concurrency from other runtimes which frequently use such assertions. /// /// - Parameter executor: The expected executor. @_spi(ConcurrencyExecutors) @available(SwiftStdlib 5.9, *) @_silgen_name("swift_task_isOnExecutor") // This function will CRASH rather than return `false`! public func _taskIsOnExecutor(_ executor: Executor) -> Bool @_spi(ConcurrencyExecutors) @available(SwiftStdlib 5.9, *) @_silgen_name("swift_executor_isComplexEquality") public func _executor_isComplexEquality(_ executor: UnownedSerialExecutor) -> Bool @available(SwiftStdlib 5.1, *) @_transparent public // COMPILER_INTRINSIC func _checkExpectedExecutor(_filenameStart: Builtin.RawPointer, _filenameLength: Builtin.Word, _filenameIsASCII: Builtin.Int1, _line: Builtin.Word, _executor: Builtin.Executor) { if _taskIsCurrentExecutor(_executor) { return } _reportUnexpectedExecutor( _filenameStart, _filenameLength, _filenameIsASCII, _line, _executor) } /// Primarily a debug utility. /// /// If the passed in ExecutorJob is a Task, returns the complete 64bit TaskId, /// otherwise returns only the job's 32bit Id. /// /// - Returns: the Id stored in this ExecutorJob or Task, for purposes of debug printing @available(SwiftStdlib 5.9, *) @_silgen_name("swift_task_getJobTaskId") internal func _getJobTaskId(_ job: UnownedJob) -> UInt64 @available(SwiftStdlib 5.9, *) @_silgen_name("_task_serialExecutor_isSameExclusiveExecutionContext") internal func _task_serialExecutor_isSameExclusiveExecutionContext(current currentExecutor: E, executor: E) -> Bool where E: SerialExecutor { currentExecutor.isSameExclusiveExecutionContext(other: executor) } @available(SwiftStdlib 6.0, *) @_silgen_name("_task_serialExecutor_checkIsolated") internal func _task_serialExecutor_checkIsolated(executor: E) where E: SerialExecutor { executor.checkIsolated() } @available(SwiftStdlib 6.2, *) @_silgen_name("_task_serialExecutor_isIsolatingCurrentContext") internal func _task_serialExecutor_isIsolatingCurrentContext(executor: E) -> Bool where E: SerialExecutor { return executor.isIsolatingCurrentContext() } /// Obtain the executor ref by calling the executor's `asUnownedSerialExecutor()`. /// The obtained executor ref will have all the user-defined flags set on the executor. @available(SwiftStdlib 5.9, *) @_silgen_name("_task_serialExecutor_getExecutorRef") internal func _task_serialExecutor_getExecutorRef(_ executor: E) -> Builtin.Executor where E: SerialExecutor { return unsafe executor.asUnownedSerialExecutor().executor } /// Obtain the executor ref by calling the executor's `asUnownedTaskExecutor()`. /// The obtained executor ref will have all the user-defined flags set on the executor. @_unavailableInEmbedded @available(SwiftStdlib 6.0, *) @_silgen_name("_task_taskExecutor_getTaskExecutorRef") internal func _task_taskExecutor_getTaskExecutorRef(_ taskExecutor: E) -> Builtin.Executor where E: TaskExecutor { return taskExecutor.asUnownedTaskExecutor().executor } // Used by the concurrency runtime @available(SwiftStdlib 5.1, *) @_silgen_name("_swift_task_enqueueOnExecutor") internal func _enqueueOnExecutor(job unownedJob: UnownedJob, executor: E) where E: SerialExecutor { #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY if #available(SwiftStdlib 5.9, *) { executor.enqueue(ExecutorJob(context: unownedJob._context)) } else { executor.enqueue(unownedJob) } #else // SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY executor.enqueue(unownedJob) #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY } @_unavailableInEmbedded @available(SwiftStdlib 6.0, *) @_silgen_name("_swift_task_enqueueOnTaskExecutor") internal func _enqueueOnTaskExecutor(job unownedJob: UnownedJob, executor: E) where E: TaskExecutor { #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY executor.enqueue(ExecutorJob(context: unownedJob._context)) #else // SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY executor.enqueue(unownedJob) #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY } #if SWIFT_CONCURRENCY_USES_DISPATCH // This must take a DispatchQueueShim, not something like AnyObject, // or else SILGen will emit a retain/release in unoptimized builds, // which won't work because DispatchQueues aren't actually // Swift-retainable. @available(SwiftStdlib 5.1, *) @_silgen_name("swift_task_enqueueOnDispatchQueue") internal func _enqueueOnDispatchQueue(_ job: UnownedJob, queue: DispatchQueueShim) /// Used by the runtime solely for the witness table it produces. /// FIXME: figure out some way to achieve that which doesn't generate /// all the other metadata /// /// Expected to work for any primitive dispatch queue; note that this /// means a dispatch_queue_t, which is not the same as DispatchQueue /// on platforms where that is an instance of a wrapper class. @available(SwiftStdlib 5.1, *) internal final class DispatchQueueShim: @unchecked Sendable, SerialExecutor { func enqueue(_ job: UnownedJob) { _enqueueOnDispatchQueue(job, queue: self) } func asUnownedSerialExecutor() -> UnownedSerialExecutor { return unsafe UnownedSerialExecutor(ordinary: self) } } #endif // SWIFT_CONCURRENCY_USES_DISPATCH @available(SwiftStdlib 6.1, *) @_silgen_name("swift_task_deinitOnExecutor") @usableFromInline internal func _deinitOnExecutor(_ object: __owned AnyObject, _ work: @convention(thin) (__owned AnyObject) -> Void, _ executor: Builtin.Executor, _ flags: Builtin.Word)