Why does DispatchSemaphore.wait() block this completion handler? - swift

So I've been playing about with NetworkExtension to to make a toy VPN implementation and I ran into an issue with the completion handlers/asynchronously running code. I'll run you through my train of thought/expirments and would appreciate any pointers at areas where I am mistaken, and how to resolve this issue!
Here's the smallest reproducible bit of code (obviously you will need to import NetworkExtension):
let semaphore = DispatchSemaphore(value: 0)
NETunnelProviderManager.loadAllFromPreferences { managers, error in
print("2 during")
semaphore.signal()
}
print("1 before")
semaphore.wait()
print("3 after")
With my understanding of semaphores and asynchronous code I'd expect the printouts to occur in the order:
1 before
2 during
3 after
However the program hangs at "1 before". If I remove the semaphore.wait() line, the printout occurs as expected in the order: 1, 3, 2 (as the closure runs later).
So after a bit of digging around with the debugger, it looks like the semaphore trap loop is blocking up execution. This sparked me to read around a bit into queues, and I discovered that changing it to the following works:
// ... as before
DispatchQueue.global().async {
semaphore.wait()
print("3 after")
}
This makes some sense as the blocking .wait() call is now being called asynchronously in a separate thread. However, this solution is not desired for me as in my actual implementation I am actually capturing the results from the closure and returning them later, in something that looks like this:
let semaphore = DispatchSemaphore(value: 0)
var results: [NETunnelProviderManager]? = nil
NETunnelProviderManager.loadAllFromPreferences { managers, error in
print("2 during")
results = managers
semaphore.signal()
}
print("1 before")
// DispatchQueue.global().async {
semaphore.wait()
print("3 after")
// }
return results
Obviously I cannot return data from from the async closure, and moving the return out of it would make it defunct. Acdditionally, adding another semaphore to make things synchronous exhibits the same issue as before just moving the problem along in a chain.
As a result, I decided to try putting the .loadAllFromPreferences() call and completion handler in an async closure and leave everything else as in the original code snippet:
// ...
DispatchQueue.global().async {
NETunnelProviderManager.loadAllFromPreferences { loadedManagers, error in
print("2 during")
semaphore.signal()
}
}
// ...
However this does not work and the .wait() call is never passed - as before. I assume that somehow the sempahore is still blocking the thread and not allowing anything to execute, meaning whatever in the system is managing the queue is not running the async block? However I'm clutching at straws here, and fear my original conclusion may not have been right.
This is where I'm starting to get out of my depth, so I'd like to know what is actually going on, and what resolution would you recommend to get the results from .loadAllFromPreferences() in a synchronous manner?
Thanks!

From the documentation for NETunnelProviderManager loadAllFromPreferences:
This block will be executed on the caller’s main thread after the load operation is complete
So we know that the completion handler is on the main thread.
We also know that the call to DispatchSemaphore wait will block whatever thread it is running on. Given this evidence, you must be calling all of this code from the main thread. Since your call to wait is blocking the main thread, the completion handler can never be called because the main thread is blocked.
This is made clear by your attempt to call wait on some global background queue. That allows the completion block to be called because your use of wait is no longer blocking the main thread.
And your attempt to call loadAllFromPreferences from a global background queue doesn't change anything because its completion block is still called on the main thread and your call to wait is still on the main thread.
It's a bad idea to block the main thread at all. The proper solution is to refactor whatever method this code is in to use its own completion handler instead of trying to use a normal return value.

Related

Swift Concurrency : Why Task is not executed on other background thread

