//===----------------------------------------------------------------------===// // // 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 @available(SwiftStdlib 5.1, *) @_silgen_name("swift_continuation_logFailedCheck") internal func logFailedCheck(_ message: UnsafeRawPointer) /// Implementation class that holds the `UnsafeContinuation` instance for /// a `CheckedContinuation`. @available(SwiftStdlib 5.1, *) internal final class CheckedContinuationCanary: @unchecked Sendable { // The instance state is stored in tail-allocated raw memory, so that // we can atomically check the continuation state. private init() { fatalError("must use create") } private static func _create(continuation: UnsafeRawPointer, function: String) -> CheckedContinuationCanary { let instance = unsafe Builtin.allocWithTailElems_1(CheckedContinuationCanary.self, 1._builtinWordValue, (UnsafeRawPointer?, String).self) unsafe instance._continuationPtr.initialize(to: continuation) unsafe instance._functionPtr.initialize(to: function) return instance } private var _continuationPtr: UnsafeMutablePointer { return unsafe UnsafeMutablePointer( Builtin.projectTailElems(self, (UnsafeRawPointer?, String).self)) } private var _functionPtr: UnsafeMutablePointer { let tailPtr = unsafe UnsafeMutableRawPointer( Builtin.projectTailElems(self, (UnsafeRawPointer?, String).self)) let functionPtr = unsafe tailPtr + MemoryLayout<(UnsafeRawPointer?, String)>.offset(of: \(UnsafeRawPointer?, String).1)! return unsafe functionPtr.assumingMemoryBound(to: String.self) } internal static func create(continuation: UnsafeContinuation, function: String) -> CheckedContinuationCanary { return unsafe _create( continuation: unsafeBitCast(continuation, to: UnsafeRawPointer.self), function: function) } internal var function: String { return unsafe _functionPtr.pointee } // Take the continuation away from the container, or return nil if it's // already been taken. internal func takeContinuation() -> UnsafeContinuation? { // Atomically exchange the current continuation value with a null pointer. let rawContinuationPtr = unsafe unsafeBitCast(_continuationPtr, to: Builtin.RawPointer.self) let rawOld = Builtin.atomicrmw_xchg_seqcst_Word(rawContinuationPtr, 0._builtinWordValue) return unsafe unsafeBitCast(rawOld, to: UnsafeContinuation?.self) } deinit { unsafe _functionPtr.deinitialize(count: 1) // Log if the continuation was never consumed before the instance was // destructed. if unsafe _continuationPtr.pointee != nil { #if !$Embedded unsafe logFailedCheck("SWIFT TASK CONTINUATION MISUSE: \(function) leaked its continuation without resuming it. This may cause tasks waiting on it to remain suspended forever.\n") #else fatalError("SWIFT TASK CONTINUATION MISUSE") #endif } } } /// A mechanism to interface /// between synchronous and asynchronous code, /// logging correctness violations. /// /// A *continuation* is an opaque representation of program state. /// To create a continuation in asynchronous code, /// call the `withCheckedContinuation(isolation:function:_:)` or /// `withCheckedThrowingContinuation(isolation:function:_:)` function. /// To resume the asynchronous task, /// call the `resume(returning:)`, /// `resume(throwing:)`, /// `resume(with:)`, /// or `resume()` method. /// /// - Important: You must call a resume method exactly once /// on every execution path throughout the program. /// /// Resuming from a continuation more than once is undefined behavior. /// Never resuming leaves the task in a suspended state indefinitely, /// and leaks any associated resources. /// `CheckedContinuation` logs a message /// if either of these invariants is violated. /// /// `CheckedContinuation` performs runtime checks /// for missing or multiple resume operations. /// `UnsafeContinuation` avoids enforcing these invariants at runtime /// because it aims to be a low-overhead mechanism /// for interfacing Swift tasks with /// event loops, delegate methods, callbacks, /// and other non-`async` scheduling mechanisms. /// However, during development, the ability to verify that the /// invariants are being upheld in testing is important. /// Because both types have the same interface, /// you can replace one with the other in most circumstances, /// without making other changes. @available(SwiftStdlib 5.1, *) public struct CheckedContinuation: Sendable { private let canary: CheckedContinuationCanary /// Creates a checked continuation from an unsafe continuation. /// /// Instead of calling this initializer, /// most code calls the `withCheckedContinuation(function:_:)` or /// `withCheckedThrowingContinuation(function:_:)` function instead. /// You only need to initialize /// your own `CheckedContinuation` if you already have an /// `UnsafeContinuation` you want to impose checking on. /// /// - Parameters: /// - continuation: An instance of `UnsafeContinuation` /// that hasn't yet been resumed. /// After passing the unsafe continuation to this initializer, /// don't use it outside of this object. /// - function: A string identifying the declaration that is the notional /// source for the continuation, used to identify the continuation in /// runtime diagnostics related to misuse of this continuation. public init(continuation: UnsafeContinuation, function: String = #function) { canary = unsafe CheckedContinuationCanary.create( continuation: continuation, function: function) } /// Resume the task awaiting the continuation by having it return normally /// from its suspension point. /// /// - Parameter value: The value to return from the continuation. /// /// A continuation must be resumed exactly once. If the continuation has /// already been resumed through this object, then the attempt to resume /// the continuation will trap. /// /// After `resume` enqueues the task, control immediately returns to /// the caller. The task continues executing when its executor is /// able to reschedule it. public func resume(returning value: sending T) { if let c: UnsafeContinuation = unsafe canary.takeContinuation() { unsafe c.resume(returning: value) } else { #if !$Embedded fatalError("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, returning \(value)!\n") #else fatalError("SWIFT TASK CONTINUATION MISUSE") #endif } } /// Resume the task awaiting the continuation by having it throw an error /// from its suspension point. /// /// - Parameter error: The error to throw from the continuation. /// /// A continuation must be resumed exactly once. If the continuation has /// already been resumed through this object, then the attempt to resume /// the continuation will trap. /// /// After `resume` enqueues the task, control immediately returns to /// the caller. The task continues executing when its executor is /// able to reschedule it. public func resume(throwing error: __owned E) { if let c: UnsafeContinuation = unsafe canary.takeContinuation() { unsafe c.resume(throwing: error) } else { #if !$Embedded fatalError("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, throwing \(error)!\n") #else fatalError("SWIFT TASK CONTINUATION MISUSE") #endif } } } @available(SwiftStdlib 5.1, *) extension CheckedContinuation { /// Resume the task awaiting the continuation by having it either /// return normally or throw an error based on the state of the given /// `Result` value. /// /// - Parameter result: A value to either return or throw from the /// continuation. /// /// A continuation must be resumed exactly once. If the continuation has /// already been resumed through this object, then the attempt to resume /// the continuation will trap. /// /// After `resume` enqueues the task, control immediately returns to /// the caller. The task continues executing when its executor is /// able to reschedule it. @_alwaysEmitIntoClient public func resume(with result: __shared sending Result) where E == Error { switch result { case .success(let val): self.resume(returning: val) case .failure(let err): self.resume(throwing: err) } } /// Resume the task awaiting the continuation by having it either /// return normally or throw an error based on the state of the given /// `Result` value. /// /// - Parameter result: A value to either return or throw from the /// continuation. /// /// A continuation must be resumed exactly once. If the continuation has /// already been resumed through this object, then the attempt to resume /// the continuation will trap. /// /// After `resume` enqueues the task, control immediately returns to /// the caller. The task continues executing when its executor is /// able to reschedule it. @_alwaysEmitIntoClient public func resume(with result: __shared sending Result) { switch result { case .success(let val): self.resume(returning: val) case .failure(let err): self.resume(throwing: err) } } /// Resume the task awaiting the continuation by having it return normally /// from its suspension point. /// /// A continuation must be resumed exactly once. If the continuation has /// already been resumed through this object, then the attempt to resume /// the continuation will trap. /// /// After `resume` enqueues the task, control immediately returns to /// the caller. The task continues executing when its executor is /// able to reschedule it. @_alwaysEmitIntoClient public func resume() where T == Void { self.resume(returning: ()) } } /// Invokes the passed in closure with a checked continuation for the current task. /// /// The body of the closure executes synchronously on the calling task, and once it returns /// the calling task is suspended. It is possible to immediately resume the task, or escape the /// continuation in order to complete it afterwards, which will then resume the suspended task. /// /// You must invoke the continuation's `resume` method exactly once. /// /// Missing to invoke it (eventually) will cause the calling task to remain suspended /// indefinitely which will result in the task "hanging" as well as being leaked with /// no possibility to destroy it. /// /// The checked continuation offers detection of misuse, and dropping the last reference /// to it, without having resumed it will trigger a warning. Resuming a continuation twice /// is also diagnosed and will cause a crash. /// /// - Parameters: /// - function: A string identifying the declaration that is the notional /// source for the continuation, used to identify the continuation in /// runtime diagnostics related to misuse of this continuation. /// - body: A closure that takes a `CheckedContinuation` parameter. /// - Returns: The value continuation is resumed with. /// /// - SeeAlso: `withCheckedThrowingContinuation(function:_:)` /// - SeeAlso: `withUnsafeContinuation(function:_:)` /// - SeeAlso: `withUnsafeThrowingContinuation(function:_:)` @inlinable @available(SwiftStdlib 5.1, *) #if !$Embedded @backDeployed(before: SwiftStdlib 6.0) #endif public func withCheckedContinuation( isolation: isolated (any Actor)? = #isolation, function: String = #function, _ body: (CheckedContinuation) -> Void ) async -> sending T { return await Builtin.withUnsafeContinuation { let unsafeContinuation = unsafe UnsafeContinuation($0) return body(unsafe CheckedContinuation(continuation: unsafeContinuation, function: function)) } } // Note: hack to stage out @_unsafeInheritExecutor forms of various functions // in favor of #isolation. The _unsafeInheritExecutor_ prefix is meaningful // to the type checker. // // This function also doubles as an ABI-compatibility shim predating the // introduction of #isolation. @available(SwiftStdlib 5.1, *) @_unsafeInheritExecutor // ABI compatibility with Swift 5.1 @_silgen_name("$ss23withCheckedContinuation8function_xSS_yScCyxs5NeverOGXEtYalF") public func _unsafeInheritExecutor_withCheckedContinuation( function: String = #function, _ body: (CheckedContinuation) -> Void ) async -> T { return await unsafe withUnsafeContinuation { body(unsafe CheckedContinuation(continuation: $0, function: function)) } } /// Invokes the passed in closure with a checked continuation for the current task. /// /// The body of the closure executes synchronously on the calling task, and once it returns /// the calling task is suspended. It is possible to immediately resume the task, or escape the /// continuation in order to complete it afterwards, which will then resume the suspended task. /// /// If `resume(throwing:)` is called on the continuation, this function throws that error. /// /// You must invoke the continuation's `resume` method exactly once. /// /// Missing to invoke it (eventually) will cause the calling task to remain suspended /// indefinitely which will result in the task "hanging" as well as being leaked with /// no possibility to destroy it. /// /// The checked continuation offers detection of misuse, and dropping the last reference /// to it, without having resumed it will trigger a warning. Resuming a continuation twice /// is also diagnosed and will cause a crash. /// /// - Parameters: /// - function: A string identifying the declaration that is the notional /// source for the continuation, used to identify the continuation in /// runtime diagnostics related to misuse of this continuation. /// - body: A closure that takes a `CheckedContinuation` parameter. /// - Returns: The value continuation is resumed with. /// /// - SeeAlso: `withCheckedContinuation(function:_:)` /// - SeeAlso: `withUnsafeContinuation(function:_:)` /// - SeeAlso: `withUnsafeThrowingContinuation(function:_:)` @inlinable @available(SwiftStdlib 5.1, *) #if !$Embedded @backDeployed(before: SwiftStdlib 6.0) #endif public func withCheckedThrowingContinuation( isolation: isolated (any Actor)? = #isolation, function: String = #function, _ body: (CheckedContinuation) -> Void ) async throws -> sending T { return try await Builtin.withUnsafeThrowingContinuation { let unsafeContinuation = unsafe UnsafeContinuation($0) return body(unsafe CheckedContinuation(continuation: unsafeContinuation, function: function)) } } // Note: hack to stage out @_unsafeInheritExecutor forms of various functions // in favor of #isolation. The _unsafeInheritExecutor_ prefix is meaningful // to the type checker. // // This function also doubles as an ABI-compatibility shim predating the // introduction of #isolation. @available(SwiftStdlib 5.1, *) @_unsafeInheritExecutor // ABI compatibility with Swift 5.1 @_silgen_name("$ss31withCheckedThrowingContinuation8function_xSS_yScCyxs5Error_pGXEtYaKlF") public func _unsafeInheritExecutor_withCheckedThrowingContinuation( function: String = #function, _ body: (CheckedContinuation) -> Void ) async throws -> T { return try await unsafe withUnsafeThrowingContinuation { body(unsafe CheckedContinuation(continuation: $0, function: function)) } } #if _runtime(_ObjC) // Intrinsics used by SILGen to create, resume, or fail checked continuations. @available(SwiftStdlib 5.1, *) @_alwaysEmitIntoClient internal func _createCheckedContinuation( _ continuation: __owned UnsafeContinuation ) -> CheckedContinuation { return unsafe CheckedContinuation(continuation: continuation) } @available(SwiftStdlib 5.1, *) @_alwaysEmitIntoClient internal func _createCheckedThrowingContinuation( _ continuation: __owned UnsafeContinuation ) -> CheckedContinuation { return unsafe CheckedContinuation(continuation: continuation) } @available(SwiftStdlib 5.1, *) @_alwaysEmitIntoClient internal func _resumeCheckedContinuation( _ continuation: CheckedContinuation, _ value: sending T ) { continuation.resume(returning: value) } @available(SwiftStdlib 5.1, *) @_alwaysEmitIntoClient internal func _resumeCheckedThrowingContinuation( _ continuation: CheckedContinuation, _ value: sending T ) { continuation.resume(returning: value) } @available(SwiftStdlib 5.1, *) @_alwaysEmitIntoClient internal func _resumeCheckedThrowingContinuationWithError( _ continuation: CheckedContinuation, _ error: consuming Error ) { continuation.resume(throwing: error) } #endif