Swift async let cancellation doesn't work - swift

I am a bit confused about tasks being cancelled.
Overview:
checkCancellation function has 2 child tasks, one task runs computeA and the other computeB. They run concurrently, computeB throws an error.
Doubt:
I expected child task computeA to be cancelled because computeB threw an error, but computeA was never cancelled.
Is my understanding wrong or am I missing something?
Or is this a bug?
Note:
I am using a SwiftUI project (as Swift Playgrounds don't support async let)
macOS Big Sur 11.5.2 (20G95)
Xcode Version 13.0 beta 5 (13A5212g)
Output:
A - started
B - going to throw
A - going to return, Task.isCancelled = false
error: infinity
Concurrent Function Definitions:
import Foundation
import UIKit
enum ComputationError: Error {
case infinity
}
fileprivate func computeA() async throws -> Int {
print("A - started")
await Task.sleep(2 * 100_000_000)
print("A - going to return, Task.isCancelled = \(Task.isCancelled)") //I expected Task.isCancelled to be true
return 25
}
fileprivate func computeB() async throws -> Int {
print("B - going to throw")
throw ComputationError.infinity
}
func checkCancellation() async throws {
async let a = computeA()
async let b = computeB()
let c = try await a + b
print("c = \(c)")
}
Invoking Concurrent function
struct ContentView: View {
var body: some View {
Button("check cancellation") {
Task {
do {
try await checkCancellation()
print("normal exit")
} catch {
print("error: \(error)")
}
}
}
}
}
Observation:
When I change the code to let c = try await b + a
Output:
A - started
B - going to throw
A - going to return, Task.isCancelled = true
error: infinity
Doubt:
I am still not sure I understand the reason for this behaviour in the original code

I expected child task computeA to be cancelled because computeB threw an error, but computeA was never cancelled. Is my understanding wrong or am I missing something?
Yes and yes. Your understanding is wrong and you are missing something.
The remark in the video has to do with subtasks created in a task group. What actually happens is that when the error percolates from the child task up to the task group, the task group calls cancel on all the other child tasks.
But you are testing using async let. There is no task group. So there is no one "there" to cancel the other subtask. In fact, if you watch the video carefully, you'll find that it tells you correctly what to expect:
If the parent task is cancelled, the child tasks are cancelled automatically.
If an async let task throws before a second async let task is even initialized, then the second task is "born cancelled". It is still awaited (the video is very clear about this, and it is correct); if it doesn't respond to cancellation by aborting, it will continue to completion.
The point here is that the parent task must end in good order. It cannot end until its children have completed one way or another. (Again, the video is very clear about this.) If that means automatically waiting for the tasks to complete in spite of the throw, then that's what will happen.
Another interesting thing to note is that with async let, where you say try await later, if the subtask throws, you won't even hear about it (and nothing at all will happen in this regard) until you reach the point where the try await takes place. In other words, the throw can only percolate up to the point where you actually say try; nothing else would make sense.

Related

What is the Swift concurrency equivalent to a promise–resolver pair?

With the PromiseKit library, it’s possible to create a promise and a resolver function together and store them on an instance of a class:
class ExampleClass {
// Promise and resolver for the top news headline, as obtained from
// some web service.
private let (headlinePromise, headlineSeal) = Promise<String>.pending()
}
Like any promise, we can chain off of headlinePromise to do some work once the value is available:
headlinePromise.get { headline in
updateUI(headline: headline)
}
// Some other stuff here
Since the promise has not been resolved yet, the contents of the get closure will be enqueued somewhere and control will immediately move to the “some other stuff here” section; updateUI will not be called unless and until the promise is resolved.
To resolve the promise, an instance method can call headlineSeal:
makeNetworkRequest("https://news.example/headline").get { headline in
headlineSeal.fulfill(headline)
}
The promise is now resolved, and any promise chains that had been waiting for headlinePromise will continue. For the rest of the life of this ExampleClass instance, any promise chain starting like
headlinePromise.get { headline in
// ...
}
will immediately begin executing. (“Immediately” might mean “right now, synchronously,” or it might mean “on the next run of the event loop”; the distinction isn’t important for me here.) Since promises can only be resolved once, any future calls to headlineSeal.fulfill(_:) or headlineSeal.reject(_:) will be no-ops.
Question
How can this pattern be translated idiomatically into Swift concurrency (“async/await”)? It’s not important that there be an object called a “promise” and a function called a “resolver”; what I’m looking for is a setup that has the following properties:
It’s possible for some code to declare a dependency on some bit of asynchronously-available state, and yield until that state is available.
It’s possible for that state to be “fulfilled” from potentially any instance method.
Once the state is available, any future chains of code that depend on that state are able to run right away.
Once the state is available, its value is immutable; the state cannot become unavailable again, nor can its value be changed.
I think that some of these can be accomplished by storing an instance variable
private let headlineTask: Task<String, Error>
and then waiting for the value with
let headline = try await headlineTask.value
but I’m not sure how that Task should be initialized or how it should be “fulfilled.”
Here is a way to reproduce a Promise which can be awaited by multiple consumers and fulfilled by any synchronous code:
public final class Promise<Success: Sendable>: Sendable {
typealias Waiter = CheckedContinuation<Success, Never>
struct State {
var waiters = [Waiter]()
var result: Success? = nil
}
private let state = ManagedCriticalState(State())
public init(_ elementType: Success.Type = Success.self) { }
#discardableResult
public func fulfill(with value: Success) -> Bool {
return state.withCriticalRegion { state in
if state.result == nil {
state.result = value
for waiters in state.waiters {
waiters.resume(returning: value)
}
state.waiters.removeAll()
return false
}
return true
}
}
public var value: Success {
get async {
await withCheckedContinuation { continuation in
state.withCriticalRegion { state in
if let result = state.result {
continuation.resume(returning: result)
} else {
state.waiters.append(continuation)
}
}
}
}
}
}
extension Promise where Success == Void {
func fulfill() -> Bool {
return fulfill(with: ())
}
}
The ManagedCriticalState type can be found in this file from the SwiftAsyncAlgorithms package.
I think I got the implementation safe and correct but if someone finds an error I'll update the answer. For reference I got inspired by AsyncChannel and this blog post.
You can use it like this:
#main
enum App {
static func main() async throws {
let promise = Promise(String.self)
// Delayed fulfilling.
let fulfiller = Task.detached {
print("Starting to wait...")
try await Task.sleep(nanoseconds: 2_000_000_000)
print("Promise fulfilled")
promise.fulfill(with: "Done!")
}
let consumer = Task.detached {
await (print("Promise resolved to '\(promise.value)'"))
}
// Launch concurrent consumer and producer
// and wait for them to complete.
try await fulfiller.value
await consumer.value
// A promise can be fulfilled only once and
// subsequent calls to `.value` immediatly return
// with the previously resolved value.
promise.fulfill(with: "Ooops")
await (print("Promise still resolved to '\(promise.value)'"))
}
}
Short explanation
In Swift Concurrency, the high-level Task type resembles a Future/Promise (it can be awaited and suspends execution until resolved) but the actual resolution cannot be controlled from the outside: one must compose built-in lower-level asynchronous functions such as URLSession.data() or Task.sleep().
However, Swift Concurrency provides a (Checked|Unsafe)Continuation type which basically act as a Promise resolver. It is a low-lever building block which purpose is to migrate regular asynchronous code (callback-based for instance) to the Swift Concurrency world.
In the above code, continuations are created by the consumers (via the .value property) and stored in the Promise. Later, when the result is available the stored continuations are fulfilled (with .resume()), which resumes the execution of the consumers. The result is also cached so that if it is already available when .value is called it is directly returned to the called.
When a Promise is fulfilled multiple times, the current behavior is to ignore subsequent calls and to return aa boolean value indicating if the Promise was already fulfilled. Other API's could be used (a trap, throwing an error, etc.).
The internal mutable state of the Promise must be protected from concurrent accesses since multiple concurrency domains could try to read and write from it at the same time. This is achieve with regular locking (I believe this could have been achieved with an actor, though).

Swift - ThreadSanitizer complaining of data race

I am running ThreadSanitizer on my project and I am getting some very inconsistent results. My setup is as follows:
A wrapper around a URLSession which reports back tasks and their status. I get the tasks via:
actor MyMonitor {
func getTasks() async -> [String: URLSessionTask] {
await mySession.allTasks.reduce(into: [String: URLSessionTask]()) { newMap, element in
if let taskDescription = element.taskDescription {
newMap[taskDescription] = element
}
}
}
}
For my unit tests, I have a URLProtocol to mock an actual working request. However, it seems when I call the getTasks function, I get a data race. (albeit inconsistent)
The TSan tool states the race is in the above snippet:
Threading Issues: Data race in (2) await resume partial function for
MyProject.MyMonitor.getTasks() async -> Swift.Dictionary<Swift.String,
__C.NSURLSessionTask> at 0x7b6000021400
Is this a false positive, because I do not have control ove URLSession's threading? Any help would be appreciated.

Swift await/async - how to wait synchronously for an async task to complete?

I'm bridging the sync/async worlds in Swift and doing incremental adoption of async/await. I'm trying to invoke an async function that returns a value from a non async function. I understand that explicit use of Task is the way to do that, as described, for instance, here.
The example doesn't really fit as that task doesn't return a value.
After much searching, I haven't been able to find any description of what I'd think was a pretty common ask: synchronous invocation of an asynchronous task (and yes, I understand that that can freeze up the main thread).
What I theoretically would like to write in my synchronous function is this:
let x = Task {
return await someAsyncFunction()
}.result
However, when I try to do that, I get this compiler error due to trying to access result:
'async' property access in a function that does not support concurrency
One alternative I found was something like:
Task.init {
self.myResult = await someAsyncFunction()
}
where myResult has to be attributed as a #State member variable.
However, that doesn't work the way I want it to, because there's no guarantee of completing that task prior to Task.init() completing and moving onto the next statement. So how can I wait synchronously for that Task to be complete?
You should not wait synchronously for an async task.
One may come up with a solution similar to this:
func updateDatabase(_ asyncUpdateDatabase: #Sendable #escaping () async -> Void) {
let semaphore = DispatchSemaphore(value: 0)
Task {
await asyncUpdateDatabase()
semaphore.signal()
}
semaphore.wait()
}
Although it works in some simple conditions, according to WWDC 2021 Swift Concurrency: Behind the scenes, this is unsafe. The reason is the system expects you to conform to a runtime contract. The contract requires that
Threads are always able to make forward progress.
That means threads are never blocking. When an asynchronous function reaches a suspension point (e.g. an await expression), the function can be suspended, but the thread does not block, it can do other works. Based on this contract, the new cooperative thread pool is able to only spawn as many threads as there are CPU cores, avoiding excessive thread context switches. This contract is also the key reason why actors won't cause deadlocks.
The above semaphore pattern violates this contract. The semaphore.wait() function blocks the thread. This can cause problems. For example
func testGroup() {
Task {
await withTaskGroup(of: Void.self) { group in
for _ in 0 ..< 100 {
group.addTask {
syncFunc()
}
}
}
NSLog("Complete")
}
}
func syncFunc() {
let semaphore = DispatchSemaphore(value: 0)
Task {
try? await Task.sleep(nanoseconds: 1_000_000_000)
semaphore.signal()
}
semaphore.wait()
}
Here we add 100 concurrent child tasks in the testGroup function, unfortunately the task group will never complete. In my Mac, the system spawns 4 cooperative threads, adding only 4 child tasks is enough to block all 4 threads indefinitely. Because after all 4 threads are blocked by the wait function, there is no more thread available to execute the inner task that signals the semaphore.
Another example of unsafe use is actor deadlock:
func testActor() {
Task {
let d = Database()
await d.updateSettings()
NSLog("Complete")
}
}
func updateDatabase(_ asyncUpdateDatabase: #Sendable #escaping () async -> Void) {
let semaphore = DispatchSemaphore(value: 0)
Task {
await asyncUpdateDatabase()
semaphore.signal()
}
semaphore.wait()
}
actor Database {
func updateSettings() {
updateDatabase {
await self.updateUser()
}
}
func updateUser() {
}
}
Here calling the updateSettings function will deadlock. Because it waits synchronously for the updateUser function, while the updateUser function is isolated to the same actor, so it waits for updateSettings to complete first.
The above two examples use DispatchSemaphore. Using NSCondition in a similar way is unsafe for the same reason. Basically waiting synchronously means blocking the current thread. Avoid this pattern unless you only want a temporary solution and fully understand the risks.
Other than using semaphore, you can wrap your asynchronous task inside an operation like here. You can signal the operation finish once the underlying async task finishes and wait for operation completion using waitUntilFinished():
let op = TaskOperation {
try await Task.sleep(nanoseconds: 1_000_000_000)
}
op.waitUntilFinished()
Note that using semaphore.wait() or op.waitUntilFinished() blocks the current thread and blocking the thread can cause undefined runtime behaviors in modern concurrency as modern concurrency assumes all threads are always making forward progress. If you are planning to use this method only in contexts where you are not using modern concurrency, with Swift 5.7 you can provide attribute mark method unavailable in asynchronous context:
#available(*, noasync, message: "this method blocks thread use the async version instead")
func yourBlockingFunc() {
// do work that can block thread
}
By using this attribute you can only invoke this method from a non-async context. But some caution is needed as you can invoke non-async methods that call this method from an async context if that method doesn't specify noasync availability.
I wrote simple functions that can run asynchronous code as synchronous similar as Kotlin does, you can see code here. It's only for test purposes, though. DO NOT USE IT IN PRODUCTION as async code must be run only asynchronous
Example:
let result = runBlocking {
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "Some result"
}
print(result) // prints "Some result"
I've been wondering about this too. How can you start a Task (or several) and wait for them to be done in your main thread, for example? This may be C++ like thinking but there must be a way to do it in Swift as well. For better or worse, I came up with using a global variable to check if the work is done:
import Foundation
var isDone = false
func printIt() async {
try! await Task.sleep(nanoseconds: 200000000)
print("hello world")
isDone = true
}
Task {
await printIt()
}
while !isDone {
Thread.sleep(forTimeInterval: 0.1)
}

XCTest - Unable to understand / implement expectations in unit tests (to test aysnc code)

(NOTE - I'm developing for macOS, so please ... iOS-specific advice won't help me)
What I'm trying to do:
I have an app component that performs a short task on a background thread, and then, if certain conditions are met, asynchronously sends out a notification on the main thread.
NOTE - I am not using NSNotification in my app code. I am using my own custom notification mechanism. So, any solution related to NSNotification is not applicable to me.
I'm writing a unit test for the above mentioned app component, and simply want to check if that notification was indeed sent or not. My test has to be able to wait a second or so to give the notification time to reach its subscriber/observer, before performing an assertion.
I want to be able to test both possible cases in my tests: Both are normal scenarios.
Notification was sent.
Notification was not sent.
After hours of reading several docs and code samples, I don't understand how to achieve this with expectations.
I just want to wait one second in my test. Is it really this complicated ?
sleep() doesn't work
DispatchQueue.main.asyncAfter(time) doesn't work
Timer doesn't work
Here's the app component that needs to be tested, and its unit test:
In the below code, where do I put expectation.fulfill() ???
class ComponentBeingTested {
func methodBeingTested() {
doSomeWork()
if certainConditionsAreMet {
DispatchQueue.main.async {sendOutNotification()}
}
}
}
...
class UnitTestForComponentBeingTested: XCTestCase {
let objectBeingTested = ComponentBeingTested()
func testMethodBeingTested() {
let expectation = self.expectation(description: "Notification was sent")
// Call the code being tested
objectBeingTested.methodBeingTested()
// How do I do this with expectations ??? Where does expectation.fulfill() go ?
waitForOneSecond()
XCTAssertTrue(notificationSent) // Assume the value of notificationSent is available
}
}
Here is an approach
func testMethodBeingTested() {
// create expectation
let expectation = self.expectation(description: "Notification was sent")
// set expectation condition
var notificationSent = false
let observer = NotificationCenter.default
.addObserver(forName: _Your_Notification_Name, object: nil, queue: nil) { _ in
notificationSent = true
expectation.fulfill()
}
// Call the code being tested
objectBeingTested.methodBeingTested()
// wait for expectation
self.wait(for: [expectation], timeout: 5)
XCTAssertTrue(notificationSent)
}
Check out XCTNSNotificationExpectation, which becomes fulfilled when a matching notification is posted. Different initializers are available, depending on how restrictive you want to be on the fulfilment of the expectation.
To check that the notification is not sent, set isInverted to true on the expectation object.
Then just add a call to waitForExpectations(timeout:handler:) at the end of your test.
Ok, after a lot of trial and error, this works great for me:
Description: I basically created a helper function in my test case class that contains all the boilerplate expectation/wait code. It does the following:
1 - Creates an expectation (i.e. XCTestExpectation) as a formality.
2 - Calls my (arbitrary) test case assertion code (passed in as a closure) on some global queue thread after the intended delay period. Once this assertion code has completed, the expectation is fulfilled (again, a formality).
3 - Waits on the expectation by calling XCTestCase.wait(timeout). This ensures that the main thread / run loop is kept alive while my assertion code completes on this other thread.
Then, in my test case, I simply invoke that helper function, providing it with a wait period and some code to execute (i.e. my assertions).
This way, I have a simple and expressive reusable function that hides all the excessive ugliness of expectations which I never thought necessary in the first place.
I can put this helper in a base class like MyAppTestCase: XCTestCase, so that it is available to all my test case classes.
NOTE - This solution can be enhanced and made even more generic/reusable, but as of now, this is quite sufficient for the purposes of the originally posted problem.
Solution:
class ComponentBeingTested {
func methodBeingTested() {
doSomeWork()
if certainConditionsAreMet {
DispatchQueue.main.async {sendOutNotification()}
}
}
}
...
class UnitTestForComponentBeingTested: XCTestCase {
let objectBeingTested = ComponentBeingTested()
// Helper function that uses expectation/wait to execute arbitrary
// test code (passed in as a closure) after some delay period.
func executeAfter(_ timeSeconds: Double, _ work: (#escaping () -> Void)) {
let theExpectation = expectation(description: "some expectation")
// Execute work() after timeSeconds seconds
DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + timeSeconds) {
// The call to work() will execute my test assertions
work()
// As a formality, fulfill the expectation
theExpectation.fulfill()
}
// Wait for (timeSeconds + 1) seconds to give the work() call
// some time to perform the assertions
wait(for: [theExpectation], timeout: timeSeconds + 1)
}
func testMethodBeingTested() {
// Call the code being tested
objectBeingTested.methodBeingTested()
// Call the helper function above, to do the waiting before executing
// the assertions
executeAfter(0.5) {
// Assume the value of notificationSent is computed elsewhere
// and is available to assert at this point
XCTAssertTrue(notificationSent)
}
}
}

All asynchronous calls succeed or none, how to handle

I'm trying to create an online mobile application and can't figure out the best way to handle functions with multiple asynchronous calls. Say I have a function for example that updates a user in some way, but involved multiple asynchronous calls in the single function call. So for example:
// Function caller
update(myUser) { (updatedUser, error) in
if let error = error {
// Present some error UI to the user
}
if let updatedUser = updatedUser {
// Do something with the user
}
}
// Function implementation
public func updateUser(user: User, completion: #escaping (User?, Error?) -> () {
// asynchronous call A
updateUserTable(user: User) { error in
if let error = error {
completion(nil, error)
} else {
// create some new user object
completion(user, nil)
}
}
// asynchronous call B
uploadMediaForUser(user: User) { error in
if let error = error {
completion(nil, error)
}
}
// asynchronous call C
removeOldReferenceForUser(user: User) { error in
if let error = error {
completion(nil, error)
}
}
// Possibly any additional amount of asynchronous calls...
}
In a case like this, where one function call like updating a user involved multiple asynchronous calls, is this an all or nothing situation? Say for example the updateUserTable() call completes, but the user disconnects from the internet as uploadMediaForUser() was running, and that throws an error. Since updateUserTable() completed fine, my function caller thinks this method succeeded when in fact not everything involved in updating the user completed. Now I'm stuck with a user that might have mismatched references or wrong information in my database because the user's connection dropped mid call.
How do I handle this all or nothing case? If EVERY asynchronous call completed without an error, I know updating the user was a success. If only a partial amount of asynchronous calls succeeded and some failed, this is BAD and I need to either undo the changes that succeeded or attempt the failed methods again.
What do I do in this scenario? And also, and how do I use my completion closure to help identify the actions needed depending on the success or failure of the method. Did all them succeed? Good, tell the user. Do some succeed and some failed? Bad, revert changes or try again (i dont know)??
Edit:
Just calling my completion with the error doesn't seem like enough. Sure the user sees that something failed, but that doesn't help with the application knowing the steps needed to fix the damage where partial changes were made.
I would suggest adding helper enums for your tasks and returned result, things like (User?, Error?) have a small ambiguity of the case when for example both are nil? or you have the User and the Error set, is it a success or not?
Regarding the all succeeded or some failed - I would suggest using the DispatchGroup to notify when all tasks finished (and check how they finished in the end).
Also from you current code, when some request fails it's not clear for which user - as you pass nil, so it might bring difficulties in rolling it back after failure.
So in my point of view something like below (not tested the code, but think you should catch the idea from it) could give you control about the issues you described:
public enum UpdateTask {
case userTable
case mediaUpload
// ... any more tasks you need
}
public enum UpdateResult {
case success
case error([UpdateTask: Error])
}
// Function implementation
public func updateUser(user: User, completion: #escaping (User, UpdateResult) -> ()) {
let updateGroup = DispatchGroup()
var tasksErrors = [UpdateTask: Error]()
// asynchronous call A
updateGroup.enter()
updateUserTable(user: User) { error in
if let error = error {
tasksErrors[.userTable] = error
}
updateGroup.leave()
}
// ... any other similar tasks here
updateGroup.notify(queue: DispatchQueue.global()) { // Choose the Queue that suits your needs here by yourself
if tasksErrors.isEmpty {
completion(user, .success)
} else {
completion(user, .error(tasksErrors))
}
}
}
Keep a “previous” version of everything changed, then if something failed revert back to the “previous” versions. Only change UI once all returned without failure, and if one failed, revert to “previous” version.
EX:
var temporary = “userName”
getChanges(fromUser) {
If error {
userName = temporary //This reverts back due to failure.
}
}