1 /*
   2  * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #import <AppKit/AppKit.h>
  27 #import <JavaNativeFoundation/JavaNativeFoundation.h>
  28 #import <objc/message.h>
  29 
  30 #import "ThreadUtilities.h"
  31 
  32 
  33 // The following must be named "jvm", as there are extern references to it in AWT
  34 JavaVM *jvm = NULL;
  35 static JNIEnv *appKitEnv = NULL;
  36 
  37 static NSArray *sPerformModes = nil;
  38 static NSArray *sAWTPerformModes = nil;
  39 
  40 static BOOL sCocoaComponentCompatibility = NO;
  41 static NSTimeInterval sCocoaComponentCompatibilityTimeout = 0.5;
  42 static BOOL sLoggingEnabled = YES;
  43 
  44 #ifdef AWT_THREAD_ASSERTS_ENV_ASSERT
  45 int sAWTThreadAsserts = 0;
  46 #endif /* AWT_THREAD_ASSERTS_ENV_ASSERT */
  47 
  48 
  49 // This is for backward compatibility for those people using CocoaComponent
  50 // Since we've flipped the AWT threading model for Tiger (10.4), all the rules
  51 // for CocoaComponent are wrong.
  52 // So for existing CocoaComponent users, we can't be synchronous.
  53 // Making things totally asynchronous breaks a _lot_, so we try to be
  54 // synchronous and time out after a little bit.
  55 #define NOT_READY 0
  56 #define READY 1
  57 #define IN_PROGRESS 2
  58 
  59 BOOL sInPerformFromJava = NO;
  60 NSUInteger sPerformCount = 0;
  61 
  62 // This class is used so that performSelectorOnMainThread can be
  63 // controlled a little more easily by us.  It has 2 roles.
  64 // The first is to set/unset a flag (sInPerformFromJava) that code can
  65 // check to see if we are in a synchronous perform initiated by a java thread.
  66 // The second is to implement the CocoaComponent backward compatibility mode.
  67 @interface CPerformer : NSObject {
  68     id fTarget;
  69     SEL fSelector;
  70     id fArg;
  71     BOOL fWait;
  72 }
  73 
  74 - (id) initWithTarget:(id)target selector:(SEL)selector arg:(id)arg wait:(BOOL)wait;
  75 - (void) perform;
  76 - (void) performCompatible;
  77 - (void) _performCompatible:(NSConditionLock *)resultLock;
  78 @end
  79 
  80 
  81 @implementation CPerformer
  82 
  83 - (id) initWithTarget:(id)target selector:(SEL)selector arg:(id)arg {
  84     return [self initWithTarget:target selector:selector arg:arg wait:YES];
  85 }
  86 
  87 - (id) initWithTarget:(id)target selector:(SEL)selector arg:(id)arg wait:(BOOL)wait {
  88     self = [super init];
  89     if (self != nil) {
  90         fTarget = [target retain];
  91         fSelector = selector;
  92         fArg = [arg retain];
  93         // Only set sInPerformFromJava if this is a synchronous perform
  94         fWait = wait;
  95     }
  96     return self;
  97 }
  98 
  99 - (void) dealloc {
 100     [fTarget release];
 101     [fArg release];
 102     [super dealloc];
 103 }
 104 //- (void)finalize { [super finalize]; }
 105 
 106 - (void) perform {
 107     AWT_ASSERT_APPKIT_THREAD;
 108 
 109     // If this is the first time we're going from java thread -> appkit thread,
 110     // set sInPerformFromJava for the duration of the invocation
 111     BOOL nestedPerform = sInPerformFromJava;
 112     if (fWait) {
 113         sInPerformFromJava = YES;
 114     }
 115 
 116     sPerformCount++;
 117 
 118     // Actually do the work (cheat to avoid a method call)
 119     @try {
 120         objc_msgSend(fTarget, fSelector, fArg);
 121         //[fTarget performSelector:fSelector withObject:fArg];
 122     } @catch (NSException *e) {
 123         NSLog(@"*** CPerformer: ignoring exception '%@' raised during perform of selector '%@' on target '%@' with args '%@'", e, NSStringFromSelector(fSelector), fTarget, fArg);
 124     } @finally {
 125         // If we actually set sInPerformFromJava, unset it now
 126         if (!nestedPerform && fWait) {
 127             sInPerformFromJava = NO;
 128         }
 129     }
 130 }
 131 
 132 - (void) performCompatible {
 133     // We check if we are on the AppKit thread because frequently, apps
 134     // using CocoaComponent are doing things on the wrong thread!
 135     if (pthread_main_np()) {
 136         [fTarget performSelector:fSelector withObject:fArg];
 137     } else {
 138         // Setup the lock
 139         NSConditionLock *resultLock =
 140             [[NSConditionLock alloc] initWithCondition:NOT_READY];
 141 
 142         // Make sure that if we return early, nothing gets released out
 143         // from under us
 144         [resultLock retain];
 145         [fTarget retain];
 146         [fArg retain];
 147         [self retain];
 148         // Do an asynchronous perform to the main thread.
 149         [self performSelectorOnMainThread:@selector(_performCompatible:)
 150               withObject:resultLock waitUntilDone:NO modes:sAWTPerformModes];
 151 
 152         // Wait for a little bit for it to finish
 153         [resultLock lockWhenCondition:READY beforeDate:[NSDate dateWithTimeIntervalSinceNow:sCocoaComponentCompatibilityTimeout]];
 154 
 155         // If the _performCompatible is actually in progress,
 156         // we should let it finish
 157         if ([resultLock condition] == IN_PROGRESS) {
 158             [resultLock lockWhenCondition:READY];
 159         }
 160 
 161         if ([resultLock condition] == NOT_READY && sLoggingEnabled) {
 162             NSLog(@"[Java CocoaComponent compatibility mode]: Operation timed out due to possible deadlock: selector '%@' on target '%@' with args '%@'", NSStringFromSelector(fSelector), fTarget, fArg);
 163         }
 164 
 165         [resultLock unlock];
 166         [resultLock autorelease];
 167     }
 168 }
 169 
 170 - (void) _performCompatible:(NSConditionLock *)resultLock {
 171     // notify that the perform is in progress!
 172     [resultLock lock];
 173     [resultLock unlockWithCondition:IN_PROGRESS];
 174 
 175     sPerformCount++;
 176 
 177     // Actually do the work.
 178     @try {
 179         [fTarget performSelector:fSelector withObject:fArg];
 180     } @catch (NSException *e) {
 181         NSLog(@"*** CPerformer: ignoring exception '%@' raised during performCompatible of selector '%@' on target '%@' with args '%@'", e, NSStringFromSelector(fSelector), fTarget, fArg);
 182     } @finally {
 183         // notify done!
 184         [resultLock lock];
 185         [resultLock unlockWithCondition:READY];
 186 
 187         // Clean up after ourselves
 188         [resultLock autorelease];
 189         [fTarget autorelease];
 190         [fArg autorelease];
 191         [self autorelease];
 192     }
 193 }
 194 @end
 195 
 196 
 197 @implementation ThreadUtilities
 198 
 199 + (JNIEnv*)getJNIEnv {
 200 AWT_ASSERT_APPKIT_THREAD;
 201     if (appKitEnv == NULL) {
 202         (*jvm)->AttachCurrentThreadAsDaemon(jvm, (void **)&appKitEnv, NULL);
 203     }
 204     return appKitEnv;
 205 }
 206 
 207 + (JNIEnv*)getJNIEnvUncached {
 208     JNIEnv *env = NULL;
 209     (*jvm)->AttachCurrentThreadAsDaemon(jvm, (void **)&env, nil);
 210     return env;
 211 }
 212 
 213 + (void)initialize {
 214     // Headless: BOTH
 215     // Embedded: BOTH
 216     // Multiple Calls: NO
 217     // Caller: Obj-C class initialization
 218     // Thread: ?
 219 
 220     if (sPerformModes == nil) {
 221         // Create list of Run Loop modes to perform on
 222         // The default performSelector, with no mode argument, runs in Default,
 223         // ModalPanel, and EventTracking modes
 224         sPerformModes =    [[NSArray alloc] initWithObjects:NSDefaultRunLoopMode, NSModalPanelRunLoopMode, nil];
 225         sAWTPerformModes = [[NSArray alloc] initWithObjects:NSDefaultRunLoopMode, NSModalPanelRunLoopMode, NSEventTrackingRunLoopMode, [JNFRunLoop javaRunLoopMode], nil];
 226 
 227 #ifdef AWT_THREAD_ASSERTS_ENV_ASSERT
 228         sAWTThreadAsserts = (getenv("COCOA_AWT_DISABLE_THREAD_ASSERTS") == NULL);
 229 #endif /* AWT_THREAD_ASSERTS_ENV_ASSERT */
 230     }
 231 }
 232 
 233 // These methods can behave slightly differently than the normal
 234 // performSelector...  In particular, we define a special runloop mode
 235 // (AWTRunLoopMode) so that we can "block" the main thread against the
 236 // java event thread without deadlocking. See CToolkit.invokeAndWait.
 237 + (void)performOnMainThread:(SEL)aSelector onObject:(id)target withObject:(id)arg waitUntilDone:(BOOL)wait awtMode:(BOOL)inAWT {
 238     CPerformer *performer = [[CPerformer alloc] initWithTarget:target selector:aSelector arg:arg wait:wait];
 239     if (sCocoaComponentCompatibility && wait && inAWT) {
 240         [performer performCompatible];
 241         [performer autorelease];
 242     } else {
 243         [performer performSelectorOnMainThread:@selector(perform) withObject:nil waitUntilDone:wait modes:((inAWT) ? sAWTPerformModes : sPerformModes)]; // AWT_THREADING Safe (cover method)
 244         [performer release];
 245     }
 246 }
 247 
 248 @end
 249 
 250 
 251 void OSXAPP_SetJavaVM(JavaVM *vm)
 252 {
 253     jvm = vm;
 254 }
 255