Why does this swift async-await code fails with leaked continuation? - swift

I am experimenting with Swift async-await and AsyncSequence protocol,
Here is the code:
struct AsyncNumbers<Element: Numeric>: AsyncSequence {
private let numbers: [Element]
init(_ numbers: [Element]) {
self.numbers = numbers
}
func makeAsyncIterator() -> AsyncNumberIterator {
return AsyncNumberIterator(numbers)
}
}
extension AsyncNumbers {
struct AsyncNumberIterator: AsyncIteratorProtocol {
private let numbers: [Element]
private var index = -1
init(_ numbers: [Element]) {
self.numbers = numbers
}
mutating func next() async -> AsyncNumbers<Element>.Element? {
index += 1
return await withCheckedContinuation { [self] continuation in
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { timer in
guard index < numbers.count else {
continuation.resume(returning: nil)
timer.invalidate()
return
}
continuation.resume(returning: numbers[index])
}
}
}
}
}
func printNumbers() async {
let numbers = AsyncNumbers([5,78,3,45,99,100,23,4,7,8,9])
for await num in numbers {
print("Number:", num)
}
print("End")
}
Task {
await printNumbers()
}
This code fails before even the first number is printed to the console, with error SWIFT TASK CONTINUATION MISUSE: next() leaked its continuation!,
I don't understand why I am getting this error here, the timer block is called only once after 2 seconds no duplicate calls to the continuation block are happening here,
Does anyone know what might the issue?
Thank you in advance :)

As several rightly commented, the problem is your use of Timer and Concurrency.
Timer.scheduledTimer requires a runloop to work. In contrast to the main thread, secondary threads do not have a runloop until you explicitly create it.
Since you are calling this from a coroutine in the body of an await function, your thread context is a secondary thread. This means that at the time of you calling Timer.scheduledTimer you are not having a runloop, hence the timer callback will never be called and – depending on the context of the surrounding code – Swift rightly warns you that you are leaking the coroutine.

Related

SwiftUI Running Async Code Within Synchronous Handler