I am trying to learn the swift concurrency but it brings in a lot of confusion. I understood that a Task {} is an asynchronous unit and will allow us to bridge the async function call from a synchronous context. And it is similar to DispatchQueue.Global() which in turn will execute the block on some arbitrary thread.
override func viewDidLoad() {
super.viewDidLoad()
Task {
do {
let data = try await asychronousApiCall()
print(data)
} catch {
print("Request failed with error: \(error)")
}
}
for i in 1...30000 {
print("Thread \(Thread.current)")
}
}
my asychronousApiCall function is below
func asychronousApiCall() async throws -> Data {
print("starting with asychronousApiCall")
print("Thread \(Thread.current)")
let url = URL(string: "https://www.stackoverflow.com")!
// Use the async variant of URLSession to fetch data
// Code might suspend here
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
When I try this implementation. I always see that "starting with asychronousApiCall" is printed after the for loop is done and the thread is MainThread.
like this
Thread <_NSMainThread: 0x600000f10500>{number = 1, name = main}
You said:
I understood that a Task {} is an asynchronous unit and will allow us to bridge the async function call from a synchronous context.
Yes.
You continue:
And it is similar to DispatchQueue.global() which in turn will execute the block on some arbitrary thread.
No, if you call it from the main actor, it is more akin to DispatchQueue.main.async { … }. As the documentation says, it “[r]uns the given nonthrowing operation asynchronously as part of a new top-level task on behalf of the current actor” [emphasis added]. I.e., if you are currently on the main actor, the task will be run on behalf of the main actor, too.
While it is a probably mistake to dwell on direct GCD-to-concurrency mappings, Task.detached { … } is more comparable to DispatchQueue.global().async { … }.
You commented:
Please scroll to figure 8 in last of the article. It has a normal task and Thread is print is some other thread.
figure 8
In that screen snapshot, they are showing that prior to the suspension point (i.e., before the await) it was on the main thread (which makes sense, because it is running it on behalf of the same actor). But they are also highlighting that after the suspension point, it was on another thread (which might seem counterintuitive, but it is what can happen after a suspension point). This is very common behavior in Swift concurrency, though it can vary.
FWIW, in your example above, you only examine the thread before the suspension point and not after. The take-home message of figure 8 is that the thread used after the suspension point may not be the same one used before the suspension point.
If you are interested in learning more about some of these implementation details, I might suggest watching WWDC 2021 video Swift concurrency: Behind the scenes.
While it is interesting to look at Thread.current, it should be noted that Apple is trying to wean us off of this practice. E.g., in Swift 5.7, if we look at Thread.current from an asynchronous context, we get a warning:
Class property 'current' is unavailable from asynchronous contexts; Thread.current cannot be used from async contexts.; this is an error in Swift 6
The whole idea of Swift concurrency is that we stop thinking in terms of threads and we instead let Swift concurrency choose the appropriate thread on our behalf (which cleverly avoids costly context switches where it can; sometimes resulting code that runs on threads other than what we might otherwise expect).

In Swift, if Thread.current.isMainThread == false, then is it safe to DispatchQueue.main.sync recursively once?

In Swift, if Thread.current.isMainThread == false, then is it safe to DispatchQueue.main.sync recursively once?
The reason I ask is that, in my company's app, we had a crash that turned out to be due to some UI method being called from off the main thread, like:
public extension UIViewController {
func presentModally(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
// some code that sets presentation style then:
present(viewControllerToPresent, animated: flag, completion: completion)
}
}
Since this was getting called from many places, some of which would sometimes call it from a background thread, we were getting crashes here and there.
Fixing all the call sites was not feasible due to the app being over a million lines of code, so my solution to this was simply to check if we're on the main thread, and if not, then redirect the call to the main thread, like so:
public extension UIViewController {
func presentModally(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
guard Thread.current.isMainThread else {
DispatchQueue.main.sync {
presentModally(viewControllerToPresent, animated: flag, completion: completion)
}
return
}
// some code that sets presentation style then:
present(viewControllerToPresent, animated: flag, completion: completion)
}
}
The benefits of this approach seem to be:
Preservation of execution order. If the caller is off the main thread, we'll redirect onto the main thread, then execute the same function before we return -- thus preserving the normal execution order that the would have happened had the original function been called from the main thread, since functions called on the main thread (or any other thread) execute synchronously by default.
Ability to implicitly reference self without compiler warnings. In Xcode 11.4, performing this call synchronously also satisfies the compiler that it's OK to implicitly retain self, since the dispatch context will be entered then exited before the original function call returns -- so we don't get any new compiler warnings from this approach. That's nice and clean.
More focused diffs via less indentation. It avoids wrapping the entire function body in a closure (like you'd normally see done if Dispatch.main.async { ... } was used, where the whole body must now be indented a level deeper, incurring whitespace diffs in your PR that can lead to annoying merge conflicts and make it harder for reviewers to distinguish the salient elements in GitHub's PR diff views).
Meanwhile the alternative, DispatchQueue.main.async, would seem to have the following drawbacks:
Potentially changes expected execution order. The function would return before executing the dispatched closure, which in turn means that self could have deallocated before it runs. That means we'd have to explicitly retain self (or weakify it) to avoid a compiler warning. It also means that, in this example, present(...) would not get called before the function would return to the caller. This could cause the modal to pop-up after some other code subsequent to the call site, leading to unintended behavior.
Requirement of either weakifying or explicitly retaining self. This is not really a drawback but it's not as clean, stylistically, as being able to implicitly retain self.
So the question is: are these assumptions all correct, or am I missing something here?
My colleagues who reviewed the PR seemed to feel that using "DispatchQueue.main.sync" is somehow inherently bad and risky, and could lead to a deadlock. While I realize that using this from the main thread would indeed deadlock, here we explicitly avoid that here using a guard statement to make sure we're NOT on the main thread first.
Despite being presented with all the above rationale, and despite being unable to explain to me how a deadlock could actually happen given that the dispatch only happens if the function gets called off the main thread to begin with, my colleagues still have deep reservations about this pattern, feeling that it could lead to a deadlock or block the UI in unexpected ways.
Are those fears founded? Or is this pattern perfectly safe?
This pattern is definitely not “perfectly” safe. One can easily contrive a deadlock:
let group = DispatchGroup()
DispatchQueue.global().async(group: group) {
self.presentModally(controller, animated: true)
}
group.wait()
Checking that isMainThread is false is insufficient, strictly speaking, to know whether it’s safe to dispatch synchronously to the main thread.
But that’s not the real issue. You obviously have some routine somewhere that thinks it’s running on the main thread, when it’s not. Personally, I’d be worried about what else that code did while operating under this misconception (e.g. unsynchronized model updates, etc.).
Your workaround, rather than fixing the root cause of the problem, is just hiding it. As a general rule, I would not suggest coding around bugs introduced elsewhere in the codebase. You really should just figure out where you’re calling this routine from a background thread and resolve that.
In terms of how to find the problem, hopefully the stack trace associated with the crash will tell you. I’d also suggest adding a breakpoint for the main thread checker by clicking on that little arrow next to it in the scheme settings:
Then exercise the app and if it encounters this issue, it will pause execution at the offending line, which can be very useful in tracking down these issues. That often is much easier than reverse-engineering from the stack trace.
I agree with the comments that you have some structural difficulties with your code.
But there are still times in which I need code to run on the main thread and I don't know if I'm already on the main thread or not. This has occurred often enough that I wrote a ExecuteOnMain() function just for this:
dispatch_queue_t MainSequentialQueue( )
{
static dispatch_queue_t mainQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
#if HAS_MAIN_RUNLOOP
// If this process has a main thread run loop, queue sequential tasks to run on the main thread
mainQueue = dispatch_get_main_queue();
#else
// If the process doesn't execute in a run loop, create a sequential utility thread to perform these tasks
mainQueue = dispatch_queue_create("main-sequential",DISPATCH_QUEUE_SERIAL);
#endif
});
return mainQueue;
}
BOOL IsMainQueue( )
{
#if HAS_MAIN_RUNLOOP
// Return YES if this code is already executing on the main thread
return [NSThread isMainThread];
#else
// Return YES if this code is already executing on the sequential queue, NO otherwise
return ( MainSequentialQueue() == dispatch_get_current_queue() );
#endif
}
void DispatchOnMain( dispatch_block_t block )
{
// Shorthand for asynchronously dispatching a block to execute on the main thread
dispatch_async(MainSequentialQueue(),block);
}
void ExecuteOnMain( dispatch_block_t block )
{
// Shorthand for synchronously executing a block on the main thread before returning.
// Unlike dispatch_sync(), this won't deadlock if executed on the main thread.
if (IsMainQueue())
// If this is the main thread, execute the block immediately
block();
else
// If this is not the main thread, queue the block to execute on the main queue and wait for it to finish
dispatch_sync(MainSequentialQueue(),block);
}
A bit late, but I had a need for this type of solution too. I had some common code that could be invoked from both the main thread and background threads, and updated the UI. My solution to the generic use case was:
public extension UIViewController {
func runOnUiThread(closure: #escaping () -> ()) {
if Thread.isMainThread {
closure()
} else {
DispatchQueue.main.sync(execute: closure)
}
}
}
Then to call it from a UIViewController:
runOnUiThread {
code here
}
As others have pointed out, this is not completely safe. You might have some code on background thread that is invoked from the main thread, synchronously. If that background code then calls the code above, it will attempt to run on the main thread and will create a deadlock. The main thread is waiting for the background code to execute, and the background code will wait for the main thread to be free.

