Deadlock with GCD and webView

grand-central-dispatch, ios, objective-c, webkit

Solution

This seems to simply be a bug in UIWebView. According to this question, it was introduced in iOS 5 and did not deadlock on iOS 4.3 and below.

Interestingly, presenting a UIAlertView right before the call to `stringByEvaluatingJavaScriptFromString:` strangely prevents the deadlock:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_async(dispatch_get_main_queue(), ^{
        UIAlertView *message = [[UIAlertView alloc] initWithTitle:@"Test"
                                                          message:@"Test"
                                                         delegate:nil
                                                cancelButtonTitle:@"OK"
                                                otherButtonTitles:nil];
        [message show];

        [theWebView stringByEvaluatingJavaScriptFromString:@"alert('hi');"];
    });
});

However, my theory is this: When I pause execution after the deadlock occurs, I see that the WebThread is halted at `__psynch_mutexwait`. Since the JavaScript engine is executed on different threads, it must tell the main thread to display the alert view. However, `stringByEvaluatingJavaScriptFromString:` is a blocking call that returns a value. The value can only be returned once the alert is dismissed by clicking OK. This is where the deadlock seemingly occurs: From a different thread we tell the main thread to tell the web view to run JavaScript (which happens on yet another thread), which in turn tells the main thread to display an alert view, which can only deliver its return value back to JavaScript once OK is clicked. And only when the call to `stringByEvaluatingJavaScriptFromString:` returns is the block that we passed to GCD complete.

It must be a bug, though. It is strange that the deadlock doesn't occur when I display a UIAlertView first. Maybe iOS puts the second alert view on some kind of queue in that case, which prevents the deadlock. Odd!

Problem

I've found an issue that seems to be causing a deadlock in WebKit. If I run this code from my main thread, I rightly see an alert. I can tap on the "OK" button on the alert and it dismisses and all is working well: ``` [theWebView stringByEvaluatingJavaScriptFromString:@"alert('hi');"]; ``` If I make a slight modification, then the alert message still appears, but the OK button cannot be tapped on - you cannot dismiss the alert and if you break into the app it is hung in the `stringByEvaluatingJavaScriptFromString` call: ``` dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_main_queue(), ^{ [theWebView stringByEvaluatingJavaScriptFromString:@"alert('hi');"]; }); }); ``` The only different in these two is that in the second one, it is running the JS in the main thread in the context of a dispatch queue. On the other hand, if I do the following, then the hang does not occur: ``` - (void) showHi:(id) it { [(UIWebView*)it stringByEvaluatingJavaScriptFromString:@"alert('hi');"]; } .... dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self performSelectorOnMainThread:@selector(showHi:) withObject:theWebView waitUntilDone:NO]; }); ``` Can someone shine some light on what is going wrong to cause the hang? EDIT: Related questions: Perform UI Changes on main thread using dispatch_async or performSelectorOnMainThread? Whats the difference between performSelectorOnMainThread and dispatch_async on main queue? Grand Central Dispatch (GCD) vs. performSelector - need a better explanation Very similar question: UIWebView stringByEvaluatingJavaScriptFromString hangs on iOS5.0/5.1 when called using GCD

Original source

Related problems