I am creating a game where, after a user signs in, I want to send their playerID to my backend. Since this is in SwiftUI, I have the following (btw I know we're not supposed to be using playerID anymore but this is just a minimal reproducible example):
import SwiftUI
import GameKit
struct SampleView: View {
let localPlayer = GKLocalPlayer.local
func authenticateUser() async {
localPlayer.authenticateHandler = { vc, error in
guard error == nil else {
print(error?.localizedDescription ?? "")
return
}
if localPlayer.isAuthenticated {
let playerID = localPlayer.playerID
GKAccessPoint.shared.isActive = localPlayer.isAuthenticated
// here is where I would like to make an async call
}
}
}
var body: some View {
VStack {
Text("Sample View")
}
.task {
await authenticateUser()
}
}
}
struct SampleView_Previews: PreviewProvider {
static var previews: some View {
SampleView()
}
}
In the comment indicating where I'd like to place an async call, I have tried something like
await myBackendCall(playerID)
but this throws the error
Invalid conversion from 'async' function of type '(UIViewController?, (any Error)?) async -> Void' to synchronous function type '(UIViewController?, (any Error)?) -> Void'
which makes sense given that the authenticateHandler function isn't an async function.
What is the best approach here? I'd like to wait until I have the value for PlayerID, and then call await myBackendCall(playerID). Any advice here would be much appreciated, thank you!
To make a completion handler async use a continuation, it returns true if the user is authenticated, otherwise false.
func authenticateUser() async -> Bool {
return await withCheckedContinuation { continuation in
localPlayer.authenticateHandler = { vc, error in
if let error {
print(error.localizedDescription)
continuation.resume(returning: false)
} else {
continuation.resume(returning: localPlayer.isAuthenticated)
}
}
}
}
and in the task scope write
.task {
let isAuthenticated = await authenticateUser()
if isAuthenticated {
let playerID = localPlayer.playerID
GKAccessPoint.shared.isActive = localPlayer.isAuthenticated
// here is where I would like to make an async call
}
}
When you have a callback closure (like authenticateHandler), it invariably means that the closure may possibly be called multiple times. The appropriate async-await pattern would be an AsyncSequence (e.g., an AsyncStream or an AsyncThrowingStream).
So, you might wrap authenticateHandler in an asynchronous sequence, like so:
func viewControllers() -> AsyncThrowingStream<UIViewController, Error> {
AsyncThrowingStream<UIViewController, Error> { continuation in
GKLocalPlayer.local.authenticateHandler = { viewController, error in
if let viewController {
continuation.yield(viewController)
} else {
continuation.finish(throwing: error ?? GKError(.unknown))
}
}
}
}
Then you could do things like:
.task {
do {
for try await _ in viewControllers() {
GKAccessPoint.shared.isActive = GKLocalPlayer.local.isAuthenticated
// do your subsequent `async` call here
}
} catch {
GKAccessPoint.shared.isActive = false
print(error.localizedDescription)
}
}
For more information, see WWDC 2021 video Meet AsyncSequence. But the idea is that withCheckedContinuation (or withThrowingCheckedContinuation) is designed for completion handler patterns, where it must be called once, and only once. If you use a checked continuation and the closure is called again, it will be “logging correctness violations”, because “You must call a resume method exactly once on every execution path throughout the program.”
Instead, in cases where it may be called multiple times, consider handling it as an asynchronous sequence.

Issues with retain cycle using AsyncStream in a Task

Found this issue while working with the new Swift concurrency tools.
Here's the setup:
class FailedDeinit {
init() {
print(#function, id)
task = Task {
await subscribe()
}
}
deinit {
print(#function, id)
}
func subscribe() async {
let stream = AsyncStream<Double> { _ in }
for await p in stream {
print("\(p)")
}
}
private var task: Task<(), Swift.Error>?
let id = UUID()
}
var instance: FailedDeinit? = FailedDeinit()
instance = nil
Running this code in a Playground yields this:
init() F007863C-9187-4591-A4F4-BC6BC990A935
!!! The deinit method is never called!!!
Strangely, when I change the code to this:
class SuccessDeinit {
init() {
print(#function, id)
task = Task {
let stream = AsyncStream<Double> { _ in }
for await p in stream {
print("\(p)")
}
}
}
deinit {
print(#function, id)
}
private var task: Task<(), Swift.Error>?
let id = UUID()
}
var instance: SuccessDeinit? = SuccessDeinit()
instance = nil
By moving the code from the method subscribe() directly in the Task, the result in the console changes to this:
init() 0C455201-89AE-4D7A-90F8-D6B2D93493B1
deinit 0C455201-89AE-4D7A-90F8-D6B2D93493B1
This may be a bug or not but there is definitely something that I do not understand. I would welcome any insight about that.
~!~!~!~!
This is crazy (or maybe I am?) but with a SwiftUI macOS project. I still DON'T get the same behaviour as you. Look at that code where I kept the same definition of the FailedDeinit and SuccessDeinit classes but used them within a SwiftUI view.
struct ContentView: View {
#State private var failed: FailedDeinit?
#State private var success: SuccessDeinit?
var body: some View {
VStack {
HStack {
Button("Add failed") { failed = .init() }
Button("Remove failed") { failed = nil }
}
HStack {
Button("Add Success") { success = .init() }
Button("Remove Success") { success = nil }
}
}
}
}
class FailedDeinit {
init() {
print(#function, id)
task = Task { [weak self] in
await self?.subscribe()
}
}
deinit {
print(#function, id)
}
func subscribe() async {
let stream = AsyncStream<Double> { _ in }
for await p in stream {
print("\(p)")
}
}
private var task: Task<(), Swift.Error>?
let id = UUID()
}
Consider the following:
task = Task {
await subscribe()
}
It is true that introduces a strong reference to self. You can resolve that strong reference with:
task = Task { [weak self] in
await self?.subscribe()
}
But that is only part of the problem here. This [weak self] pattern only helps us in this case if either the Task has not yet started or if it has finished.
The issue is that as soon as subscribe starts executing, despite the weak reference in the closure, it will keep a strong reference to self until subscribe finishes. So, this weak reference is prudent, but it is not the whole story.
The issue here is more subtle than appears at first glance. Consider the following:
func subscribe() async {
let stream = AsyncStream<Double> { _ in }
for await p in stream {
print("\(p)")
}
}
The subscribe method will keep executing until the stream calls finish. But you never finish the stream. (You don’t yield any values, either. Lol.) Anyway, without anything in the AsyncStream, once subscribe starts it will never complete and thus will never release self.
So let us consider your second rendition, when you create the Task, bypassing subscribe:
task = Task {
let stream = AsyncStream<Double> { _ in }
for await p in stream {
print("\(p)")
}
}
Yes, you will see the object be deallocated, but you are neglecting to notice that this Task will never finish, either! So, do not be lulled into into a false sense of security just because the containing object was released: The Task never finishes! The memory associated with that Task will never get released (even if the parent object, FailedDeinit in your example, is).
This all can be illustrated by changing your stream to actually yield values and eventually finish:
task = Task {
let stream = AsyncStream<Double> { continuation in
Task {
for i in 0 ..< 10 {
try await Task.sleep(nanoseconds: 1 * NSEC_PER_SECOND)
continuation.yield(Double(i))
}
continuation.finish()
}
}
for await p in stream {
print("\(p)")
}
print("all done")
}
In this case, if you dismiss it while the stream is underway, you will see that the AsyncStream continues until it finishes. (And, if you happen to be doing this inside a method, the object in question will also be retained until the task is canceled.)
So, what you need to do is to cancel the Task if you want the AsyncStream to finish. And you also should implement onTermination of the continuation in such a manner that it stops the asynchronous stream.
But, the result is that if I cancel this when the view controller (or whatever) is released, then my example yielding values 0 through 9 will stop and the task will be freed.
It all comes down to what your AsyncStream is really doing. But in the process of simplifying the MCVE and removing the contents of the AsyncStream, you simultaneously do not handle cancelation and never call finish. Those two, combined, manifest the problem you describe.
This doesn't really have anything to do with async/await or AsyncStream. It's a perfectly normal retain cycle. You (the FailedDeinit instance) are retaining the task, but the task refers to subscribe which is a method of you, i.e. self, so the task is retaining you. So simply break the retain cycle just like you would break any other retain cycle. Just change
task = Task {
await subscribe()
}
To
task = Task { [weak self] in
await self?.subscribe()
}
Also, be sure to test in a real project, not a playground, as playgrounds are not indicative of anything in this regard. Here's the code I used:
import UIKit
class FailedDeinit {
init() {
print(#function, id)
task = Task { [weak self] in
await self?.subscribe()
}
}
deinit {
print(#function, id)
}
func subscribe() async {
let stream = AsyncStream<Double> { _ in }
for await p in stream {
print("\(p)")
}
}
private var task: Task<(), Swift.Error>?
let id = UUID()
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var instance: FailedDeinit? = FailedDeinit()
instance = nil
}
}

SWIFT TASK CONTINUATION MISUSE: leaked its continuation - for delegate?

I'm trying to extend my class with async/await capabilities, but at run-time there is an error in the console:
SWIFT TASK CONTINUATION MISUSE: query(_:) leaked its continuation!
Below is the class I'm trying to add the continuation to which uses a delegate:
class LocalSearch: NSObject, MKLocalSearchCompleterDelegate {
private let completer: MKLocalSearchCompleter
private var completionContinuation: CheckedContinuation<[MKLocalSearchCompletion], Error>?
init() {
completer = MKLocalSearchCompleter()
super.init()
completer.delegate = self
}
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
completionContinuation = continuation
guard !value.isEmpty else {
completionContinuation?.resume(returning: [])
completionContinuation = nil
return
}
completer.queryFragment = value
}
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
completionContinuation?.resume(returning: completer.results)
completionContinuation = nil
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
completionContinuation?.resume(throwing: error)
completionContinuation = nil
}
}
This is how I use it:
let localSearch = LocalSearch()
do {
let results = try await localSearch.query("toront")
print(results)
} catch {
print(error)
}
What am I doing wrong or is there a better way to achieve this?
This message appears if a continuation you created via withCheckedContinuation, or withCheckedThrowingContinuation doesn't report success or failure before being discarded. This is will lead to resource leaking:
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.
Excerpt taken from the documentation for CheckedContinuation (emphasis mine).
Here are possible causes for this to happen:
not all code paths resume the continuation, e.g. there is an if/guard/case that exits the scope without instructing the continuation to report success/failure
class Searcher {
func search(for query: String) async throws -> [String] {
await withCheckedContinuation { continuation in
someFunctionCall(withCompletion: { [weak self] in
guard let `self` = self else {
// if `result` doesn't have the expected value, the continuation
// will never report completion
return
}
continuation.resume(returning: something)
})
}
}
}
an "old"-style async function doesn't call the completion closure on all paths; this is a less obvious reason, and sometimes a harder to debug one:
class Searcher {
private let internalSearcher = InternalSearcher()
func search(for query: String) async throws -> [String] {
await withCheckedContinuation { continuation in
internalSearcher.search(query: query) { result in
// everything fine here
continuation.resume(returning: result)
}
}
}
}
class InternalSearcher {
func search(query: String, completion: #escaping ([String]) -> Void {
guard !query.isEmpty else {
return
// legit precondition check, however in this case,
// the completion is not called, meaning that the
// upstream function call will imediately discard
// the continuation, without instructing it to report completion
}
// perform the actual search, report the results
}
}
the continuation is stored as a property when a function is called; this means that if a second function call happens while the first one is in progress, then the first completion will be overwritten, meaning it will never report completion:
class Searcher {
var continuation: CheckedContinuation<[String], Error>?
func search(for query: String) async throws -> [String] {
try await withCheckedTrowingContinuation { continuation in
// note how a second call to `search` will overwrite the
// previous continuation, in case the delegate method was
// not yet called
self.continuation = continuation
// trigger the searching mechanism
}
}
func delegateMethod(results: [String]) {
self.continuation.resume(returning: results)
self.continuation = nil
}
}
#1 and #2 usually happen when dealing with functions that accept completion callbacks, while #3 usually happens when dealing with delegate methods, since in that case, we need to store the continuation somewhere outside the async function scope, in order to access it from the delegate methods.
Bottom line - try to make sure that a continuation reports completion on all possible code paths, otherwise, the continuation will indefinitely block the async call, leading to the task associated with that async call leaking its associated resources.
In your case, what likely happened is that a second query() call occurred before the first call had a chance to finish.
And in that case, the first continuation got discarded without reporting completion, meaning the first caller never continued the execution after the try await query() call, and this is not ok at all.
The following piece of code needs to be fixed, in order not to overwrite a pending continuation:
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
completionContinuation = continuation
One quick solution would be to store an array of continuations, resume all continuations in the delegate methods, and clear the array afterward. Also, in your specific case, you could simply extract the validation out of the continuation code, as you are allowed to synchronously return/throw, even when in an async function:
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
guard !value.isEmpty else {
return []
}
return try await withCheckedThrowingContinuation { continuation in
continuations.append(continuation)
completer.queryFragment = value
}
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
continuations.forEach { $0.resume(returning: completer.results) }
continuations.removeAll()
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
continuations.forEach { $0.resume(throwing: error) }
continuations.removeAll()
}
I'd also strongly recommend converting your class to an actor, in order to avoid data races, regardless if you store one continuation, like now, or you use an array. The reason is that the continuation property is consumed from multiple threads and at some point you might end up with two threads concurrently accessing/writing the property.
I think the problem is here -
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
// storing into a variable makes this continuation instance outlive the scope of it
// In other words, it leaks OR escapes the scope
// This is same as why we need to add #escaping attribute for callback functions arguments
// those are either stored in variables like this
// or passed to other functions (escaping scope of current function)
completionContinuation = continuation
// Try commenting above line, the warning should go away
// And your code will stop working as well :)
// How to design this component is other question.
}
}
UPDATE
import MapKit
class LocalSearch: NSObject, MKLocalSearchCompleterDelegate {
typealias Completion = (_ results: [MKLocalSearchCompletion]?, _ error: Error?) -> Void
private let completer: MKLocalSearchCompleter
private var completion: Completion?
override init() {
completer = MKLocalSearchCompleter()
super.init()
completer.delegate = self
}
func query(_ value: String, completion: #escaping Completion) {
self.completion = completion
completer.queryFragment = value
}
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
guard !value.isEmpty else {
continuation.resume(returning: [])
return
}
self.query(value, completion: { (results, error) in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: results ?? [])
}
})
}
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
completion?(completer.results, nil)
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
completion?(nil, error)
}
}

