blob: 9544b51c278df569ea4ab0bfef7ef744ac52c735 [file] [log] [blame]
/*
* Copyright (C) 2006-2018 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "WebCoreThread.h"
#if PLATFORM(IOS_FAMILY)
#import "CommonVM.h"
#import "FloatingPointEnvironment.h"
#import "GraphicsContextGLOpenGL.h"
#import "Logging.h"
#import "RuntimeApplicationChecks.h"
#import "ThreadGlobalData.h"
#import "WAKWindow.h"
#import "WKUtilities.h"
#import "WebCoreJITOperations.h"
#import "WebCoreThreadInternal.h"
#import "WebCoreThreadMessage.h"
#import "WebCoreThreadRun.h"
#import <Foundation/NSInvocation.h>
#import <JavaScriptCore/InitializeThreading.h>
#import <JavaScriptCore/JSLock.h>
#import <libkern/OSAtomic.h>
#import <objc/runtime.h>
#import <wtf/Assertions.h>
#import <wtf/BlockPtr.h>
#import <wtf/MainThread.h>
#import <wtf/NeverDestroyed.h>
#import <wtf/RecursiveLockAdapter.h>
#import <wtf/RunLoop.h>
#import <wtf/ThreadSpecific.h>
#import <wtf/Threading.h>
#import <wtf/WorkQueue.h>
#import <wtf/spi/cf/CFRunLoopSPI.h>
#import <wtf/spi/cocoa/objcSPI.h>
#import <wtf/text/AtomString.h>
#define LOG_MESSAGES 0
#define LOG_WEB_LOCK 0
#define LOG_MAIN_THREAD_LOCKING 0
#define LOG_RELEASES 0
#define DistantFuture (86400.0 * 2000 * 365.2425 + 86400.0) // same as +[NSDate distantFuture]
namespace {
// Release global state that is accessed from both web thread as well
// as any client thread that calls into WebKit.
void ReleaseWebThreadGlobalState()
{
// ANGLE maintains thread global state for its current context.
// This is conceptually owned by the web thread lock. Release the context
// before the lock is released.
// In single-threaded environments we do not need to unset the context, as there should not be access from
// multiple threads.
ASSERT(WebThreadIsEnabled());
using ReleaseThreadResourceBehavior = WebCore::GraphicsContextGLOpenGL::ReleaseThreadResourceBehavior;
// For web thread, just release the context as we know we will see calls to it again.
// For non-web threads, e.g. third-party client threads, we don't know if we ever see another call from the
// thread, so we also release the thread resources.
ReleaseThreadResourceBehavior releaseBehavior =
WebThreadIsCurrent() ? ReleaseThreadResourceBehavior::ReleaseCurrentContext : ReleaseThreadResourceBehavior::ReleaseThreadResources;
WebCore::GraphicsContextGLOpenGL::releaseThreadResources(releaseBehavior);
}
}
static const constexpr Seconds DelegateWaitInterval { 10_s };
static void _WebThreadAutoLock();
static void _WebThreadLock();
static void _WebThreadLockFromAnyThread(bool shouldLog);
static void _WebThreadUnlock();
@interface NSObject(ForwardDeclarations)
-(void)_webcore_releaseOnWebThread;
-(void)_webcore_releaseWithWebThreadLock;
@end
@implementation NSObject(WebCoreThreadAdditions)
- (void)releaseOnMainThread {
if ([NSThread isMainThread])
[self release];
else
[self performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
}
@end
static RecursiveLock webLock;
static Lock webThreadReleaseLock;
static RecursiveLock webCoreReleaseLock;
static void* autoreleasePoolMark;
static CFRunLoopRef webThreadRunLoop;
static pthread_t webThread;
static BOOL isWebThreadLocked;
static BOOL webThreadStarted;
static unsigned webThreadLockCount;
static void* savedAutoreleasePoolMark;
static BOOL isNestedWebThreadRunLoop;
typedef enum {
PushOrPopAutoreleasePool,
IgnoreAutoreleasePool
} AutoreleasePoolOperation;
static RetainPtr<CFRunLoopSourceRef>& webThreadReleaseSource()
{
static NeverDestroyed<RetainPtr<CFRunLoopSourceRef>> webThreadReleaseSource;
return webThreadReleaseSource;
}
static RetainPtr<CFMutableArrayRef>& webThreadReleaseObjArray()
{
static NeverDestroyed<RetainPtr<CFMutableArrayRef>> webThreadReleaseObjArray;
return webThreadReleaseObjArray;
}
static Lock delegateLock;
static StaticCondition delegateCondition;
static RetainPtr<CFRunLoopSourceRef>& delegateSource()
{
static NeverDestroyed<RetainPtr<CFRunLoopSourceRef>> delegateSource;
return delegateSource;
}
static BOOL delegateHandled;
#if LOG_MAIN_THREAD_LOCKING
static BOOL sendingDelegateMessage;
#endif
static RetainPtr<CFRunLoopObserverRef>& mainRunLoopAutoUnlockObserver()
{
static NeverDestroyed<RetainPtr<CFRunLoopObserverRef>> mainRunLoopAutoUnlockObserver;
return mainRunLoopAutoUnlockObserver;
}
static BOOL mainThreadHasPendingAutoUnlock;
static Lock startupLock;
static StaticCondition startupCondition;
static WebThreadContext* webThreadContext;
static unsigned mainThreadLockCount;
static unsigned otherThreadLockCount;
static unsigned sMainThreadModalCount;
WEBCORE_EXPORT volatile bool webThreadShouldYield;
static void WebCoreObjCDeallocOnWebThreadImpl(id self, SEL _cmd);
static void WebCoreObjCDeallocWithWebThreadLock(Class cls);
static void WebCoreObjCDeallocWithWebThreadLockImpl(id self, SEL _cmd);
static RetainPtr<NSMutableArray>& sAsyncDelegates()
{
static NeverDestroyed<RetainPtr<NSMutableArray>> asyncDelegates;
return asyncDelegates;
}
static RetainPtr<NSRunLoop>& webThreadNSRunLoop()
{
static NeverDestroyed<RetainPtr<NSRunLoop>> webThreadNSRunLoop;
return webThreadNSRunLoop;
}
static RetainPtr<NSInvocation>& delegateInvocation()
{
static NeverDestroyed<RetainPtr<NSInvocation>> delegateInvocation;
return delegateInvocation;
}
WEBCORE_EXPORT volatile unsigned webThreadDelegateMessageScopeCount = 0;
static bool perCalloutAutoreleasepoolEnabled;
static inline void SendMessage(RetainPtr<NSInvocation>&& invocation)
{
[invocation invoke];
if (!WebThreadIsEnabled() || CFRunLoopGetMain() == CFRunLoopGetCurrent())
return;
RunLoop::main().dispatch([invocation = WTFMove(invocation)] { });
}
static void HandleDelegateSource(void*)
{
ASSERT(!WebThreadIsCurrent());
#if LOG_MAIN_THREAD_LOCKING
sendingDelegateMessage = YES;
#endif
_WebThreadAutoLock();
{
Locker locker { delegateLock };
#if LOG_MESSAGES
if ([[delegateInvocation() target] isKindOfClass:[NSNotificationCenter class]]) {
id argument0;
[delegateInvocation() getArgument:&argument0 atIndex:0];
NSLog(@"notification receive: %@", argument0);
} else
NSLog(@"delegate receive: %@", NSStringFromSelector([delegateInvocation() selector]));
#endif
SendMessage(WTFMove(delegateInvocation()));
delegateHandled = YES;
delegateCondition.notifyOne();
}
#if LOG_MAIN_THREAD_LOCKING
sendingDelegateMessage = NO;
#endif
}
class WebThreadDelegateMessageScope {
public:
WebThreadDelegateMessageScope() { ++webThreadDelegateMessageScopeCount; }
~WebThreadDelegateMessageScope()
{
ASSERT(webThreadDelegateMessageScopeCount);
--webThreadDelegateMessageScopeCount;
}
};
static void SendDelegateMessage(RetainPtr<NSInvocation>&& invocation)
{
if (!WebThreadIsCurrent()) {
SendMessage(WTFMove(invocation));
return;
}
ASSERT(delegateSource());
delegateLock.lock();
delegateInvocation() = WTFMove(invocation);
delegateHandled = NO;
#if LOG_MESSAGES
if ([[delegateInvocation() target] isKindOfClass:[NSNotificationCenter class]]) {
id argument0;
[delegateInvocation() getArgument:&argument0 atIndex:0];
NSLog(@"notification send: %@", argument0);
} else
NSLog(@"delegate send: %@", NSStringFromSelector([delegateInvocation() selector]));
#endif
{
WebThreadDelegateMessageScope delegateScope;
// Code block created to scope JSC::JSLock::DropAllLocks outside of WebThreadLock()
JSC::JSLock::DropAllLocks dropAllLocks(WebCore::commonVM());
_WebThreadUnlock();
CFRunLoopSourceSignal(delegateSource().get());
CFRunLoopWakeUp(CFRunLoopGetMain());
while (!delegateHandled) {
if (!delegateCondition.waitFor(delegateLock, DelegateWaitInterval)) {
id delegateInformation;
if ([[delegateInvocation() target] isKindOfClass:[NSNotificationCenter class]])
[delegateInvocation() getArgument:&delegateInformation atIndex:0];
else
delegateInformation = NSStringFromSelector([delegateInvocation() selector]);
auto mode = adoptCF(CFRunLoopCopyCurrentMode(CFRunLoopGetMain()));
NSLog(@"%s: delegate (%@) failed to return after waiting %f seconds. main run loop mode: %@", __PRETTY_FUNCTION__, delegateInformation, DelegateWaitInterval.seconds(), mode.get());
}
}
delegateLock.unlock();
_WebThreadLock();
}
}
void WebThreadRunOnMainThread(void(^delegateBlock)())
{
if (!WebThreadIsCurrent()) {
ASSERT(pthread_main_np());
delegateBlock();
return;
}
JSC::JSLock::DropAllLocks dropAllLocks(WebCore::commonVM());
_WebThreadUnlock();
WorkQueue::main().dispatchSync(makeBlockPtr(delegateBlock).get());
_WebThreadLock();
}
void WebThreadAdoptAndRelease(id obj)
{
ASSERT(!WebThreadIsCurrent());
ASSERT(webThreadReleaseSource());
#if LOG_RELEASES
NSLog(@"Release send [main thread]: %@", obj);
#endif
Locker locker { webThreadReleaseLock };
if (webThreadReleaseObjArray() == nil)
webThreadReleaseObjArray() = adoptCF(CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, nullptr));
CFArrayAppendValue(webThreadReleaseObjArray().get(), obj);
CFRunLoopSourceSignal(webThreadReleaseSource().get());
CFRunLoopWakeUp(webThreadRunLoop);
}
void WebCoreObjCDeallocOnWebThread(Class cls)
{
SEL releaseSEL = @selector(release);
SEL webThreadReleaseSEL = @selector(_webcore_releaseOnWebThread);
// get the existing release method
Method releaseMethod = class_getInstanceMethod(cls, releaseSEL);
if (!releaseMethod) {
ASSERT_WITH_MESSAGE(releaseMethod, "WebCoreObjCDeallocOnWebThread() failed to find %s for %@", releaseSEL, NSStringFromClass(cls));
return;
}
// add the implementation that ensures release WebThread release/deallocation
if (!class_addMethod(cls, webThreadReleaseSEL, (IMP)WebCoreObjCDeallocOnWebThreadImpl, method_getTypeEncoding(releaseMethod))) {
ASSERT_WITH_MESSAGE(releaseMethod, "WebCoreObjCDeallocOnWebThread() failed to add %s for %@", webThreadReleaseSEL, NSStringFromClass(cls));
return;
}
// ensure the implementation exists at cls in the class hierarchy
if (class_addMethod(cls, releaseSEL, class_getMethodImplementation(cls, releaseSEL), method_getTypeEncoding(releaseMethod)))
releaseMethod = class_getInstanceMethod(cls, releaseSEL);
// swizzle the old release for the new implementation
method_exchangeImplementations(releaseMethod, class_getInstanceMethod(cls, webThreadReleaseSEL));
}
void WebCoreObjCDeallocWithWebThreadLock(Class cls)
{
SEL releaseSEL = @selector(release);
SEL webThreadLockReleaseSEL = @selector(_webcore_releaseWithWebThreadLock);
// get the existing release method
Method releaseMethod = class_getInstanceMethod(cls, releaseSEL);
if (!releaseMethod) {
ASSERT_WITH_MESSAGE(releaseMethod, "WebCoreObjCDeallocWithWebThreadLock() failed to find %s for %@", releaseSEL, NSStringFromClass(cls));
return;
}
// add the implementation that ensures release WebThreadLock release/deallocation
if (!class_addMethod(cls, webThreadLockReleaseSEL, (IMP)WebCoreObjCDeallocWithWebThreadLockImpl, method_getTypeEncoding(releaseMethod))) {
ASSERT_WITH_MESSAGE(releaseMethod, "WebCoreObjCDeallocWithWebThreadLock() failed to add %s for %@", webThreadLockReleaseSEL, NSStringFromClass(cls));
return;
}
// ensure the implementation exists at cls in the class hierarchy
if (class_addMethod(cls, releaseSEL, class_getMethodImplementation(cls, releaseSEL), method_getTypeEncoding(releaseMethod)))
releaseMethod = class_getInstanceMethod(cls, releaseSEL);
// swizzle the old release for the new implementation
method_exchangeImplementations(releaseMethod, class_getInstanceMethod(cls, webThreadLockReleaseSEL));
}
void WebCoreObjCDeallocOnWebThreadImpl(id self, SEL)
{
if (!WebThreadIsEnabled()) {
[self _webcore_releaseOnWebThread];
return;
}
{
Locker locker { webCoreReleaseLock };
if ([self retainCount] != 1) {
// This is not the only reference retaining the object, so another
// thread could also call release - hold the lock whilst calling
// release to avoid a race condition.
[self _webcore_releaseOnWebThread];
return;
}
// This is the only reference retaining the object, so we can
// safely release the webCoreReleaseLock now.
}
if (WebThreadIsCurrent())
[self _webcore_releaseOnWebThread];
else
WebThreadAdoptAndRelease(self);
}
void WebCoreObjCDeallocWithWebThreadLockImpl(id self, SEL)
{
Locker locker { webCoreReleaseLock };
if (WebThreadIsLockedOrDisabled() || 1 != [self retainCount])
[self _webcore_releaseWithWebThreadLock];
else
WebThreadAdoptAndRelease(self);
}
static void HandleWebThreadReleaseSource(void*)
{
ASSERT(WebThreadIsCurrent());
RetainPtr<CFMutableArrayRef> objects;
{
Locker locker { webThreadReleaseLock };
if (CFArrayGetCount(webThreadReleaseObjArray().get())) {
objects = adoptCF(CFArrayCreateMutableCopy(nullptr, 0, webThreadReleaseObjArray().get()));
CFArrayRemoveAllValues(webThreadReleaseObjArray().get());
}
}
if (!objects)
return;
for (unsigned i = 0, count = CFArrayGetCount(objects.get()); i < count; ++i) {
auto obj = adoptCF(CFArrayGetValueAtIndex(objects.get(), i));
#if LOG_RELEASES
NSLog(@"Release recv [web thread] : %@", obj.get());
#endif
}
}
void WebThreadCallDelegate(NSInvocation* invocation)
{
// NSInvocation released in SendMessage()
SendDelegateMessage(invocation);
}
void WebThreadPostNotification(NSString* name, id object, id userInfo)
{
if (pthread_main_np())
[[NSNotificationCenter defaultCenter] postNotificationName:name object:object userInfo:userInfo];
else {
RunLoop::main().dispatch([name = retainPtr(name), object = retainPtr(object), userInfo = retainPtr(userInfo)] {
[[NSNotificationCenter defaultCenter] postNotificationName:name.get() object:object.get() userInfo:userInfo.get()];
});
}
}
void WebThreadCallDelegateAsync(NSInvocation* invocation)
{
ASSERT(invocation);
if (WebThreadIsCurrent())
[sAsyncDelegates() addObject:invocation];
else
WebThreadCallDelegate(invocation);
}
// Note: despite the name, returns an autoreleased object.
NSInvocation* WebThreadMakeNSInvocation(id target, SEL selector)
{
NSMethodSignature* signature = [target methodSignatureForSelector:selector];
ASSERT(signature);
if (signature) {
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:selector];
[invocation setTarget:target];
[invocation retainArguments];
return invocation;
}
return nil;
}
static void MainRunLoopAutoUnlock(CFRunLoopObserverRef, CFRunLoopActivity, void*)
{
ASSERT(!WebThreadIsCurrent());
if (sMainThreadModalCount)
return;
if (!mainThreadHasPendingAutoUnlock)
return;
mainThreadHasPendingAutoUnlock = NO;
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), mainRunLoopAutoUnlockObserver().get(), kCFRunLoopCommonModes);
_WebThreadUnlock();
}
static void _WebThreadAutoLock(void)
{
ASSERT(!WebThreadIsCurrent());
if (!mainThreadLockCount) {
mainThreadHasPendingAutoUnlock = YES;
CFRunLoopAddObserver(CFRunLoopGetCurrent(), mainRunLoopAutoUnlockObserver().get(), kCFRunLoopCommonModes);
_WebThreadLock();
CFRunLoopWakeUp(CFRunLoopGetMain());
}
}
static void WebRunLoopLockInternal(AutoreleasePoolOperation poolOperation)
{
_WebThreadLock();
if (poolOperation == PushOrPopAutoreleasePool && !perCalloutAutoreleasepoolEnabled)
autoreleasePoolMark = objc_autoreleasePoolPush();
isWebThreadLocked = YES;
}
static void WebRunLoopUnlockInternal(AutoreleasePoolOperation poolOperation)
{
ASSERT(sAsyncDelegates());
if ([sAsyncDelegates() count]) {
for (NSInvocation* invocation in sAsyncDelegates().get())
SendDelegateMessage(invocation);
[sAsyncDelegates() removeAllObjects];
}
if (poolOperation == PushOrPopAutoreleasePool && !perCalloutAutoreleasepoolEnabled)
objc_autoreleasePoolPop(autoreleasePoolMark);
_WebThreadUnlock();
isWebThreadLocked = NO;
}
static void WebRunLoopLock(CFRunLoopObserverRef, CFRunLoopActivity activity, void*)
{
ASSERT(WebThreadIsCurrent());
ASSERT_UNUSED(activity, activity == kCFRunLoopAfterWaiting || activity == kCFRunLoopBeforeTimers || activity == kCFRunLoopBeforeSources);
// If the WebThread is locked by the main thread then we want to
// grab the lock ourselves when the main thread releases the lock.
if (isWebThreadLocked && !mainThreadLockCount)
return;
WebRunLoopLockInternal(PushOrPopAutoreleasePool);
}
static void WebRunLoopUnlock(CFRunLoopObserverRef, CFRunLoopActivity activity, void*)
{
ASSERT(WebThreadIsCurrent());
ASSERT_UNUSED(activity, activity == kCFRunLoopBeforeWaiting || activity == kCFRunLoopExit);
ASSERT(!mainThreadLockCount);
if (!isWebThreadLocked)
return;
WebRunLoopUnlockInternal(PushOrPopAutoreleasePool);
}
static void MainRunLoopUnlockGuard(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void* context)
{
ASSERT(!WebThreadIsCurrent());
// We shouldn't have the web lock at this point. However, MobileMail sometimes
// get to a state where the main thread has web lock but it didn't release it on last
// runloop exit, and web thread gets stuck at waiting for the lock. If this happens,
// we need to help release the lock. See <rdar://problem/8005192>.
if (mainThreadLockCount && !sMainThreadModalCount) {
NSLog(@"WARNING: Main thread didn't release the lock at last runloop exit!");
MainRunLoopAutoUnlock(observer, activity, context);
if (mainThreadLockCount)
mainThreadLockCount = 0;
}
}
static void _WebRunLoopEnableNestedFromMainThread()
{
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), mainRunLoopAutoUnlockObserver().get(), kCFRunLoopCommonModes);
}
static void _WebRunLoopDisableNestedFromMainThread()
{
CFRunLoopAddObserver(CFRunLoopGetCurrent(), mainRunLoopAutoUnlockObserver().get(), kCFRunLoopCommonModes);
}
void WebRunLoopEnableNested()
{
if (!WebThreadIsEnabled())
return;
ASSERT(!isNestedWebThreadRunLoop);
if (!WebThreadIsCurrent())
_WebRunLoopEnableNestedFromMainThread();
_CFRunLoopSetPerCalloutAutoreleasepoolEnabled(NO);
savedAutoreleasePoolMark = autoreleasePoolMark;
autoreleasePoolMark = 0;
WebRunLoopUnlockInternal(IgnoreAutoreleasePool);
isNestedWebThreadRunLoop = YES;
}
void WebRunLoopDisableNested()
{
if (!WebThreadIsEnabled())
return;
ASSERT(isNestedWebThreadRunLoop);
if (!WebThreadIsCurrent())
_WebRunLoopDisableNestedFromMainThread();
_CFRunLoopSetPerCalloutAutoreleasepoolEnabled(YES);
autoreleasePoolMark = savedAutoreleasePoolMark;
savedAutoreleasePoolMark = 0;
WebRunLoopLockInternal(IgnoreAutoreleasePool);
isNestedWebThreadRunLoop = NO;
}
static ThreadSpecific<WebThreadContext, WTF::CanBeGCThread::True>* threadContext;
static WebThreadContext* CurrentThreadContext()
{
static std::once_flag flag;
std::call_once(flag, [] {
threadContext = new ThreadSpecific<WebThreadContext, WTF::CanBeGCThread::True>();
});
return *threadContext;
}
static void* RunWebThread(void*)
{
FloatingPointEnvironment::singleton().propagateMainThreadEnvironment();
// WTF::initializeWebThread() needs to be called before JSC::initialize() since the
// code invoked by the latter needs to know if it's running on the WebThread. See
// <rdar://problem/8502487>.
WTF::initializeWebThread();
JSC::initialize();
WebCore::populateJITOperations();
// Make sure that the WebThread and the main thread share the same ThreadGlobalData objects.
WebCore::threadGlobalData().setWebCoreThreadData();
#if HAVE(PTHREAD_SETNAME_NP)
pthread_setname_np("WebThread");
#endif
webThreadContext = CurrentThreadContext();
webThreadRunLoop = CFRunLoopGetCurrent();
webThreadNSRunLoop() = [NSRunLoop currentRunLoop];
auto webRunLoopLockObserverRef = adoptCF(CFRunLoopObserverCreate(nullptr, kCFRunLoopBeforeTimers | kCFRunLoopBeforeSources | kCFRunLoopAfterWaiting, YES, 0, WebRunLoopLock, nullptr));
CFRunLoopAddObserver(webThreadRunLoop, webRunLoopLockObserverRef.get(), kCFRunLoopCommonModes);
WebThreadInitRunQueue();
// We must have the lock when CA paints in the web thread. CA commits at 2000000 so we use larger order number than that to free the lock.
auto webRunLoopUnlockObserverRef = adoptCF(CFRunLoopObserverCreate(nullptr, kCFRunLoopBeforeWaiting | kCFRunLoopExit, YES, 2500000, WebRunLoopUnlock, nullptr));
CFRunLoopAddObserver(webThreadRunLoop, webRunLoopUnlockObserverRef.get(), kCFRunLoopCommonModes);
CFRunLoopSourceContext ReleaseSourceContext = {0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, HandleWebThreadReleaseSource};
webThreadReleaseSource() = adoptCF(CFRunLoopSourceCreate(nullptr, -1, &ReleaseSourceContext));
CFRunLoopAddSource(webThreadRunLoop, webThreadReleaseSource().get(), kCFRunLoopDefaultMode);
perCalloutAutoreleasepoolEnabled = _CFRunLoopSetPerCalloutAutoreleasepoolEnabled(YES);
{
Locker locker { startupLock };
startupCondition.notifyOne();
}
while (1)
CFRunLoopRunInMode(kCFRunLoopDefaultMode, DistantFuture, true);
return nullptr;
}
static void StartWebThread()
{
webThreadStarted = TRUE;
// ThreadGlobalData touches AtomString, which requires Threading initialization.
WTF::initializeMainThread();
// Initialize AtomString on the main thread.
WTF::AtomString::init();
// Initialize ThreadGlobalData on the main UI thread so that the WebCore thread
// can later set it's thread-specific data to point to the same objects.
WebCore::ThreadGlobalData& unused = WebCore::threadGlobalData();
UNUSED_PARAM(unused);
// register class for WebThread deallocation
WebCoreObjCDeallocOnWebThread([WAKWindow class]);
WebCoreObjCDeallocWithWebThreadLock([WAKView class]);
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopSourceContext delegateSourceContext = {0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, HandleDelegateSource};
delegateSource() = adoptCF(CFRunLoopSourceCreate(nullptr, 0, &delegateSourceContext));
// We shouldn't get delegate callbacks while scrolling, but there might be
// one outstanding when we start. Add the source for all common run loop
// modes so we don't block the web thread while scrolling.
CFRunLoopAddSource(runLoop, delegateSource().get(), kCFRunLoopCommonModes);
sAsyncDelegates() = adoptNS([[NSMutableArray alloc] init]);
mainRunLoopAutoUnlockObserver() = adoptCF(CFRunLoopObserverCreate(nullptr, kCFRunLoopBeforeWaiting | kCFRunLoopExit, YES, 3000001, MainRunLoopAutoUnlock, nullptr));
pthread_attr_t tattr;
pthread_attr_init(&tattr);
pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM);
// The web thread is a secondary thread, and secondary threads are usually given
// a 512 kb stack, but we need more space in order to have room for the JavaScriptCore
// reentrancy limit. This limit works on both the simulator and the device.
pthread_attr_setstacksize(&tattr, 800 * KB);
pthread_attr_set_qos_class_np(&tattr, QOS_CLASS_USER_INTERACTIVE, -10);
// Wait for the web thread to startup completely before we continue.
{
Locker locker { startupLock };
// Propagate the mainThread's fenv to workers & the web thread.
FloatingPointEnvironment::singleton().saveMainThreadEnvironment();
pthread_create(&webThread, &tattr, RunWebThread, nullptr);
pthread_attr_destroy(&tattr);
startupCondition.wait(startupLock);
}
initializeApplicationUIThread();
}
#if LOG_WEB_LOCK || LOG_MAIN_THREAD_LOCKING
static unsigned lockCount;
#endif
static void _WebThreadLock()
{
// Suspend the web thread if the main thread is trying to lock.
bool onMainThread = pthread_main_np();
if (onMainThread)
webThreadShouldYield = true;
else if (!WebThreadIsCurrent()) {
NSLog(@"%s, %p: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...", __PRETTY_FUNCTION__, CurrentThreadContext());
CRASH();
}
webLock.lock();
#if LOG_WEB_LOCK || LOG_MAIN_THREAD_LOCKING
lockCount++;
#if LOG_WEB_LOCK
NSLog(@"lock %d, web-thread: %d", lockCount, WebThreadIsCurrent());
#endif
#endif
if (onMainThread) {
ASSERT(CFRunLoopGetCurrent() == CFRunLoopGetMain());
webThreadShouldYield = false;
mainThreadLockCount++;
#if LOG_MAIN_THREAD_LOCKING
if (!sendingDelegateMessage && lockCount == 1)
NSLog(@"Main thread locking outside of delegate messages.");
#endif
} else {
webThreadLockCount++;
if (webThreadLockCount > 1) {
NSLog(@"%s, %p: Multiple locks on web thread not allowed! Please file a bug. Crashing now...", __PRETTY_FUNCTION__, CurrentThreadContext());
CRASH();
}
}
}
void WebThreadLock(void)
{
if (!webThreadStarted || pthread_equal(webThread, pthread_self()))
return;
_WebThreadAutoLock();
}
void WebThreadUnlock(void)
{
// This is a no-op, we unlock automatically on top of the runloop
ASSERT(!WebThreadIsCurrent());
}
void WebThreadLockFromAnyThread(void)
{
_WebThreadLockFromAnyThread(true);
}
void WebThreadLockFromAnyThreadNoLog(void)
{
_WebThreadLockFromAnyThread(false);
}
static void _WebThreadLockFromAnyThread(bool shouldLog)
{
if (!webThreadStarted)
return;
ASSERT(!WebThreadIsCurrent());
if (pthread_main_np()) {
_WebThreadAutoLock();
return;
}
if (shouldLog)
NSLog(@"%s, %p: Obtaining the web lock from a thread other than the main thread or the web thread. UIKit should not be called from a secondary thread.", __PRETTY_FUNCTION__, CurrentThreadContext());
webLock.lock();
// This used for any thread other than the web thread.
otherThreadLockCount++;
webThreadShouldYield = false;
}
void WebThreadUnlockFromAnyThread(void)
{
if (!webThreadStarted)
return;
ASSERT(!WebThreadIsCurrent());
ReleaseWebThreadGlobalState();
// No-op except from a secondary thread.
if (pthread_main_np())
return;
ASSERT(otherThreadLockCount);
otherThreadLockCount--;
webLock.unlock();
}
void WebThreadUnlockGuardForMail(void)
{
ASSERT(!WebThreadIsCurrent());
auto mainRunLoopUnlockGuardObserver = adoptCF(CFRunLoopObserverCreate(nullptr, kCFRunLoopEntry, YES, 0, MainRunLoopUnlockGuard, nullptr));
CFRunLoopAddObserver(CFRunLoopGetMain(), mainRunLoopUnlockGuardObserver.get(), kCFRunLoopCommonModes);
}
void _WebThreadUnlock()
{
ReleaseWebThreadGlobalState();
#if LOG_WEB_LOCK || LOG_MAIN_THREAD_LOCKING
lockCount--;
#if LOG_WEB_LOCK
NSLog(@"unlock %d, web-thread: %d", lockCount, WebThreadIsCurrent());
#endif
#endif
if (!WebThreadIsCurrent()) {
ASSERT(mainThreadLockCount);
mainThreadLockCount--;
} else {
webThreadLockCount--;
webThreadShouldYield = false;
}
webLock.unlock();
}
bool WebThreadIsLocked(void)
{
if (WebThreadIsCurrent())
return webThreadLockCount;
if (pthread_main_np())
return mainThreadLockCount;
return otherThreadLockCount;
}
bool WebThreadIsLockedOrDisabled(void)
{
return !WebThreadIsEnabled() || WebThreadIsLocked();
}
void WebThreadLockPushModal(void)
{
if (WebThreadIsCurrent())
return;
ASSERT(WebThreadIsLocked());
++sMainThreadModalCount;
}
void WebThreadLockPopModal(void)
{
if (WebThreadIsCurrent())
return;
ASSERT(WebThreadIsLocked());
ASSERT(sMainThreadModalCount);
--sMainThreadModalCount;
}
CFRunLoopRef WebThreadRunLoop(void)
{
if (webThreadStarted) {
ASSERT(webThreadRunLoop);
return webThreadRunLoop;
}
return CFRunLoopGetCurrent();
}
NSRunLoop* WebThreadNSRunLoop(void)
{
if (webThreadStarted) {
ASSERT(webThreadNSRunLoop());
return webThreadNSRunLoop().get();
}
return [NSRunLoop currentRunLoop];
}
WebThreadContext* WebThreadCurrentContext(void)
{
return CurrentThreadContext();
}
void WebThreadEnable(void)
{
RELEASE_ASSERT_WITH_MESSAGE(!WebCore::IOSApplication::isWebProcess(), "The WebProcess should never run a Web Thread");
if (WebCore::IOSApplication::isSpringBoard()) {
using WebCore::LogThreading;
RELEASE_LOG_FAULT(Threading, "SpringBoard enabled WebThread.");
}
static std::once_flag flag;
std::call_once(flag, StartWebThread);
}
bool WebThreadIsEnabled(void)
{
return webThreadStarted;
}
bool WebThreadIsCurrent(void)
{
return webThreadStarted && pthread_equal(webThread, pthread_self());
}
bool WebThreadNotCurrent(void)
{
return webThreadStarted && !pthread_equal(webThread, pthread_self());
}
#endif // PLATFORM(IOS_FAMILY)