]> git.saurik.com Git - apple/objc4.git/blob - runtime/objc-initialize.m
objc4-437.tar.gz
[apple/objc4.git] / runtime / objc-initialize.m
1 /*
2 * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /***********************************************************************
25 * objc-initialize.m
26 * +initialize support
27 **********************************************************************/
28
29 /***********************************************************************
30 * Thread-safety during class initialization (GrP 2001-9-24)
31 *
32 * Initial state: CLS_INITIALIZING and CLS_INITIALIZED both clear.
33 * During initialization: CLS_INITIALIZING is set
34 * After initialization: CLS_INITIALIZING clear and CLS_INITIALIZED set.
35 * CLS_INITIALIZING and CLS_INITIALIZED are never set at the same time.
36 * CLS_INITIALIZED is never cleared once set.
37 *
38 * Only one thread is allowed to actually initialize a class and send
39 * +initialize. Enforced by allowing only one thread to set CLS_INITIALIZING.
40 *
41 * Additionally, threads trying to send messages to a class must wait for
42 * +initialize to finish. During initialization of a class, that class's
43 * method cache is kept empty. objc_msgSend will revert to
44 * class_lookupMethodAndLoadCache, which checks CLS_INITIALIZED before
45 * messaging. If CLS_INITIALIZED is clear but CLS_INITIALIZING is set,
46 * the thread must block, unless it is the thread that started
47 * initializing the class in the first place.
48 *
49 * Each thread keeps a list of classes it's initializing.
50 * The global classInitLock is used to synchronize changes to CLS_INITIALIZED
51 * and CLS_INITIALIZING: the transition to CLS_INITIALIZING must be
52 * an atomic test-and-set with respect to itself and the transition
53 * to CLS_INITIALIZED.
54 * The global classInitWaitCond is used to block threads waiting for an
55 * initialization to complete. The classInitLock synchronizes
56 * condition checking and the condition variable.
57 **********************************************************************/
58
59 /***********************************************************************
60 * +initialize deadlock case when a class is marked initializing while
61 * its superclass is initialized. Solved by completely initializing
62 * superclasses before beginning to initialize a class.
63 *
64 * OmniWeb class hierarchy:
65 * OBObject
66 * | ` OBPostLoader
67 * OFObject
68 * / \
69 * OWAddressEntry OWController
70 * |
71 * OWConsoleController
72 *
73 * Thread 1 (evil testing thread):
74 * initialize OWAddressEntry
75 * super init OFObject
76 * super init OBObject
77 * [OBObject initialize] runs OBPostLoader, which inits lots of classes...
78 * initialize OWConsoleController
79 * super init OWController - wait for Thread 2 to finish OWController init
80 *
81 * Thread 2 (normal OmniWeb thread):
82 * initialize OWController
83 * super init OFObject - wait for Thread 1 to finish OFObject init
84 *
85 * deadlock!
86 *
87 * Solution: fully initialize super classes before beginning to initialize
88 * a subclass. Then the initializing+initialized part of the class hierarchy
89 * will be a contiguous subtree starting at the root, so other threads
90 * can't jump into the middle between two initializing classes, and we won't
91 * get stuck while a superclass waits for its subclass which waits for the
92 * superclass.
93 **********************************************************************/
94
95 #include "objc-private.h"
96 #include "message.h"
97 #include "objc-initialize.h"
98
99 /* classInitLock protects CLS_INITIALIZED and CLS_INITIALIZING, and
100 * is signalled when any class is done initializing.
101 * Threads that are waiting for a class to finish initializing wait on this. */
102 static monitor_t classInitLock = MONITOR_INITIALIZER;
103
104
105 /***********************************************************************
106 * struct _objc_initializing_classes
107 * Per-thread list of classes currently being initialized by that thread.
108 * During initialization, that thread is allowed to send messages to that
109 * class, but other threads have to wait.
110 * The list is a simple array of metaclasses (the metaclass stores
111 * the initialization state).
112 **********************************************************************/
113 typedef struct _objc_initializing_classes {
114 int classesAllocated;
115 Class *metaclasses;
116 } _objc_initializing_classes;
117
118
119 /***********************************************************************
120 * _fetchInitializingClassList
121 * Return the list of classes being initialized by this thread.
122 * If create == YES, create the list when no classes are being initialized by this thread.
123 * If create == NO, return NULL when no classes are being initialized by this thread.
124 **********************************************************************/
125 static _objc_initializing_classes *_fetchInitializingClassList(BOOL create)
126 {
127 _objc_pthread_data *data;
128 _objc_initializing_classes *list;
129 Class *classes;
130
131 data = _objc_fetch_pthread_data(create);
132 if (data == NULL && !create) return NULL;
133
134 list = data->initializingClasses;
135 if (list == NULL) {
136 if (!create) {
137 return NULL;
138 } else {
139 list = _calloc_internal(1, sizeof(_objc_initializing_classes));
140 data->initializingClasses = list;
141 }
142 }
143
144 classes = list->metaclasses;
145 if (classes == NULL) {
146 // If _objc_initializing_classes exists, allocate metaclass array,
147 // even if create == NO.
148 // Allow 4 simultaneous class inits on this thread before realloc.
149 list->classesAllocated = 4;
150 classes = _calloc_internal(list->classesAllocated, sizeof(Class));
151 list->metaclasses = classes;
152 }
153 return list;
154 }
155
156
157 /***********************************************************************
158 * _destroyInitializingClassList
159 * Deallocate memory used by the given initialization list.
160 * Any part of the list may be NULL.
161 * Called from _objc_pthread_destroyspecific().
162 **********************************************************************/
163 __private_extern__
164 void _destroyInitializingClassList(struct _objc_initializing_classes *list)
165 {
166 if (list != NULL) {
167 if (list->metaclasses != NULL) {
168 _free_internal(list->metaclasses);
169 }
170 _free_internal(list);
171 }
172 }
173
174
175 /***********************************************************************
176 * _thisThreadIsInitializingClass
177 * Return TRUE if this thread is currently initializing the given class.
178 **********************************************************************/
179 static BOOL _thisThreadIsInitializingClass(Class cls)
180 {
181 int i;
182
183 _objc_initializing_classes *list = _fetchInitializingClassList(NO);
184 if (list) {
185 cls = _class_getMeta(cls);
186 for (i = 0; i < list->classesAllocated; i++) {
187 if (cls == list->metaclasses[i]) return YES;
188 }
189 }
190
191 // no list or not found in list
192 return NO;
193 }
194
195
196 /***********************************************************************
197 * _setThisThreadIsInitializingClass
198 * Record that this thread is currently initializing the given class.
199 * This thread will be allowed to send messages to the class, but
200 * other threads will have to wait.
201 **********************************************************************/
202 static void _setThisThreadIsInitializingClass(Class cls)
203 {
204 int i;
205 _objc_initializing_classes *list = _fetchInitializingClassList(YES);
206 cls = _class_getMeta(cls);
207
208 // paranoia: explicitly disallow duplicates
209 for (i = 0; i < list->classesAllocated; i++) {
210 if (cls == list->metaclasses[i]) {
211 _objc_fatal("thread is already initializing this class!");
212 return; // already the initializer
213 }
214 }
215
216 for (i = 0; i < list->classesAllocated; i++) {
217 if (0 == list->metaclasses[i]) {
218 list->metaclasses[i] = cls;
219 return;
220 }
221 }
222
223 // class list is full - reallocate
224 list->classesAllocated = list->classesAllocated * 2 + 1;
225 list->metaclasses = _realloc_internal(list->metaclasses, list->classesAllocated * sizeof(Class));
226 // zero out the new entries
227 list->metaclasses[i++] = cls;
228 for ( ; i < list->classesAllocated; i++) {
229 list->metaclasses[i] = NULL;
230 }
231 }
232
233
234 /***********************************************************************
235 * _setThisThreadIsNotInitializingClass
236 * Record that this thread is no longer initializing the given class.
237 **********************************************************************/
238 static void _setThisThreadIsNotInitializingClass(Class cls)
239 {
240 int i;
241
242 _objc_initializing_classes *list = _fetchInitializingClassList(NO);
243 if (list) {
244 cls = _class_getMeta(cls);
245 for (i = 0; i < list->classesAllocated; i++) {
246 if (cls == list->metaclasses[i]) {
247 list->metaclasses[i] = NULL;
248 return;
249 }
250 }
251 }
252
253 // no list or not found in list
254 _objc_fatal("thread is not initializing this class!");
255 }
256
257
258 typedef struct PendingInitialize {
259 Class subclass;
260 struct PendingInitialize *next;
261 } PendingInitialize;
262
263 static NXMapTable *pendingInitializeMap;
264
265 /***********************************************************************
266 * _finishInitializing
267 * cls has completed its +initialize method, and so has its superclass.
268 * Mark cls as initialized as well, then mark any of cls's subclasses
269 * that have already finished their own +initialize methods.
270 **********************************************************************/
271 static void _finishInitializing(Class cls, Class supercls)
272 {
273 PendingInitialize *pending;
274
275 monitor_assert_locked(&classInitLock);
276 assert(!supercls || _class_isInitialized(supercls));
277
278 if (PrintInitializing) {
279 _objc_inform("INITIALIZE: %s is fully +initialized",
280 _class_getName(cls));
281 }
282
283 // propagate finalization affinity.
284 if (UseGC && supercls && _class_shouldFinalizeOnMainThread(supercls)) {
285 _class_setFinalizeOnMainThread(cls);
286 }
287
288 // mark this class as fully +initialized
289 _class_setInitialized(cls);
290 monitor_notifyAll(&classInitLock);
291 _setThisThreadIsNotInitializingClass(cls);
292
293 // mark any subclasses that were merely waiting for this class
294 if (!pendingInitializeMap) return;
295 pending = NXMapGet(pendingInitializeMap, cls);
296 if (!pending) return;
297
298 NXMapRemove(pendingInitializeMap, cls);
299
300 // Destroy the pending table if it's now empty, to save memory.
301 if (NXCountMapTable(pendingInitializeMap) == 0) {
302 NXFreeMapTable(pendingInitializeMap);
303 pendingInitializeMap = NULL;
304 }
305
306 while (pending) {
307 PendingInitialize *next = pending->next;
308 if (pending->subclass) _finishInitializing(pending->subclass, cls);
309 _free_internal(pending);
310 pending = next;
311 }
312 }
313
314
315 /***********************************************************************
316 * _finishInitializingAfter
317 * cls has completed its +initialize method, but its superclass has not.
318 * Wait until supercls finishes before marking cls as initialized.
319 **********************************************************************/
320 static void _finishInitializingAfter(Class cls, Class supercls)
321 {
322 PendingInitialize *pending;
323
324 monitor_assert_locked(&classInitLock);
325
326 if (PrintInitializing) {
327 _objc_inform("INITIALIZE: %s waiting for superclass +[%s initialize]",
328 _class_getName(cls), _class_getName(supercls));
329 }
330
331 if (!pendingInitializeMap) {
332 pendingInitializeMap =
333 NXCreateMapTableFromZone(NXPtrValueMapPrototype,
334 10, _objc_internal_zone());
335 // fixme pre-size this table for CF/NSObject +initialize
336 }
337
338 pending = _malloc_internal(sizeof(*pending));
339 pending->subclass = cls;
340 pending->next = NXMapGet(pendingInitializeMap, supercls);
341 NXMapInsert(pendingInitializeMap, supercls, pending);
342 }
343
344
345 /***********************************************************************
346 * class_initialize. Send the '+initialize' message on demand to any
347 * uninitialized class. Force initialization of superclasses first.
348 *
349 * Called only from _class_lookupMethodAndLoadCache (or itself).
350 **********************************************************************/
351 __private_extern__ void _class_initialize(Class cls)
352 {
353 Class supercls;
354 BOOL reallyInitialize = NO;
355
356 // Get the real class from the metaclass. The superclass chain
357 // hangs off the real class only.
358 cls = _class_getNonMetaClass(cls);
359
360 // Make sure super is done initializing BEFORE beginning to initialize cls.
361 // See note about deadlock above.
362 supercls = _class_getSuperclass(cls);
363 if (supercls && !_class_isInitialized(supercls)) {
364 _class_initialize(supercls);
365 }
366
367 // Try to atomically set CLS_INITIALIZING.
368 monitor_enter(&classInitLock);
369 if (!_class_isInitialized(cls) && !_class_isInitializing(cls)) {
370 _class_setInitializing(cls);
371 reallyInitialize = YES;
372 }
373 monitor_exit(&classInitLock);
374
375 if (reallyInitialize) {
376 // We successfully set the CLS_INITIALIZING bit. Initialize the class.
377
378 // Record that we're initializing this class so we can message it.
379 _setThisThreadIsInitializingClass(cls);
380
381 // Send the +initialize message.
382 // Note that +initialize is sent to the superclass (again) if
383 // this class doesn't implement +initialize. 2157218
384 if (PrintInitializing) {
385 _objc_inform("INITIALIZE: calling +[%s initialize]",
386 _class_getName(cls));
387 }
388
389 ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
390
391 if (PrintInitializing) {
392 _objc_inform("INITIALIZE: finished +[%s initialize]",
393 _class_getName(cls));
394 }
395
396 // Done initializing.
397 // If the superclass is also done initializing, then update
398 // the info bits and notify waiting threads.
399 // If not, update them later. (This can happen if this +initialize
400 // was itself triggered from inside a superclass +initialize.)
401
402 monitor_enter(&classInitLock);
403 if (!supercls || _class_isInitialized(supercls)) {
404 _finishInitializing(cls, supercls);
405 } else {
406 _finishInitializingAfter(cls, supercls);
407 }
408 monitor_exit(&classInitLock);
409 return;
410 }
411
412 else if (_class_isInitializing(cls)) {
413 // We couldn't set INITIALIZING because INITIALIZING was already set.
414 // If this thread set it earlier, continue normally.
415 // If some other thread set it, block until initialize is done.
416 // It's ok if INITIALIZING changes to INITIALIZED while we're here,
417 // because we safely check for INITIALIZED inside the lock
418 // before blocking.
419 if (_thisThreadIsInitializingClass(cls)) {
420 return;
421 } else {
422 monitor_enter(&classInitLock);
423 while (!_class_isInitialized(cls)) {
424 monitor_wait(&classInitLock);
425 }
426 monitor_exit(&classInitLock);
427 return;
428 }
429 }
430
431 else if (_class_isInitialized(cls)) {
432 // Set CLS_INITIALIZING failed because someone else already
433 // initialized the class. Continue normally.
434 // NOTE this check must come AFTER the ISINITIALIZING case.
435 // Otherwise: Another thread is initializing this class. ISINITIALIZED
436 // is false. Skip this clause. Then the other thread finishes
437 // initialization and sets INITIALIZING=no and INITIALIZED=yes.
438 // Skip the ISINITIALIZING clause. Die horribly.
439 return;
440 }
441
442 else {
443 // We shouldn't be here.
444 _objc_fatal("thread-safe class init in objc runtime is buggy!");
445 }
446 }