What exactly is GCD overcommit and is it dangerous

I've been working on some code recently where I want to ensure that certain tasks run sequentially always execute on the same thread.
As an experiment to get a feel for how this would work I created my own thread using this example Thread Example
My code works fine in that I call a method on the global queue then I enqueue an operation on my Custom Thread.
func currentQueueName() -> String? {
let name = __dispatch_queue_get_label(nil)
return String(cString: name, encoding: .utf8)
}
DispatchQueue.global().async {
print(self.currentQueueName())
myThread.enqueue {
print(self.currentQueueName())
//prints "com.apple.root.default-qos.overcommit"
for i in 1...100 {
print(i)
}
}
}
Whenever I print out the current thread during my Custom Thread Execution the thread name says.
("com.apple.root.default-qos.overcommit")
I don't get any errors or crashes.
1) What exactly does this over commit mean?
2) How have I caused it by using my own thread?
3) Is it dangerous to be seeing this message in production code?
4) If it is dangerous how can I use my Custom Thread safely
Update
After reading a post on Swift forum I'm beginning to think that over commit queue refers to any thread that isn't from Dispatch Queue Global.
I'm still not 100% certain though.

Using completion handler together with DispatchQueue

I have learned that concurrent DispatchQueue allows the code inside it to return immediately, hence not blocking the calling thread. This is usually used with background tasks loading large data.
I have also learned that completion handler (for example, in URLSession ) allows the code inside handler to be executed after some task finishes.
My question is: does it mean that concurrent dispatch queue and completion handler have overlapping purpose? If I already used completion handler, there is no need to wrap it with a concurrent dispatch queue?
For example, below is a time-consuming data loading task using URLSession, is it a good idea to wrap it with a concurrent dispatch queue?
URLSession(configuration: URLSessionConfiguration.default).dataTask(with: propertiesRequest) { data, response, error in
// print("within dataTask: data: \(data), response: \(response), error: \(error)")
if let error = error {
print(error)
} else if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode == 200 {
print("success: property task request")
do {
handler(responseDict, nil) // This is a function supplied by caller
} catch let error as NSError {
handler(nil, error)
}
}
}
}
You don't have to use Grand Central Dispatch (GCD) dispatch queues in conjunction with time-consuming URLSession request.
You'd might use GCD inside the dataTask completion handler closure if:
If you are doing something inside the closure is, itself, very time consuming (e.g. processing very complicated request) and you don't want to tie up the serial operation queue that URLSession uses for processing its completion handlers (and delegate method). This does not appear to be the issue here (e.g. parsing JSON responses is generally quick enough we don't have to worry about this), but just FYI. Or,
If, when you're done parsing the response of dataTask, if you want to update some model object or update the UI. You want to do those on the main queue.
For example, if your request was returning a bunch of objects to show in some tableview, you'd dispatch the update of the model and the UI to the main queue:
DispatchQueue.main.async {
self.objects = ...
self.tableView.reloadData()
}
But you don't have to worry about the time-consuming URLSession request, itself. That already happens asynchronously, so you don't have to dispatch that to a background queue.
The DispatchQueue and the completion handler do not overlap but rather can be used as a seamless solution for handling queues. The data loading task in URLSession is already asynchronous and thus have no need to be wrapped in a DispatchQueue.
DispatchQueue - assigning tasks to a specific thread for better performance (global queue) / user experience (main queue).
Completion Handlers - guarantees certain code will run when the task has been completed. However, the execution is on the current thread unless explicitly stated otherwise.
For example, calling on method A which employs DispatchQueue.global.async will queue the task on the global queue, freeing the main queue for more important (UI) tasks. After some time, the task would have been completed and usually, we would want to do something with the data. If it is UI related, we definitely want to call on DispatchQueue.main.async to update the screen with info or if it is trivial, no calls need to be made and vanilla code would suffice.

