This documentation is for all developers who are planning to hack on the Mac OS X native Cocoa/AWT.
If you are working on a native function or method, please take a moment to determine which of the following behaviors describe it:
1) Only called on AppKit thread
Use AWT_ASSERT_APPKIT_THREAD; (defined in ThreadUtilities.h)
2) Only NOT called on AppKit thread
Use AWT_ASSERT_NOT_APPKIT_THREAD; (defined in ThreadUtilities.h)
3) Called on any thread
Make sure any shared data (instance variables or global data) is accessed only as an atomic property, or behind a lock that will not block for an indefinite period of time.
If all changes to the Cocoa/AWT native code take this into account, we will wind up having self-documenting & self-checking code! :-)
A wise sage who untangled the wily threads of Java and AppKit in the 1.3/Carbon to 1.4/Cocoa transition once said:
Java has it's own event dispatch thread (actually, it can have multiple event dispatch threads). AppKit has it's own event dispatch thread, and (virtually all) AppKit UI operations must take place on that "main" thread, while most (virtually all) Java UI operations must take place on Java's event thread.
When Java needs to affect "native" widgets, it calls through to native; this necessitates using performSelectorOnMainThread:; most of the time it expects that these calls are synchronous, so we need to set wait:YES. (ed. actually, almost all calls without return values can now be done without waiting)
When AppKit needs to ask Java for something, or it needs to perform some operation synchronously against the Java event thread, we do the moral equivalent to performSelectorOnMainThread, but against the Java event thread.
The "trick" in our case, is that when we block the AppKit thread against the Java EDT, we spin the AppKit runloop in a special runloopmode. Calls from Java -> AppKit using performSelectorOnMainThread are allowed to run in that special runloopmode.
So far, this is working "OK" (performance, Accessibility and Printing are the trickiest bits), but it means that we really aren't allowed to "share" locks between Java and AppKit or we get a classic deadlock between the shared lock and the AppKit thread.
The JavaNativeFoundation.framework's JNFRunLoop class was born out of the necessity for both app developers who use JNI as well as JDK hackers to efficiently perform blocks of code on the main AppKit thread, while running in this special mode. JNFRunLoop.h exposes both an Obj-C selector and blocks-based API for performing with and without waiting:
/System/Library/Frameworks/JavaVM.framework/Frameworks/JavaNativeFoundation.framework/Headers/JNFRunLoop.h
Native locks are evil
- Using pthread_lock() or NSLock will almost always get you in trouble
- These locks are not recursive like Java locks are
- Using @synchronized in ObjC is slow
- Since the lock storage is not in the object header itself (like it is in Java) a side-table must be consulted
- If you need to serialize execution of a task in native, use a serial GCD dispatch_queue
- They are quite cheap to use in the uncontended case
- Avoid doing any work that will call back up into Java while holding a native lock or performing on a serial queue
- It is very easy to deadlock with a Java semaphore and not realize it
- These deadlocks are almost impossible to debug
- It is very easy to deadlock with a Java semaphore and not realize it