1/*
2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "WebCoreThread.h"
28
29#if PLATFORM(IOS)
30
31#import "FloatingPointEnvironment.h"
32#import "JSDOMWindowBase.h"
33#import "ThreadGlobalData.h"
34#import "RuntimeApplicationChecksIOS.h"
35#import "WebCoreThreadInternal.h"
36#import "WebCoreThreadMessage.h"
37#import "WebCoreThreadRun.h"
38#import "WebCoreThreadSafe.h"
39#import "WKUtilities.h"
40
41#import <runtime/InitializeThreading.h>
42#import <runtime/JSLock.h>
43#import <wtf/Assertions.h>
44#import <wtf/MainThread.h>
45#import <wtf/Threading.h>
46#import <wtf/text/AtomicString.h>
47
48#import <CoreFoundation/CFPriv.h>
49#import <Foundation/NSInvocation.h>
50#import <libkern/OSAtomic.h>
51#import <objc/runtime.h>
52
53#define LOG_MESSAGES 0
54#define LOG_WEB_LOCK 0
55#define LOG_MAIN_THREAD_LOCKING 0
56#define LOG_RELEASES 0
57
58#define DistantFuture   (86400.0 * 2000 * 365.2425 + 86400.0)   // same as +[NSDate distantFuture]
59#define MaxArgCount         5
60#define DrawWaitInterval     10
61#define DelegateWaitInterval 10
62
63static void _WebThreadAutoLock(void);
64static bool _WebTryThreadLock(bool shouldTry);
65static void _WebThreadLockFromAnyThread(bool shouldLog);
66static void _WebThreadUnlock(void);
67
68@interface NSObject(ForwardDeclarations)
69-(void)_webcore_releaseOnWebThread;
70-(void)_webcore_releaseWithWebThreadLock;
71@end
72
73@implementation NSObject(WebCoreThreadAdditions)
74
75- (void)releaseOnMainThread {
76    if ([NSThread isMainThread]) {
77        [self release];
78    } else {
79        [self performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
80    }
81}
82
83@end
84
85typedef void *NSAutoreleasePoolMark;
86#ifdef __cplusplus
87extern "C" {
88#endif
89extern NSAutoreleasePoolMark NSPushAutoreleasePool(unsigned ignored);
90extern void NSPopAutoreleasePool(NSAutoreleasePoolMark token);
91#ifdef __cplusplus
92}
93#endif
94
95static int WebTimedConditionLock (pthread_cond_t *condition, pthread_mutex_t *lock, CFAbsoluteTime interval);
96
97static pthread_mutex_t webLock;
98static NSAutoreleasePoolMark autoreleasePoolMark;
99static CFRunLoopRef webThreadRunLoop;
100static NSRunLoop* webThreadNSRunLoop;
101static pthread_t webThread;
102static BOOL isWebThreadLocked;
103static BOOL webThreadStarted;
104static unsigned webThreadLockCount;
105
106static NSAutoreleasePoolMark savedAutoreleasePoolMark;
107static BOOL isNestedWebThreadRunLoop;
108typedef enum {
109    PushOrPopAutoreleasePool,
110    IgnoreAutoreleasePool
111} AutoreleasePoolOperation;
112
113static pthread_mutex_t WebThreadReleaseLock = PTHREAD_MUTEX_INITIALIZER;
114static CFRunLoopSourceRef WebThreadReleaseSource;
115static CFMutableArrayRef WebThreadReleaseObjArray;
116
117static void MainThreadAdoptAndRelease(id obj);
118
119static pthread_mutex_t delegateLock = PTHREAD_MUTEX_INITIALIZER;
120static pthread_cond_t delegateCondition = PTHREAD_COND_INITIALIZER;
121static NSInvocation *delegateInvocation;
122static CFRunLoopSourceRef delegateSource = NULL;
123static BOOL delegateHandled;
124#if LOG_MAIN_THREAD_LOCKING
125static BOOL sendingDelegateMessage;
126#endif
127
128static CFRunLoopObserverRef mainRunLoopAutoUnlockObserver;
129
130static pthread_mutex_t startupLock = PTHREAD_MUTEX_INITIALIZER;
131static pthread_cond_t startupCondition = PTHREAD_COND_INITIALIZER;
132
133static WebThreadContext *webThreadContext;
134static pthread_key_t threadContextKey;
135static unsigned mainThreadLockCount;
136static unsigned otherThreadLockCount;
137static unsigned sMainThreadModalCount;
138
139volatile bool webThreadShouldYield;
140
141static pthread_mutex_t WebCoreReleaseLock;
142static void WebCoreObjCDeallocOnWebThreadImpl(id self, SEL _cmd);
143static void WebCoreObjCDeallocWithWebThreadLockImpl(id self, SEL _cmd);
144
145static NSMutableArray *sAsyncDelegates = nil;
146
147static CFStringRef delegateSourceRunLoopMode;
148
149static inline void SendMessage(NSInvocation *invocation)
150{
151    [invocation invoke];
152    MainThreadAdoptAndRelease(invocation);
153}
154
155static void HandleDelegateSource(void *info)
156{
157    UNUSED_PARAM(info);
158    ASSERT(!WebThreadIsCurrent());
159
160#if LOG_MAIN_THREAD_LOCKING
161    sendingDelegateMessage = YES;
162#endif
163
164    _WebThreadAutoLock();
165
166    int result = pthread_mutex_lock(&delegateLock);
167    ASSERT_WITH_MESSAGE(result == 0, "delegate lock failed with code:%d", result);
168
169#if LOG_MESSAGES
170    if ([[delegateInvocation target] isKindOfClass:[NSNotificationCenter class]]) {
171        id argument0;
172        [delegateInvocation getArgument:&argument0 atIndex:0];
173        NSLog(@"notification receive: %@", argument0);
174    } else {
175        NSLog(@"delegate receive: %@", NSStringFromSelector([delegateInvocation selector]));
176    }
177#endif
178
179    SendMessage(delegateInvocation);
180
181    delegateHandled = YES;
182    pthread_cond_signal(&delegateCondition);
183
184    result = pthread_mutex_unlock(&delegateLock);
185    ASSERT_WITH_MESSAGE(result == 0, "delegate unlock failed with code:%d", result);
186
187#if LOG_MAIN_THREAD_LOCKING
188    sendingDelegateMessage = NO;
189#endif
190}
191
192static void SendDelegateMessage(NSInvocation *invocation)
193{
194    if (WebThreadIsCurrent()) {
195        ASSERT(delegateSource);
196        int result = pthread_mutex_lock(&delegateLock);
197        ASSERT_WITH_MESSAGE(result == 0, "delegate lock failed with code:%d", result);
198
199        delegateInvocation = invocation;
200        delegateHandled = NO;
201
202#if LOG_MESSAGES
203        if ([[delegateInvocation target] isKindOfClass:[NSNotificationCenter class]]) {
204            id argument0;
205            [delegateInvocation getArgument:&argument0 atIndex:0];
206            NSLog(@"notification send: %@", argument0);
207        } else {
208            NSLog(@"delegate send: %@", NSStringFromSelector([delegateInvocation selector]));
209        }
210#endif
211
212        {
213            // Code block created to scope JSC::JSLock::DropAllLocks outside of WebThreadLock()
214            JSC::JSLock::DropAllLocks dropAllLocks(WebCore::JSDOMWindowBase::commonVM());
215            _WebThreadUnlock();
216
217            CFRunLoopSourceSignal(delegateSource);
218            CFRunLoopWakeUp(CFRunLoopGetMain());
219
220            while (!delegateHandled) {
221                if (WebTimedConditionLock(&delegateCondition, &delegateLock, DelegateWaitInterval) != 0) {
222                    id delegateInformation;
223                    if ([[delegateInvocation target] isKindOfClass:[NSNotificationCenter class]])
224                        [delegateInvocation getArgument:&delegateInformation atIndex:0];
225                    else
226                        delegateInformation = NSStringFromSelector([delegateInvocation selector]);
227
228                    CFStringRef mode = CFRunLoopCopyCurrentMode(CFRunLoopGetMain());
229                    NSLog(@"%s: delegate (%@) failed to return after waiting %d seconds. main run loop mode: %@", __PRETTY_FUNCTION__, delegateInformation, DelegateWaitInterval, mode);
230                    if (mode)
231                        CFRelease(mode);
232                }
233            }
234            result = pthread_mutex_unlock(&delegateLock);
235
236            ASSERT_WITH_MESSAGE(result == 0, "delegate unlock failed with code:%d", result);
237            _WebTryThreadLock(false);
238        }
239    } else {
240        SendMessage(invocation);
241    }
242}
243
244void WebThreadRunOnMainThread(void(^delegateBlock)())
245{
246    if (!WebThreadIsCurrent()) {
247        ASSERT(pthread_main_np());
248        delegateBlock();
249        return;
250    }
251
252    JSC::JSLock::DropAllLocks dropAllLocks(WebCore::JSDOMWindowBase::commonVM());
253    _WebThreadUnlock();
254
255    void (^delegateBlockCopy)() = Block_copy(delegateBlock);
256    dispatch_sync(dispatch_get_main_queue(), delegateBlockCopy);
257    Block_release(delegateBlockCopy);
258
259    _WebTryThreadLock(false);
260}
261
262static void MainThreadAdoptAndRelease(id obj)
263{
264    if (!WebThreadIsEnabled() || CFRunLoopGetMain() == CFRunLoopGetCurrent()) {
265        [obj release];
266        return;
267    }
268#if LOG_RELEASES
269    NSLog(@"Release send [web thread] : %@", obj);
270#endif
271    // We own obj at this point, so we don't need the block to implicitly
272    // retain it.
273    __block id objNotRetained = obj;
274    dispatch_async(dispatch_get_main_queue(), ^{
275        [objNotRetained release];
276    });
277}
278
279void WebThreadAdoptAndRelease(id obj)
280{
281    ASSERT(!WebThreadIsCurrent());
282    ASSERT(WebThreadReleaseSource);
283
284#if LOG_RELEASES
285    NSLog(@"Release send [main thread]: %@", obj);
286#endif
287
288    int result = pthread_mutex_lock(&WebThreadReleaseLock);
289    ASSERT_WITH_MESSAGE(result == 0, "Release lock failed with code:%d", result);
290
291    if (WebThreadReleaseObjArray == nil)
292        WebThreadReleaseObjArray = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, NULL);
293    CFArrayAppendValue(WebThreadReleaseObjArray, obj);
294    CFRunLoopSourceSignal(WebThreadReleaseSource);
295    CFRunLoopWakeUp(webThreadRunLoop);
296
297    result = pthread_mutex_unlock(&WebThreadReleaseLock);
298    ASSERT_WITH_MESSAGE(result == 0, "Release unlock failed with code:%d", result);
299}
300
301static inline void lockWebCoreReleaseLock()
302{
303    int lockcode = pthread_mutex_lock(&WebCoreReleaseLock);
304#pragma unused (lockcode)
305    ASSERT_WITH_MESSAGE(lockcode == 0, "WebCoreReleaseLock lock failed with code:%d", lockcode);
306}
307
308static inline void unlockWebCoreReleaseLock()
309{
310    int lockcode = pthread_mutex_unlock(&WebCoreReleaseLock);
311#pragma unused (lockcode)
312    ASSERT_WITH_MESSAGE(lockcode == 0, "WebCoreReleaseLock unlock failed with code:%d", lockcode);
313}
314
315void WebCoreObjCDeallocOnWebThread(Class cls)
316{
317    SEL releaseSEL = @selector(release);
318    SEL webThreadReleaseSEL = @selector(_webcore_releaseOnWebThread);
319
320    // get the existing release method
321    Method releaseMethod = class_getInstanceMethod(cls, releaseSEL);
322    if (!releaseMethod) {
323        ASSERT_WITH_MESSAGE(releaseMethod, "WebCoreObjCDeallocOnWebThread() failed to find %s for %@", releaseSEL, NSStringFromClass(cls));
324        return;
325    }
326
327    // add the implementation that ensures release WebThread release/deallocation
328    if (!class_addMethod(cls, webThreadReleaseSEL, (IMP)WebCoreObjCDeallocOnWebThreadImpl, method_getTypeEncoding(releaseMethod))) {
329        ASSERT_WITH_MESSAGE(releaseMethod, "WebCoreObjCDeallocOnWebThread() failed to add %s for %@", webThreadReleaseSEL, NSStringFromClass(cls));
330        return;
331    }
332
333    // ensure the implementation exists at cls in the class hierarchy
334    if (class_addMethod(cls, releaseSEL, class_getMethodImplementation(cls, releaseSEL), method_getTypeEncoding(releaseMethod)))
335        releaseMethod = class_getInstanceMethod(cls, releaseSEL);
336
337    // swizzle the old release for the new implementation
338    method_exchangeImplementations(releaseMethod, class_getInstanceMethod(cls, webThreadReleaseSEL));
339}
340
341void WebCoreObjCDeallocWithWebThreadLock(Class cls)
342{
343    SEL releaseSEL = @selector(release);
344    SEL webThreadLockReleaseSEL = @selector(_webcore_releaseWithWebThreadLock);
345
346    // get the existing release method
347    Method releaseMethod = class_getInstanceMethod(cls, releaseSEL);
348    if (!releaseMethod) {
349        ASSERT_WITH_MESSAGE(releaseMethod, "WebCoreObjCDeallocWithWebThreadLock() failed to find %s for %@", releaseSEL, NSStringFromClass(cls));
350        return;
351    }
352
353    // add the implementation that ensures release WebThreadLock release/deallocation
354    if (!class_addMethod(cls, webThreadLockReleaseSEL, (IMP)WebCoreObjCDeallocWithWebThreadLockImpl, method_getTypeEncoding(releaseMethod))) {
355        ASSERT_WITH_MESSAGE(releaseMethod, "WebCoreObjCDeallocWithWebThreadLock() failed to add %s for %@", webThreadLockReleaseSEL, NSStringFromClass(cls));
356        return;
357    }
358
359    // ensure the implementation exists at cls in the class hierarchy
360    if (class_addMethod(cls, releaseSEL, class_getMethodImplementation(cls, releaseSEL), method_getTypeEncoding(releaseMethod)))
361        releaseMethod = class_getInstanceMethod(cls, releaseSEL);
362
363    // swizzle the old release for the new implementation
364    method_exchangeImplementations(releaseMethod, class_getInstanceMethod(cls, webThreadLockReleaseSEL));
365}
366
367void WebCoreObjCDeallocOnWebThreadImpl(id self, SEL _cmd)
368{
369    UNUSED_PARAM(_cmd);
370    if (!WebThreadIsEnabled())
371        [self _webcore_releaseOnWebThread];
372    else {
373        lockWebCoreReleaseLock();
374        if ([self retainCount] == 1) {
375            // This is the only reference retaining the object, so we can
376            // safely release the WebCoreReleaseLock now.
377            unlockWebCoreReleaseLock();
378            if (WebThreadIsCurrent())
379                [self _webcore_releaseOnWebThread];
380            else
381                WebThreadAdoptAndRelease(self);
382        } else {
383            // This is not the only reference retaining the object, so another
384            // thread could also call release - hold the lock whilst calling
385            // release to avoid a race condition.
386            [self _webcore_releaseOnWebThread];
387            unlockWebCoreReleaseLock();
388        }
389    }
390}
391
392void WebCoreObjCDeallocWithWebThreadLockImpl(id self, SEL _cmd)
393{
394    UNUSED_PARAM(_cmd);
395    lockWebCoreReleaseLock();
396    if (WebThreadIsLockedOrDisabled() || 1 != [self retainCount])
397        [self _webcore_releaseWithWebThreadLock];
398    else
399        WebThreadAdoptAndRelease(self);
400    unlockWebCoreReleaseLock();
401}
402
403static void HandleWebThreadReleaseSource(void *info)
404{
405    UNUSED_PARAM(info);
406    ASSERT(WebThreadIsCurrent());
407
408    int result = pthread_mutex_lock(&WebThreadReleaseLock);
409    ASSERT_WITH_MESSAGE(result == 0, "Release lock failed with code:%d", result);
410
411    CFMutableArrayRef objects = NULL;
412    if (CFArrayGetCount(WebThreadReleaseObjArray)) {
413        objects = CFArrayCreateMutableCopy(NULL, 0, WebThreadReleaseObjArray);
414        CFArrayRemoveAllValues(WebThreadReleaseObjArray);
415    }
416
417    result = pthread_mutex_unlock(&WebThreadReleaseLock);
418    ASSERT_WITH_MESSAGE(result == 0, "Release unlock failed with code:%d", result);
419
420    if (!objects)
421        return;
422
423    unsigned count = CFArrayGetCount(objects);
424    unsigned i;
425    for (i = 0; i < count; i++) {
426        id obj = (id)CFArrayGetValueAtIndex(objects, i);
427#if LOG_RELEASES
428        NSLog(@"Release recv [web thread] : %@", obj);
429#endif
430        [obj release];
431    }
432
433    CFRelease(objects);
434}
435
436void WebThreadCallDelegate(NSInvocation *invocation)
437{
438    // NSInvocation released in SendMessage()
439    SendDelegateMessage([invocation retain]);
440}
441
442void WebThreadPostNotification(NSString *name, id object, id userInfo)
443{
444    if (pthread_main_np())
445        [[NSNotificationCenter defaultCenter] postNotificationName:name object:object userInfo:userInfo];
446    else {
447        dispatch_async(dispatch_get_main_queue(), ^ {
448            [[NSNotificationCenter defaultCenter] postNotificationName:name object:object userInfo:userInfo];
449        });
450    }
451}
452
453void WebThreadCallDelegateAsync(NSInvocation *invocation)
454{
455    ASSERT(invocation);
456    if (WebThreadIsCurrent())
457        [sAsyncDelegates addObject:invocation];
458    else
459        WebThreadCallDelegate(invocation);
460}
461
462// Note: despite the name, returns an autoreleased object.
463NSInvocation *WebThreadMakeNSInvocation(id target, SEL selector)
464{
465    NSMethodSignature *signature = [target methodSignatureForSelector:selector];
466    ASSERT(signature);
467    if (signature) {
468        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
469        [invocation setSelector:selector];
470        [invocation setTarget:target];
471        [invocation retainArguments];
472        return invocation;
473    }
474    return nil;
475}
476
477static void MainRunLoopAutoUnlock(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *context)
478{
479    UNUSED_PARAM(observer);
480    UNUSED_PARAM(activity);
481    UNUSED_PARAM(context);
482    ASSERT(!WebThreadIsCurrent());
483
484    if (sMainThreadModalCount != 0)
485        return;
486
487    CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), mainRunLoopAutoUnlockObserver, kCFRunLoopCommonModes);
488
489    _WebThreadUnlock();
490}
491
492static void _WebThreadAutoLock(void)
493{
494    ASSERT(!WebThreadIsCurrent());
495
496    if (mainThreadLockCount == 0) {
497        CFRunLoopAddObserver(CFRunLoopGetCurrent(), mainRunLoopAutoUnlockObserver, kCFRunLoopCommonModes);
498        _WebTryThreadLock(false);
499        CFRunLoopWakeUp(CFRunLoopGetMain());
500    }
501}
502
503static void WebRunLoopLockInternal(AutoreleasePoolOperation poolOperation)
504{
505    _WebTryThreadLock(false);
506    if (poolOperation == PushOrPopAutoreleasePool)
507        autoreleasePoolMark = NSPushAutoreleasePool(0);
508    isWebThreadLocked = YES;
509}
510
511static void WebRunLoopUnlockInternal(AutoreleasePoolOperation poolOperation)
512{
513    ASSERT(sAsyncDelegates);
514    if ([sAsyncDelegates count]) {
515        for (NSInvocation* invocation in sAsyncDelegates)
516            SendDelegateMessage([invocation retain]);
517        [sAsyncDelegates removeAllObjects];
518    }
519
520    if (poolOperation == PushOrPopAutoreleasePool)
521        NSPopAutoreleasePool(autoreleasePoolMark);
522
523    _WebThreadUnlock();
524    isWebThreadLocked = NO;
525}
526
527static void WebRunLoopLock(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *context)
528{
529    UNUSED_PARAM(observer);
530    UNUSED_PARAM(context);
531    ASSERT(WebThreadIsCurrent());
532    ASSERT_UNUSED(activity, activity == kCFRunLoopAfterWaiting || activity == kCFRunLoopBeforeTimers || activity == kCFRunLoopBeforeSources);
533
534    // If the WebThread is locked by the main thread then we want to
535    // grab the lock ourselves when the main thread releases the lock.
536    if (isWebThreadLocked && !mainThreadLockCount)
537        return;
538    WebRunLoopLockInternal(PushOrPopAutoreleasePool);
539}
540
541static void WebRunLoopUnlock(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *context)
542{
543    UNUSED_PARAM(observer);
544    UNUSED_PARAM(context);
545    ASSERT(WebThreadIsCurrent());
546    ASSERT_UNUSED(activity, activity == kCFRunLoopBeforeWaiting || activity == kCFRunLoopExit);
547    ASSERT(!mainThreadLockCount);
548
549    if (!isWebThreadLocked)
550        return;
551    WebRunLoopUnlockInternal(PushOrPopAutoreleasePool);
552}
553
554static void MainRunLoopUnlockGuard(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *context)
555{
556    UNUSED_PARAM(observer);
557    UNUSED_PARAM(activity);
558    UNUSED_PARAM(context);
559    ASSERT(!WebThreadIsCurrent());
560
561    // We shouldn't have the web lock at this point.  However, MobileMail sometimes
562    // get to a state where the main thread has web lock but it didn't release it on last
563    // runloop exit, and web thread gets stuck at waiting for the lock. If this happens,
564    // we need to help release the lock.  See <rdar://problem/8005192>.
565    if (mainThreadLockCount != 0 && sMainThreadModalCount == 0) {
566        NSLog(@"WARNING: Main thread didn't release the lock at last runloop exit!");
567
568        MainRunLoopAutoUnlock(observer, activity, context);
569        if (mainThreadLockCount != 0)
570            mainThreadLockCount = 0;
571    }
572}
573
574static void _WebRunLoopEnableNestedFromMainThread()
575{
576    CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), mainRunLoopAutoUnlockObserver, kCFRunLoopCommonModes);
577}
578
579static void _WebRunLoopDisableNestedFromMainThread()
580{
581    CFRunLoopAddObserver(CFRunLoopGetCurrent(), mainRunLoopAutoUnlockObserver, kCFRunLoopCommonModes);
582}
583
584void WebRunLoopEnableNested()
585{
586    if (!WebThreadIsEnabled())
587        return;
588
589    ASSERT(!isNestedWebThreadRunLoop);
590
591    if (!WebThreadIsCurrent())
592        _WebRunLoopEnableNestedFromMainThread();
593
594    savedAutoreleasePoolMark = autoreleasePoolMark;
595    autoreleasePoolMark = 0;
596    WebRunLoopUnlockInternal(IgnoreAutoreleasePool);
597    isNestedWebThreadRunLoop = YES;
598}
599
600void WebRunLoopDisableNested()
601{
602    if (!WebThreadIsEnabled())
603        return;
604
605    ASSERT(isNestedWebThreadRunLoop);
606
607    if (!WebThreadIsCurrent())
608        _WebRunLoopDisableNestedFromMainThread();
609
610    autoreleasePoolMark = savedAutoreleasePoolMark;
611    savedAutoreleasePoolMark = 0;
612    WebRunLoopLockInternal(IgnoreAutoreleasePool);
613    isNestedWebThreadRunLoop = NO;
614}
615
616static void FreeThreadContext(void *threadContext)
617{
618    if (threadContext != NULL)
619       free(threadContext);
620}
621
622static void InitThreadContextKey()
623{
624    pthread_key_create(&threadContextKey, FreeThreadContext);
625}
626
627static WebThreadContext *CurrentThreadContext(void)
628{
629    static pthread_once_t initControl = PTHREAD_ONCE_INIT;
630    pthread_once(&initControl, InitThreadContextKey);
631
632    WebThreadContext *threadContext = (WebThreadContext*)pthread_getspecific(threadContextKey);
633    if (threadContext == NULL) {
634        threadContext = (WebThreadContext *)calloc(sizeof(WebThreadContext), 1);
635        pthread_setspecific(threadContextKey, threadContext);
636    }
637    return threadContext;
638}
639
640static
641#ifndef __llvm__
642NO_RETURN
643#endif
644void *RunWebThread(void *arg)
645{
646    FloatingPointEnvironment::shared().propagateMainThreadEnvironment();
647
648    UNUSED_PARAM(arg);
649    // WTF::initializeMainThread() needs to be called before JSC::initializeThreading() since the
650    // code invoked by the latter needs to know if it's running on the WebThread. See
651    // <rdar://problem/8502487>.
652    WTF::initializeMainThread();
653    WTF::initializeWebThread();
654    JSC::initializeThreading();
655
656    // Make sure that the WebThread and the main thread share the same ThreadGlobalData objects.
657    WebCore::threadGlobalData().setWebCoreThreadData();
658    initializeWebThreadIdentifier();
659
660#if HAVE(PTHREAD_SETNAME_NP)
661    pthread_setname_np("WebThread");
662#endif
663
664    webThreadContext = CurrentThreadContext();
665    webThreadRunLoop = CFRunLoopGetCurrent();
666    webThreadNSRunLoop = [[NSRunLoop currentRunLoop] retain];
667
668    CFRunLoopObserverRef webRunLoopLockObserverRef = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeTimers|kCFRunLoopBeforeSources|kCFRunLoopAfterWaiting, YES, 0, WebRunLoopLock, NULL);
669    CFRunLoopAddObserver(webThreadRunLoop, webRunLoopLockObserverRef, kCFRunLoopCommonModes);
670    CFRelease(webRunLoopLockObserverRef);
671
672    WebThreadInitRunQueue();
673
674    // 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.
675    CFRunLoopObserverRef webRunLoopUnlockObserverRef = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting|kCFRunLoopExit, YES, 2500000, WebRunLoopUnlock, NULL);
676    CFRunLoopAddObserver(webThreadRunLoop, webRunLoopUnlockObserverRef, kCFRunLoopCommonModes);
677    CFRelease(webRunLoopUnlockObserverRef);
678
679    CFRunLoopSourceContext ReleaseSourceContext = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, HandleWebThreadReleaseSource};
680    WebThreadReleaseSource = CFRunLoopSourceCreate(NULL, -1, &ReleaseSourceContext);
681    CFRunLoopAddSource(webThreadRunLoop, WebThreadReleaseSource, kCFRunLoopDefaultMode);
682
683    int result = pthread_mutex_lock(&startupLock);
684    ASSERT_WITH_MESSAGE(result == 0, "startup lock failed with code:%d", result);
685
686    result = pthread_cond_signal(&startupCondition);
687    ASSERT_WITH_MESSAGE(result == 0, "startup signal failed with code:%d", result);
688
689    result = pthread_mutex_unlock(&startupLock);
690    ASSERT_WITH_MESSAGE(result == 0, "startup unlock failed with code:%d", result);
691
692    while (1)
693        CFRunLoopRunInMode(kCFRunLoopDefaultMode, DistantFuture, true);
694
695#ifdef __llvm__
696    return NULL;
697#endif
698}
699
700static void StartWebThread()
701{
702    webThreadStarted = TRUE;
703
704    // Initialize ThreadGlobalData on the main UI thread so that the WebCore thread
705    // can later set it's thread-specific data to point to the same objects.
706    WebCore::ThreadGlobalData& unused = WebCore::threadGlobalData();
707    (void)unused;
708
709    // Initialize AtomicString on the main thread.
710    WTF::AtomicString::init();
711
712    // register class for WebThread deallocation
713    WebCoreObjCDeallocOnWebThread([DOMObject class]);
714    WebCoreObjCDeallocOnWebThread([WAKWindow class]);
715    WebCoreObjCDeallocWithWebThreadLock([WAKView class]);
716
717    pthread_mutexattr_t mattr;
718    pthread_mutexattr_init(&mattr);
719    pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
720    pthread_mutex_init(&webLock, &mattr);
721    pthread_mutexattr_destroy(&mattr);
722
723    pthread_mutexattr_t mutex_attr;
724    pthread_mutexattr_init(&mutex_attr);
725    pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
726    pthread_mutex_init(&WebCoreReleaseLock, &mutex_attr);
727    pthread_mutexattr_destroy(&mutex_attr);
728
729    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
730    CFRunLoopSourceContext delegateSourceContext = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, HandleDelegateSource};
731    delegateSource = CFRunLoopSourceCreate(NULL, 0, &delegateSourceContext);
732    // We shouldn't get delegate callbacks while scrolling, but there might be
733    // one outstanding when we start.  Add the source for all common run loop
734    // modes so we don't block the web thread while scrolling.
735    if (!delegateSourceRunLoopMode)
736        delegateSourceRunLoopMode = kCFRunLoopCommonModes;
737    CFRunLoopAddSource(runLoop, delegateSource, delegateSourceRunLoopMode);
738
739    sAsyncDelegates = [[NSMutableArray alloc] init];
740
741    mainRunLoopAutoUnlockObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting | kCFRunLoopExit, YES, 3000001, MainRunLoopAutoUnlock, NULL);
742
743    pthread_attr_t tattr;
744    pthread_attr_init(&tattr);
745    pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM);
746    // The web thread is a secondary thread, and secondary threads are usually given
747    // a 512 kb stack, but we need more space in order to have room for the JavaScriptCore
748    // reentrancy limit. This limit works on both the simulator and the device.
749    pthread_attr_setstacksize(&tattr, 200 * 4096);
750
751    struct sched_param param;
752    pthread_attr_getschedparam(&tattr, &param);
753    param.sched_priority--;
754    pthread_attr_setschedparam(&tattr, &param);
755
756    // Wait for the web thread to startup completely before we continue.
757    int result = pthread_mutex_lock(&startupLock);
758    ASSERT_WITH_MESSAGE(result == 0, "startup lock failed with code:%d", result);
759
760    // Propagate the mainThread's fenv to workers & the web thread.
761    FloatingPointEnvironment::shared().saveMainThreadEnvironment();
762
763    pthread_create(&webThread, &tattr, RunWebThread, NULL);
764    pthread_attr_destroy(&tattr);
765
766    result = pthread_cond_wait(&startupCondition, &startupLock);
767    ASSERT_WITH_MESSAGE(result == 0, "startup wait failed with code:%d", result);
768
769    result = pthread_mutex_unlock(&startupLock);
770    ASSERT_WITH_MESSAGE(result == 0, "startup unlock failed with code:%d", result);
771
772    initializeApplicationUIThreadIdentifier();
773}
774
775static int WebTimedConditionLock (pthread_cond_t *condition, pthread_mutex_t *lock, CFAbsoluteTime interval)
776{
777    struct timespec time;
778    CFAbsoluteTime at = CFAbsoluteTimeGetCurrent() + interval;
779    time.tv_sec = (time_t)(floor(at) + kCFAbsoluteTimeIntervalSince1970);
780    time.tv_nsec = (int32_t)((at - floor(at)) * 1000000000.0);
781    return pthread_cond_timedwait(condition, lock, &time);
782}
783
784
785#if LOG_WEB_LOCK || LOG_MAIN_THREAD_LOCKING
786static unsigned lockCount;
787#endif
788
789static bool _WebTryThreadLock(bool shouldTry)
790{
791    // Suspend the web thread if the main thread is trying to lock.
792    bool onMainThread = pthread_main_np();
793    if (onMainThread)
794        webThreadShouldYield = true;
795    else if (!WebThreadIsCurrent()) {
796        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());
797        CRASH();
798    }
799
800
801    int result;
802    bool busy = false;
803    if (shouldTry) {
804        result = pthread_mutex_trylock(&webLock);
805        if (result == EBUSY) {
806            busy = true;
807        } else
808            ASSERT_WITH_MESSAGE(result == 0, "try web lock failed with code:%d", result);
809    }
810    else {
811        result = pthread_mutex_lock(&webLock);
812        ASSERT_WITH_MESSAGE(result == 0, "web lock failed with code:%d", result);
813    }
814
815    if (!busy) {
816#if LOG_WEB_LOCK || LOG_MAIN_THREAD_LOCKING
817        lockCount++;
818#if LOG_WEB_LOCK
819        NSLog(@"lock   %d, web-thread: %d", lockCount, WebThreadIsCurrent());
820#endif
821#endif
822        if (onMainThread) {
823            ASSERT(CFRunLoopGetCurrent() == CFRunLoopGetMain());
824            webThreadShouldYield = false;
825            mainThreadLockCount++;
826#if LOG_MAIN_THREAD_LOCKING
827            if (!sendingDelegateMessage && lockCount == 1)
828                NSLog(@"Main thread locking outside of delegate messages.");
829#endif
830        } else {
831            webThreadLockCount++;
832            if (webThreadLockCount > 1) {
833                NSLog(@"%s, %p: Multiple locks on web thread not allowed! Please file a bug. Crashing now...", __PRETTY_FUNCTION__, CurrentThreadContext());
834                CRASH();
835            }
836        }
837    }
838
839    return !busy;
840}
841
842void WebThreadLock(void)
843{
844    if (!webThreadStarted || pthread_equal(webThread, pthread_self()))
845        return;
846    _WebThreadAutoLock();
847}
848
849void WebThreadUnlock(void)
850{
851    // This is a no-op, we unlock automatically on top of the runloop
852    ASSERT(!WebThreadIsCurrent());
853}
854
855void WebThreadLockFromAnyThread()
856{
857    _WebThreadLockFromAnyThread(true);
858}
859
860void WebThreadLockFromAnyThreadNoLog()
861{
862    _WebThreadLockFromAnyThread(false);
863}
864
865static void _WebThreadLockFromAnyThread(bool shouldLog)
866{
867    if (!webThreadStarted)
868        return;
869    ASSERT(!WebThreadIsCurrent());
870    if (pthread_main_np()) {
871        _WebThreadAutoLock();
872        return;
873    }
874    if (shouldLog)
875        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());
876
877    pthread_mutex_lock(&webLock);
878
879    // This used for any thread other than the web thread.
880    otherThreadLockCount++;
881    webThreadShouldYield = false;
882}
883
884void WebThreadUnlockFromAnyThread()
885{
886    if (!webThreadStarted)
887        return;
888    ASSERT(!WebThreadIsCurrent());
889    // No-op except from a secondary thread.
890    if (pthread_main_np())
891        return;
892
893    ASSERT(otherThreadLockCount);
894    otherThreadLockCount--;
895
896    int result;
897    result = pthread_mutex_unlock(&webLock);
898    ASSERT_WITH_MESSAGE(result == 0, "web unlock failed with code:%d", result);
899}
900
901void WebThreadUnlockGuardForMail()
902{
903    ASSERT(!WebThreadIsCurrent());
904
905    CFRunLoopObserverRef mainRunLoopUnlockGuardObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopEntry, YES, 0, MainRunLoopUnlockGuard, NULL);
906    CFRunLoopAddObserver(CFRunLoopGetMain(), mainRunLoopUnlockGuardObserver, kCFRunLoopCommonModes);
907    CFRelease(mainRunLoopUnlockGuardObserver);
908}
909
910void _WebThreadUnlock(void)
911{
912#if LOG_WEB_LOCK || LOG_MAIN_THREAD_LOCKING
913    lockCount--;
914#if LOG_WEB_LOCK
915    NSLog(@"unlock %d, web-thread: %d", lockCount, WebThreadIsCurrent());
916#endif
917#endif
918
919    if (!WebThreadIsCurrent()) {
920        ASSERT(mainThreadLockCount != 0);
921        mainThreadLockCount--;
922    } else {
923        webThreadLockCount--;
924        webThreadShouldYield = false;
925    }
926
927    int result;
928    result = pthread_mutex_unlock(&webLock);
929    ASSERT_WITH_MESSAGE(result == 0, "web unlock failed with code:%d", result);
930}
931
932bool WebThreadIsLocked(void)
933{
934    if (WebThreadIsCurrent())
935        return webThreadLockCount;
936    else if (pthread_main_np())
937        return mainThreadLockCount;
938    else
939        return otherThreadLockCount;
940}
941
942bool WebThreadIsLockedOrDisabled(void)
943{
944    return !WebThreadIsEnabled() || WebThreadIsLocked();
945}
946
947void WebThreadLockPushModal(void)
948{
949    if (WebThreadIsCurrent())
950        return;
951
952    ASSERT(WebThreadIsLocked());
953    ++sMainThreadModalCount;
954}
955
956void WebThreadLockPopModal(void)
957{
958    if (WebThreadIsCurrent())
959        return;
960
961    ASSERT(WebThreadIsLocked());
962    ASSERT(sMainThreadModalCount != 0);
963    --sMainThreadModalCount;
964}
965
966CFRunLoopRef WebThreadRunLoop(void)
967{
968    if (webThreadStarted) {
969        ASSERT(webThreadRunLoop);
970        return webThreadRunLoop;
971    }
972
973    return CFRunLoopGetCurrent();
974}
975
976NSRunLoop* WebThreadNSRunLoop(void)
977{
978    if (webThreadStarted) {
979        ASSERT(webThreadNSRunLoop);
980        return webThreadNSRunLoop;
981    }
982
983    return [NSRunLoop currentRunLoop];
984}
985
986WebThreadContext *WebThreadCurrentContext(void)
987{
988    return CurrentThreadContext();
989}
990
991bool WebThreadContextIsCurrent(void)
992{
993    return WebThreadCurrentContext() == webThreadContext;
994}
995
996void WebThreadSetDelegateSourceRunLoopMode(CFStringRef mode)
997{
998    ASSERT(!webThreadStarted);
999    delegateSourceRunLoopMode = mode;
1000}
1001
1002void WebThreadEnable(void)
1003{
1004    RELEASE_ASSERT_WITH_MESSAGE(!WebCore::applicationIsWebProcess(), "The WebProcess should never run a Web Thread");
1005
1006    static pthread_once_t initControl = PTHREAD_ONCE_INIT;
1007    pthread_once(&initControl, StartWebThread);
1008}
1009
1010bool WebThreadIsEnabled(void)
1011{
1012    return webThreadStarted;
1013}
1014
1015bool WebThreadIsCurrent(void)
1016{
1017    return webThreadStarted && pthread_equal(webThread, pthread_self());
1018}
1019
1020bool WebThreadNotCurrent(void)
1021{
1022    return webThreadStarted && !pthread_equal(webThread, pthread_self());
1023}
1024
1025#endif // PLATFORM(IOS)
1026