Working with Swift completion handlers for chained functions

I am chaining some functions together and I can't figure out how to call a completion handler with a return value once all the functions are done running.
class AirQualityProvider {
var aBlock: ((Int?) -> Void)?
func getAirQuality(completion: #escaping (Int?) -> Void) {
aBlock = completion
callAPI()
}
private func callAPI() {
let data = Data()
parseDataForAQI(data: data)
}
private func parseDataForAQI(data: Data) {
for d in data {
dosomeMath(d)
}
}
private func dosomeMath(data: Int) {
// HERE IS WHERE I WANT IT TO SUM UP ALL THE NUMBERS
THEN ONLY RETURN ONE VALUE using a completion handler.
Currently, it returns the average as it is being generated.
}
Almost got it working with help to Alexander. The code Alexander supplied works perfectly, it is amazing. The issue is, when I run taskrunner inside alamofire it returns empty. Outside alamofire it works as usual. I need to run this inside alamofire.
func A(json : JSON){
for (key,subJson) in json{
if subJson["free"].doubleValue > 0.0 {
func B(asset: subJson["asset"].stringValue, json: subJson)
}
}
print(taskRunner.getResults())
}
func B(asset : String, json : JSON){
//OUTSIDE ALAMOFIRE WORKS
self.taskRunner.execute{
return 100
}
Alamofire.request(url).responseJSON { response in
//INSIDE ALAMOFIRE DOESN'T WORK. Returns []
self.taskRunner.execute{
return 100
}
}
}
I would use a dispatch queue to synchronize the aggregation of results (by synchronizing Array.append(_:) calls, and the subsequent reading of the array). Here's a simple example:
import Dispatch
import Foundation
class ParallelTaskRunner<Result> {
private var results = [Result]()
private let group = DispatchGroup()
private let resultAggregatorQueue = DispatchQueue(label: "Result Aggregator")
func execute(_ closure: (#escaping (Result) -> Void) -> Void) {
group.enter() // Register that a new task is in-flight
closure { result in
self.resultAggregatorQueue.sync { // Synchronize access to the array
self.results.append(result) // Record the result
}
self.group.leave() // This task is done
}
}
func getResults() -> [Result] {
group.wait() // Make sure all in-flight tasks are done
return resultAggregatorQueue.sync { return results }
}
}
let taskQueue = DispatchQueue(label: "Task Queue", attributes: .concurrent)
let taskRunner = ParallelTaskRunner<Int>()
for i in 0...100 {
taskRunner.execute { completionHandler in
taskQueue.async { // Simulated async computation
let randomTime = 3.0
print("Sleeping for \(randomTime)")
Thread.sleep(forTimeInterval: randomTime) // Simulates intesnive computation
let result = i // Simulate a result
completionHandler(result)
}
}
}
print(taskRunner.getResults()) // Oh look, all the results are here! :D

Waiting until the task finishes

How could I make my code wait until the task in DispatchQueue finishes? Does it need any CompletionHandler or something?
func myFunction() {
var a: Int?
DispatchQueue.main.async {
var b: Int = 3
a = b
}
// wait until the task finishes, then print
print(a) // - this will contain nil, of course, because it
// will execute before the code above
}
I'm using Xcode 8.2 and writing in Swift 3.
If you need to hide the asynchronous nature of myFunction from the caller, use DispatchGroups to achieve this. Otherwise, use a completion block. Find samples for both below.
DispatchGroup Sample
You can either get notified when the group's enter() and leave() calls are balanced:
func myFunction() {
var a = 0
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
a = 1
group.leave()
}
// does not wait. But the code in notify() is executed
// after enter() and leave() calls are balanced
group.notify(queue: .main) {
print(a)
}
}
or you can wait:
func myFunction() {
var a = 0
let group = DispatchGroup()
group.enter()
// avoid deadlocks by not using .main queue here
DispatchQueue.global(qos: .default).async {
a = 1
group.leave()
}
// wait ...
group.wait()
print(a) // you could also `return a` here
}
Note: group.wait() blocks the current queue (probably the main queue in your case), so you have to dispatch.async on another queue (like in the above sample code) to avoid a deadlock.
Completion Block Sample
func myFunction(completion: #escaping (Int)->()) {
var a = 0
DispatchQueue.main.async {
let b: Int = 1
a = b
completion(a) // call completion after you have the result
}
}
// on caller side:
myFunction { result in
print("result: \(result)")
}
In Swift 3, there is no need for completion handler when DispatchQueue finishes one task.
Furthermore you can achieve your goal in different ways
One way is this:
var a: Int?
let queue = DispatchQueue(label: "com.app.queue")
queue.sync {
for i in 0..<10 {
print("Ⓜ️" , i)
a = i
}
}
print("After Queue \(a)")
It will wait until the loop finishes but in this case your main thread will block.
You can also do the same thing like this:
let myGroup = DispatchGroup()
myGroup.enter()
//// Do your task
myGroup.leave() //// When your task completes
myGroup.notify(queue: DispatchQueue.main) {
////// do your remaining work
}
One last thing: If you want to use completionHandler when your task completes using DispatchQueue, you can use DispatchWorkItem.
Here is an example how to use DispatchWorkItem:
let workItem = DispatchWorkItem {
// Do something
}
let queue = DispatchQueue.global()
queue.async {
workItem.perform()
}
workItem.notify(queue: DispatchQueue.main) {
// Here you can notify you Main thread
}
Swift 5 version of the solution
func myCriticalFunction() {
var value1: String?
var value2: String?
let group = DispatchGroup()
group.enter()
//async operation 1
DispatchQueue.global(qos: .default).async {
// Network calls or some other async task
value1 = //out of async task
group.leave()
}
group.enter()
//async operation 2
DispatchQueue.global(qos: .default).async {
// Network calls or some other async task
value2 = //out of async task
group.leave()
}
group.wait()
print("Value1 \(value1) , Value2 \(value2)")
}
Use dispatch group
dispatchGroup.enter()
FirstOperation(completion: { _ in
dispatchGroup.leave()
})
dispatchGroup.enter()
SecondOperation(completion: { _ in
dispatchGroup.leave()
})
dispatchGroup.wait() // Waits here on this thread until the two operations complete executing.
In Swift 5.5+ you can take advantage of Swift Concurrency which allows to return a value from a closure dispatched to the main thread
func myFunction() async {
var a : Int?
a = await MainActor.run {
let b = 3
return b
}
print(a)
}
Task {
await myFunction()
}
Swift 4
You can use Async Function for these situations. When you use DispatchGroup(),Sometimes deadlock may be occures.
var a: Int?
#objc func myFunction(completion:#escaping (Bool) -> () ) {
DispatchQueue.main.async {
let b: Int = 3
a = b
completion(true)
}
}
override func viewDidLoad() {
super.viewDidLoad()
myFunction { (status) in
if status {
print(self.a!)
}
}
}
Somehow the dispatchGroup enter() and leave() commands above didn't work for my case.
Using sleep(5) in a while loop on the background thread worked for me though. Leaving here in case it helps someone else and it didn't interfere with my other threads.