Bug 172362

Summary: Make sure implementation of InitializeWebKit2() always run on the main thread
Product: WebKit Reporter: Chris Dumez <cdumez>
Component: WebKit2Assignee: Chris Dumez <cdumez>
Status: RESOLVED FIXED    
Severity: Normal CC: achristensen, beidson, commit-queue, darin, ggaren, jberlin, ossy, rob, sam, webkit-bug-importer, ysuzuki
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: Unspecified   
OS: Unspecified   
See Also: https://bugs.webkit.org/show_bug.cgi?id=212283
Attachments:
Description Flags
Patch none

Description Chris Dumez 2017-05-19 09:41:33 PDT
Make sure implementation of InitializeWebKit2() always run on the main thread as it is not safe to run it on any other thread (see <rdar://problem/32280185>).
Comment 1 Radar WebKit Bug Importer 2017-05-19 09:42:03 PDT
<rdar://problem/32295678>
Comment 2 Chris Dumez 2017-05-19 10:06:46 PDT
Created attachment 310670 [details]
Patch
Comment 3 Chris Dumez 2017-05-19 10:07:39 PDT
I have verified locally that <rdar://problem/32280185> no longer reproduces with this patch, even though the client is using the API on a background thread.
Comment 4 Geoffrey Garen 2017-05-19 10:40:20 PDT
Comment on attachment 310670 [details]
Patch

I think this is a fine work-around for the threading problems we've discovered in Safari.

In the long run, I think we need to remove this design. Instead of wallpapering over threading problems, since this is a main-thread-only API, we should ASSERT that it's only invoked on the main thread, and fix the cases where that's not true.
Comment 5 Chris Dumez 2017-05-19 11:16:45 PDT
(In reply to Geoffrey Garen from comment #4)
> Comment on attachment 310670 [details]
> Patch
> 
> I think this is a fine work-around for the threading problems we've
> discovered in Safari.
> 
> In the long run, I think we need to remove this design. Instead of
> wallpapering over threading problems, since this is a main-thread-only API,
> we should ASSERT that it's only invoked on the main thread, and fix the
> cases where that's not true.

Understood, we should try and ASSERT later once we've fixed API usage on non-main thread on client side.
Comment 6 WebKit Commit Bot 2017-05-19 11:44:17 PDT
Comment on attachment 310670 [details]
Patch

Clearing flags on attachment: 310670

Committed r217137: <http://trac.webkit.org/changeset/217137>
Comment 7 WebKit Commit Bot 2017-05-19 11:44:19 PDT
All reviewed patches have been landed.  Closing bug.
Comment 8 Darin Adler 2017-05-19 14:47:31 PDT
(In reply to Chris Dumez from comment #5)
> Understood, we should try and ASSERT later once we've fixed API usage on
> non-main thread on client side.

In the past for Objective-C APIs we made a linked on or after check. If the call was from code linked with a new enough version of WebKit, we threw an Objective-C exception if the API was called on the wrong thread. That worked well.
Comment 9 Csaba Osztrogonác 2017-05-22 03:40:47 PDT
(In reply to WebKit Commit Bot from comment #6)
> Comment on attachment 310670 [details]
> Patch
> 
> Clearing flags on attachment: 310670
> 
> Committed r217137: <http://trac.webkit.org/changeset/217137>

FYI, it broke the Apple Mac cmake build, because you missed to add the
new file to the build system:
Undefined symbols for architecture x86_64:
  "WebKit::InitializeWebKit2()", referenced from:
      API::Object::Object() in APIObject.cpp.o
      WebKit::WebProcessPool::create(API::ProcessPoolConfiguration&) in WebProcessPool.cpp.o
      API::WebsiteDataStore::defaultDataStore() in APIWebsiteDataStore.cpp.o
      -[WKWebViewConfiguration init] in WKWebViewConfiguration.mm.o
      -[_WKUserStyleSheet initWithSource:forMainFrameOnly:] in _WKUserStyleSheet.mm.o
      -[_WKUserStyleSheet initWithSource:forMainFrameOnly:legacyWhitelist:legacyBlacklist:userContentWorld:] in _WKUserStyleSheet.mm.o
      -[_WKUserStyleSheet initWithSource:forMainFrameOnly:legacyWhitelist:legacyBlacklist:baseURL:userContentWorld:] in _WKUserStyleSheet.mm.o
      ...
ld: symbol(s) not found for architecture x86_64
Comment 10 Csaba Osztrogonác 2017-05-26 02:51:18 PDT
fix landed in https://trac.webkit.org/changeset/217484/webkit
Comment 11 Rob Phillips 2020-04-24 13:43:24 PDT
Hi everybody - this bug fix has caused a dead lock race condition in ad frameworks (all being init'ed at the same time) that use WebKit to check user agent strings in the same way. This started happening for us in iOS 13.4.x but likely has been around a while.

A dead lock happens under this scenario:

- Both Frameworks A and B are initialized on a background queue in a serial order
- Framework A creates a WKWebView on the background queue without dispatching back to main thread first
- Framework B dispatches onto main thread from the background queue to create a WKWebView as well
- Both Framework A and B are now stuck in InitializeWebKit2()
- Within InitializeWebKit2(), Framework A's dispatch_sync is waiting on the main thread to be available so it can 
- Within InitializeWebKit2(), Framework B hangs the main thread while waiting for the std::call_once to finish (the same std::call_once used in Framework A's initialization)

See:

https://github.com/WebKit/webkit/blob/master/Source/WebKit/Shared/Cocoa/WebKit2InitializeCocoa.mm#L71

Please advise on whether this can be changed back to just asserting since web views should just assert if they are init'ed on anything but the main thread. From this bug report, it appears this just worked around a Safari bug a few years ago.

This advice from the comments below is sound:
>In the long run, I think we need to remove this design. Instead of wallpapering over threading problems, since this is a main-thread-only API, we should ASSERT that it's only invoked on the main thread, and fix the cases where that's not true.

Thanks!
Comment 12 Chris Dumez 2020-04-24 13:47:31 PDT
(In reply to Rob Phillips from comment #11)
> Hi everybody - this bug fix has caused a dead lock race condition in ad
> frameworks (all being init'ed at the same time) that use WebKit to check
> user agent strings in the same way. This started happening for us in iOS
> 13.4.x but likely has been around a while.
> 
> A dead lock happens under this scenario:
> 
> - Both Frameworks A and B are initialized on a background queue in a serial
> order
> - Framework A creates a WKWebView on the background queue without
> dispatching back to main thread first

That's a bug and should be fixed in the framework.

> - Framework B dispatches onto main thread from the background queue to
> create a WKWebView as well
> - Both Framework A and B are now stuck in InitializeWebKit2()
> - Within InitializeWebKit2(), Framework A's dispatch_sync is waiting on the
> main thread to be available so it can 
> - Within InitializeWebKit2(), Framework B hangs the main thread while
> waiting for the std::call_once to finish (the same std::call_once used in
> Framework A's initialization)
> 
> See:
> 
> https://github.com/WebKit/webkit/blob/master/Source/WebKit/Shared/Cocoa/
> WebKit2InitializeCocoa.mm#L71
> 
> Please advise on whether this can be changed back to just asserting since
> web views should just assert if they are init'ed on anything but the main
> thread. From this bug report, it appears this just worked around a Safari
> bug a few years ago.
> 
> This advice from the comments below is sound:
> >In the long run, I think we need to remove this design. Instead of wallpapering over threading problems, since this is a main-thread-only API, we should ASSERT that it's only invoked on the main thread, and fix the cases where that's not true.
> 
> Thanks!
Comment 13 Rob Phillips 2020-04-24 14:10:27 PDT
Agreed re: it being an ad framework bug; but it's also a bug in WebKit. 

Instead of asserting (which is what a lot of UIKit does now) which would allow for a proper stack trace and crash report, we instead get a dead lock / watchdog kill which we can't know about or debug unless customers send us crash reports from their device.

The proper thing to do here would be to assert in InitializeWebKit2() if not called from the main thread
Comment 14 Darin Adler 2020-04-24 16:13:54 PDT
(In reply to Rob Phillips from comment #13)
> The proper thing to do here would be to assert in InitializeWebKit2() if not
> called from the main thread

An interesting claim!
Comment 15 Chris Dumez 2020-04-24 16:17:26 PDT
(In reply to Darin Adler from comment #14)
> (In reply to Rob Phillips from comment #13)
> > The proper thing to do here would be to assert in InitializeWebKit2() if not
> > called from the main thread
> 
> An interesting claim!

We may be able to get away with it if we do it behind a linked-on-after check so that only apps that get rebuilt against the latest SDK would get the new behavior (CRASH if InitializeWebKit2() is called from non main thread).
Comment 16 Darin Adler 2020-04-24 16:25:30 PDT
Sure. That’s what the WebCore ThreadCheck.h header is about.

WebCoreThreadViolationCheckRoundOne
WebCoreThreadViolationCheckRoundTwo
WebCoreThreadViolationCheckRoundThree
Comment 17 Rob Phillips 2020-04-25 13:09:49 PDT
(In reply to Darin Adler from comment #14)
> (In reply to Rob Phillips from comment #13)
> > The proper thing to do here would be to assert in InitializeWebKit2() if not
> > called from the main thread
> 
> An interesting claim!

😄Apologies, my *preference would be. Either way, thanks for the consideration folks. I'm patching around the ad frameworks we're using for now
Comment 18 Yusuke Suzuki 2020-04-25 14:08:08 PDT
This can be a dead lock if the main thread is waiting for the initialization of WebKit done in the different thread, is my understanding correct?

1. The main thread dispatches a task in a A thread and waiting for the result from that thread at some point.
2. A thread calls WebKit APIs
3. A thread posts a task to the main thread to initialize WebKit
4. dead lock
Comment 19 Yusuke Suzuki 2020-04-25 14:15:52 PDT
(In reply to Yusuke Suzuki from comment #18)
> This can be a dead lock if the main thread is waiting for the initialization
> of WebKit done in the different thread, is my understanding correct?
> 
> 1. The main thread dispatches a task in a A thread and waiting for the
> result from that thread at some point.
> 2. A thread calls WebKit APIs
> 3. A thread posts a task to the main thread to initialize WebKit
> 4. dead lock

Ah, I was just looking at the patch, and not looking at @Rob's comment!
Comment 20 Chris Dumez 2020-05-22 15:29:23 PDT
(In reply to Chris Dumez from comment #15)
> (In reply to Darin Adler from comment #14)
> > (In reply to Rob Phillips from comment #13)
> > > The proper thing to do here would be to assert in InitializeWebKit2() if not
> > > called from the main thread
> > 
> > An interesting claim!
> 
> We may be able to get away with it if we do it behind a linked-on-after
> check so that only apps that get rebuilt against the latest SDK would get
> the new behavior (CRASH if InitializeWebKit2() is called from non main
> thread).

Doing so in Bug 212283.