Swift: synchronously perform code in background; queue.sync does not work as I would expect

I would like to perform some code synchronously in the background, I really thought this is the way to go:
let queue = DispatchQueue.global(qos: .default)
queue.async {
print("\(Thread.isMainThread)")
}
but this prints true unless I use queue.async. async isn't possible as then the code will be executed in parallel. How can I achieve running multiple blocks synchronously in the background?
What I would like to achieve: synchronize events in my app with the devices calendar, which happens in the background. The method which does this can be called from different places multiple times so I would like to keep this in order and in the background.
Async execution isn't your problem, since you only care about the order of execution of your code blocks relative to each other but not relative to the main thread. You shouldn't block the main thread, which is in fact DispatchQueue.main and not DispatchQueue.global.
What you should do is execute your code on a serial queue asynchronously, so you don't block the main thread, but you still ensure that your code blocks execute sequentially.
You can achieve this using the following piece of code:
let serialQueue = DispatchQueue(label: "serialQueue")
serialQueue.async{ //call this whenever you need to add a new work item to your queue
//call function here
}
DispatchQueue is not equal to a Thread. Think of it as of a kind of abstraction over the thread pool.
That being said, main queue is indeed "fixed" on the main thread. And that is why, when you synchronously dispatch a work item from the main queue, you are still on the main thread.
To actually execute sync code in the background, you have to already be in the background:
DispatchQueue.global().async {
DispatchQueue.global().sync {
print("\(Thread.isMainThread)")
}
}
This will print false.
Also, as user #rmaddy correctly pointed out in comments, doing any expensive tasks synchronously from the main queue might result in your program becoming unresponsive, since the main thread is responsible for the UI updates.