]> git.saurik.com Git - apple/objc4.git/blobdiff - runtime/objc-initialize.m
objc4-371.tar.gz
[apple/objc4.git] / runtime / objc-initialize.m
diff --git a/runtime/objc-initialize.m b/runtime/objc-initialize.m
new file mode 100644 (file)
index 0000000..aede2e0
--- /dev/null
@@ -0,0 +1,356 @@
+/*
+ * Copyright (c) 1999-2007 Apple Inc.  All Rights Reserved.
+ * 
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+/***********************************************************************
+* objc-initialize.m
+* +initialize support
+**********************************************************************/
+
+/***********************************************************************
+ * Thread-safety during class initialization (GrP 2001-9-24)
+ *
+ * Initial state: CLS_INITIALIZING and CLS_INITIALIZED both clear. 
+ * During initialization: CLS_INITIALIZING is set
+ * After initialization: CLS_INITIALIZING clear and CLS_INITIALIZED set.
+ * CLS_INITIALIZING and CLS_INITIALIZED are never set at the same time.
+ * CLS_INITIALIZED is never cleared once set.
+ *
+ * Only one thread is allowed to actually initialize a class and send 
+ * +initialize. Enforced by allowing only one thread to set CLS_INITIALIZING.
+ *
+ * Additionally, threads trying to send messages to a class must wait for 
+ * +initialize to finish. During initialization of a class, that class's 
+ * method cache is kept empty. objc_msgSend will revert to 
+ * class_lookupMethodAndLoadCache, which checks CLS_INITIALIZED before 
+ * messaging. If CLS_INITIALIZED is clear but CLS_INITIALIZING is set, 
+ * the thread must block, unless it is the thread that started 
+ * initializing the class in the first place. 
+ *
+ * Each thread keeps a list of classes it's initializing. 
+ * The global classInitLock is used to synchronize changes to CLS_INITIALIZED 
+ * and CLS_INITIALIZING: the transition to CLS_INITIALIZING must be 
+ * an atomic test-and-set with respect to itself and the transition 
+ * to CLS_INITIALIZED.
+ * The global classInitWaitCond is used to block threads waiting for an 
+ * initialization to complete. The classInitLock synchronizes
+ * condition checking and the condition variable.
+ **********************************************************************/
+
+/***********************************************************************
+ *  +initialize deadlock case when a class is marked initializing while 
+ *  its superclass is initialized. Solved by completely initializing 
+ *  superclasses before beginning to initialize a class.
+ *
+ *  OmniWeb class hierarchy:
+ *                 OBObject 
+ *                     |    ` OBPostLoader
+ *                 OFObject
+ *                 /     \
+ *      OWAddressEntry  OWController
+ *                        | 
+ *                      OWConsoleController
+ *
+ *  Thread 1 (evil testing thread):
+ *    initialize OWAddressEntry
+ *    super init OFObject
+ *    super init OBObject                   
+ *    [OBObject initialize] runs OBPostLoader, which inits lots of classes...
+ *    initialize OWConsoleController
+ *    super init OWController - wait for Thread 2 to finish OWController init
+ *
+ *  Thread 2 (normal OmniWeb thread):
+ *    initialize OWController
+ *    super init OFObject - wait for Thread 1 to finish OFObject init
+ *
+ *  deadlock!
+ *
+ *  Solution: fully initialize super classes before beginning to initialize 
+ *  a subclass. Then the initializing+initialized part of the class hierarchy
+ *  will be a contiguous subtree starting at the root, so other threads 
+ *  can't jump into the middle between two initializing classes, and we won't 
+ *  get stuck while a superclass waits for its subclass which waits for the 
+ *  superclass.
+ **********************************************************************/
+
+#include <pthread.h>
+#include <assert.h>
+
+#import "objc-private.h"
+#import "objc-initialize.h"
+
+/* classInitLock protects classInitWaitCond and examination and modification 
+ * of CLS_INITIALIZED and CLS_INITIALIZING. */
+static OBJC_DECLARE_LOCK(classInitLock);
+
+/* classInitWaitCond is signalled when any class is done initializing. 
+ * Threads that are waiting for a class to finish initializing wait on this. */
+static pthread_cond_t classInitWaitCond = PTHREAD_COND_INITIALIZER;
+
+
+/***********************************************************************
+* struct _objc_initializing_classes
+* Per-thread list of classes currently being initialized by that thread. 
+* During initialization, that thread is allowed to send messages to that 
+* class, but other threads have to wait.
+* The list is a simple array of metaclasses (the metaclass stores 
+* the initialization state). 
+**********************************************************************/
+typedef struct _objc_initializing_classes {
+    int classesAllocated;
+    Class *metaclasses;
+} _objc_initializing_classes;
+
+
+/***********************************************************************
+* _fetchInitializingClassList
+* Return the list of classes being initialized by this thread.
+* If create == YES, create the list when no classes are being initialized by this thread.
+* If create == NO, return NULL when no classes are being initialized by this thread.
+**********************************************************************/
+static _objc_initializing_classes *_fetchInitializingClassList(BOOL create)
+{
+    _objc_pthread_data *data;
+    _objc_initializing_classes *list;
+    Class *classes;
+
+    data = _objc_fetch_pthread_data(create);
+    if (data == NULL  &&  !create) return NULL;
+
+    list = data->initializingClasses;
+    if (list == NULL) {
+        if (!create) {
+            return NULL;
+        } else {
+            list = _calloc_internal(1, sizeof(_objc_initializing_classes));
+            data->initializingClasses = list;
+        }
+    }
+
+    classes = list->metaclasses;
+    if (classes == NULL) {
+        // If _objc_initializing_classes exists, allocate metaclass array, 
+        // even if create == NO.
+        // Allow 4 simultaneous class inits on this thread before realloc.
+        list->classesAllocated = 4;
+        classes = _calloc_internal(list->classesAllocated, sizeof(Class));
+        list->metaclasses = classes;
+    }
+    return list;
+}
+
+
+/***********************************************************************
+* _destroyInitializingClassList
+* Deallocate memory used by the given initialization list. 
+* Any part of the list may be NULL.
+* Called from _objc_pthread_destroyspecific().
+**********************************************************************/
+__private_extern__ 
+void _destroyInitializingClassList(struct _objc_initializing_classes *list)
+{
+    if (list != NULL) {
+        if (list->metaclasses != NULL) {
+            _free_internal(list->metaclasses);
+        }
+        _free_internal(list);
+    }
+}
+
+
+/***********************************************************************
+* _thisThreadIsInitializingClass
+* Return TRUE if this thread is currently initializing the given class.
+**********************************************************************/
+static BOOL _thisThreadIsInitializingClass(Class cls)
+{
+    int i;
+
+    _objc_initializing_classes *list = _fetchInitializingClassList(NO);
+    if (list) {
+        cls = _class_getMeta(cls);
+        for (i = 0; i < list->classesAllocated; i++) {
+            if (cls == list->metaclasses[i]) return YES;
+        }
+    }
+
+    // no list or not found in list
+    return NO;
+}
+
+
+/***********************************************************************
+* _setThisThreadIsInitializingClass
+* Record that this thread is currently initializing the given class. 
+* This thread will be allowed to send messages to the class, but 
+*   other threads will have to wait.
+**********************************************************************/
+static void _setThisThreadIsInitializingClass(Class cls)
+{
+    int i;
+    _objc_initializing_classes *list = _fetchInitializingClassList(YES);
+    cls = _class_getMeta(cls);
+  
+    // paranoia: explicitly disallow duplicates
+    for (i = 0; i < list->classesAllocated; i++) {
+        if (cls == list->metaclasses[i]) {
+            _objc_fatal("thread is already initializing this class!");
+            return; // already the initializer
+        }
+    }
+  
+    for (i = 0; i < list->classesAllocated; i++) {
+        if (0   == list->metaclasses[i]) {
+            list->metaclasses[i] = cls;
+            return;
+        }
+    }
+
+    // class list is full - reallocate
+    list->classesAllocated = list->classesAllocated * 2 + 1;
+    list->metaclasses = _realloc_internal(list->metaclasses, list->classesAllocated * sizeof(Class));
+    // zero out the new entries
+    list->metaclasses[i++] = cls;
+    for ( ; i < list->classesAllocated; i++) {
+        list->metaclasses[i] = NULL;
+    }
+}
+
+
+/***********************************************************************
+* _setThisThreadIsNotInitializingClass
+* Record that this thread is no longer initializing the given class. 
+**********************************************************************/
+static void _setThisThreadIsNotInitializingClass(Class cls)
+{
+    int i;
+
+    _objc_initializing_classes *list = _fetchInitializingClassList(NO);
+    if (list) {
+        cls = _class_getMeta(cls);
+        for (i = 0; i < list->classesAllocated; i++) {
+            if (cls == list->metaclasses[i]) {
+                list->metaclasses[i] = NULL;
+                return;
+            }
+        }
+    }
+
+    // no list or not found in list
+    _objc_fatal("thread is not initializing this class!");  
+}
+
+
+/***********************************************************************
+* class_initialize.  Send the '+initialize' message on demand to any
+* uninitialized class. Force initialization of superclasses first.
+*
+* Called only from _class_lookupMethodAndLoadCache (or itself).
+**********************************************************************/
+__private_extern__ void _class_initialize(Class cls)
+{
+    Class supercls;
+    BOOL reallyInitialize = NO;
+
+    // Get the real class from the metaclass. The superclass chain 
+    // hangs off the real class only.
+    cls = _class_getNonMetaClass(cls);
+
+    // Make sure super is done initializing BEFORE beginning to initialize cls.
+    // See note about deadlock above.
+    supercls = _class_getSuperclass(cls);
+    if (supercls  &&  !_class_isInitialized(supercls)) {
+        _class_initialize(supercls);
+    }
+    
+    // Try to atomically set CLS_INITIALIZING.
+    OBJC_LOCK(&classInitLock);
+    if (!_class_isInitialized(cls) && !_class_isInitializing(cls)) {
+        _class_setInitializing(cls);
+        reallyInitialize = YES;
+    }
+    OBJC_UNLOCK(&classInitLock);
+    
+    if (reallyInitialize) {
+        // We successfully set the CLS_INITIALIZING bit. Initialize the class.
+        
+        // Record that we're initializing this class so we can message it.
+        _setThisThreadIsInitializingClass(cls);
+        
+        // Send the +initialize message.
+        // Note that +initialize is sent to the superclass (again) if 
+        // this class doesn't implement +initialize. 2157218
+        if (PrintInitializing) {
+            _objc_inform("INITIALIZE: calling +[%s initialize]",
+                         _class_getName(cls));
+        }
+        [(id)cls initialize];
+        
+        // propagate finalization affinity.
+        if (UseGC && supercls && _class_shouldFinalizeOnMainThread(supercls)) {
+            _class_setFinalizeOnMainThread(cls);
+        }
+        
+        // Done initializing. Update the info bits and notify waiting threads.
+        OBJC_LOCK(&classInitLock);
+        _class_setInitialized(cls);
+        pthread_cond_broadcast(&classInitWaitCond);
+        OBJC_UNLOCK(&classInitLock);
+        _setThisThreadIsNotInitializingClass(cls);
+        return;
+    }
+    
+    else if (_class_isInitializing(cls)) {
+        // We couldn't set INITIALIZING because INITIALIZING was already set.
+        // If this thread set it earlier, continue normally.
+        // If some other thread set it, block until initialize is done.
+        // It's ok if INITIALIZING changes to INITIALIZED while we're here, 
+        //   because we safely check for INITIALIZED inside the lock 
+        //   before blocking.
+        if (_thisThreadIsInitializingClass(cls)) {
+            return;
+        } else {
+            OBJC_LOCK(&classInitLock);
+            while (!_class_isInitialized(cls)) {
+                pthread_cond_wait(&classInitWaitCond, &classInitLock);
+            }
+            OBJC_UNLOCK(&classInitLock);
+            return;
+        }
+    }
+    
+    else if (_class_isInitialized(cls)) {
+        // Set CLS_INITIALIZING failed because someone else already 
+        //   initialized the class. Continue normally.
+        // NOTE this check must come AFTER the ISINITIALIZING case.
+        // Otherwise: Another thread is initializing this class. ISINITIALIZED 
+        //   is false. Skip this clause. Then the other thread finishes 
+        //   initialization and sets INITIALIZING=no and INITIALIZED=yes. 
+        //   Skip the ISINITIALIZING clause. Die horribly.
+        return;
+    }
+    
+    else {
+        // We shouldn't be here. 
+        _objc_fatal("thread-safe class init in objc runtime is buggy!");
+    }
+}