]>
Commit | Line | Data |
---|---|---|
b3962a83 A |
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 <pthread.h> | |
96 | #include <assert.h> | |
97 | ||
98 | #import "objc-private.h" | |
99 | #import "objc-initialize.h" | |
100 | ||
101 | /* classInitLock protects classInitWaitCond and examination and modification | |
102 | * of CLS_INITIALIZED and CLS_INITIALIZING. */ | |
103 | static OBJC_DECLARE_LOCK(classInitLock); | |
104 | ||
105 | /* classInitWaitCond is signalled when any class is done initializing. | |
106 | * Threads that are waiting for a class to finish initializing wait on this. */ | |
107 | static pthread_cond_t classInitWaitCond = PTHREAD_COND_INITIALIZER; | |
108 | ||
109 | ||
110 | /*********************************************************************** | |
111 | * struct _objc_initializing_classes | |
112 | * Per-thread list of classes currently being initialized by that thread. | |
113 | * During initialization, that thread is allowed to send messages to that | |
114 | * class, but other threads have to wait. | |
115 | * The list is a simple array of metaclasses (the metaclass stores | |
116 | * the initialization state). | |
117 | **********************************************************************/ | |
118 | typedef struct _objc_initializing_classes { | |
119 | int classesAllocated; | |
120 | Class *metaclasses; | |
121 | } _objc_initializing_classes; | |
122 | ||
123 | ||
124 | /*********************************************************************** | |
125 | * _fetchInitializingClassList | |
126 | * Return the list of classes being initialized by this thread. | |
127 | * If create == YES, create the list when no classes are being initialized by this thread. | |
128 | * If create == NO, return NULL when no classes are being initialized by this thread. | |
129 | **********************************************************************/ | |
130 | static _objc_initializing_classes *_fetchInitializingClassList(BOOL create) | |
131 | { | |
132 | _objc_pthread_data *data; | |
133 | _objc_initializing_classes *list; | |
134 | Class *classes; | |
135 | ||
136 | data = _objc_fetch_pthread_data(create); | |
137 | if (data == NULL && !create) return NULL; | |
138 | ||
139 | list = data->initializingClasses; | |
140 | if (list == NULL) { | |
141 | if (!create) { | |
142 | return NULL; | |
143 | } else { | |
144 | list = _calloc_internal(1, sizeof(_objc_initializing_classes)); | |
145 | data->initializingClasses = list; | |
146 | } | |
147 | } | |
148 | ||
149 | classes = list->metaclasses; | |
150 | if (classes == NULL) { | |
151 | // If _objc_initializing_classes exists, allocate metaclass array, | |
152 | // even if create == NO. | |
153 | // Allow 4 simultaneous class inits on this thread before realloc. | |
154 | list->classesAllocated = 4; | |
155 | classes = _calloc_internal(list->classesAllocated, sizeof(Class)); | |
156 | list->metaclasses = classes; | |
157 | } | |
158 | return list; | |
159 | } | |
160 | ||
161 | ||
162 | /*********************************************************************** | |
163 | * _destroyInitializingClassList | |
164 | * Deallocate memory used by the given initialization list. | |
165 | * Any part of the list may be NULL. | |
166 | * Called from _objc_pthread_destroyspecific(). | |
167 | **********************************************************************/ | |
168 | __private_extern__ | |
169 | void _destroyInitializingClassList(struct _objc_initializing_classes *list) | |
170 | { | |
171 | if (list != NULL) { | |
172 | if (list->metaclasses != NULL) { | |
173 | _free_internal(list->metaclasses); | |
174 | } | |
175 | _free_internal(list); | |
176 | } | |
177 | } | |
178 | ||
179 | ||
180 | /*********************************************************************** | |
181 | * _thisThreadIsInitializingClass | |
182 | * Return TRUE if this thread is currently initializing the given class. | |
183 | **********************************************************************/ | |
184 | static BOOL _thisThreadIsInitializingClass(Class cls) | |
185 | { | |
186 | int i; | |
187 | ||
188 | _objc_initializing_classes *list = _fetchInitializingClassList(NO); | |
189 | if (list) { | |
190 | cls = _class_getMeta(cls); | |
191 | for (i = 0; i < list->classesAllocated; i++) { | |
192 | if (cls == list->metaclasses[i]) return YES; | |
193 | } | |
194 | } | |
195 | ||
196 | // no list or not found in list | |
197 | return NO; | |
198 | } | |
199 | ||
200 | ||
201 | /*********************************************************************** | |
202 | * _setThisThreadIsInitializingClass | |
203 | * Record that this thread is currently initializing the given class. | |
204 | * This thread will be allowed to send messages to the class, but | |
205 | * other threads will have to wait. | |
206 | **********************************************************************/ | |
207 | static void _setThisThreadIsInitializingClass(Class cls) | |
208 | { | |
209 | int i; | |
210 | _objc_initializing_classes *list = _fetchInitializingClassList(YES); | |
211 | cls = _class_getMeta(cls); | |
212 | ||
213 | // paranoia: explicitly disallow duplicates | |
214 | for (i = 0; i < list->classesAllocated; i++) { | |
215 | if (cls == list->metaclasses[i]) { | |
216 | _objc_fatal("thread is already initializing this class!"); | |
217 | return; // already the initializer | |
218 | } | |
219 | } | |
220 | ||
221 | for (i = 0; i < list->classesAllocated; i++) { | |
222 | if (0 == list->metaclasses[i]) { | |
223 | list->metaclasses[i] = cls; | |
224 | return; | |
225 | } | |
226 | } | |
227 | ||
228 | // class list is full - reallocate | |
229 | list->classesAllocated = list->classesAllocated * 2 + 1; | |
230 | list->metaclasses = _realloc_internal(list->metaclasses, list->classesAllocated * sizeof(Class)); | |
231 | // zero out the new entries | |
232 | list->metaclasses[i++] = cls; | |
233 | for ( ; i < list->classesAllocated; i++) { | |
234 | list->metaclasses[i] = NULL; | |
235 | } | |
236 | } | |
237 | ||
238 | ||
239 | /*********************************************************************** | |
240 | * _setThisThreadIsNotInitializingClass | |
241 | * Record that this thread is no longer initializing the given class. | |
242 | **********************************************************************/ | |
243 | static void _setThisThreadIsNotInitializingClass(Class cls) | |
244 | { | |
245 | int i; | |
246 | ||
247 | _objc_initializing_classes *list = _fetchInitializingClassList(NO); | |
248 | if (list) { | |
249 | cls = _class_getMeta(cls); | |
250 | for (i = 0; i < list->classesAllocated; i++) { | |
251 | if (cls == list->metaclasses[i]) { | |
252 | list->metaclasses[i] = NULL; | |
253 | return; | |
254 | } | |
255 | } | |
256 | } | |
257 | ||
258 | // no list or not found in list | |
259 | _objc_fatal("thread is not initializing this class!"); | |
260 | } | |
261 | ||
262 | ||
263 | /*********************************************************************** | |
264 | * class_initialize. Send the '+initialize' message on demand to any | |
265 | * uninitialized class. Force initialization of superclasses first. | |
266 | * | |
267 | * Called only from _class_lookupMethodAndLoadCache (or itself). | |
268 | **********************************************************************/ | |
269 | __private_extern__ void _class_initialize(Class cls) | |
270 | { | |
271 | Class supercls; | |
272 | BOOL reallyInitialize = NO; | |
273 | ||
274 | // Get the real class from the metaclass. The superclass chain | |
275 | // hangs off the real class only. | |
276 | cls = _class_getNonMetaClass(cls); | |
277 | ||
278 | // Make sure super is done initializing BEFORE beginning to initialize cls. | |
279 | // See note about deadlock above. | |
280 | supercls = _class_getSuperclass(cls); | |
281 | if (supercls && !_class_isInitialized(supercls)) { | |
282 | _class_initialize(supercls); | |
283 | } | |
284 | ||
285 | // Try to atomically set CLS_INITIALIZING. | |
286 | OBJC_LOCK(&classInitLock); | |
287 | if (!_class_isInitialized(cls) && !_class_isInitializing(cls)) { | |
288 | _class_setInitializing(cls); | |
289 | reallyInitialize = YES; | |
290 | } | |
291 | OBJC_UNLOCK(&classInitLock); | |
292 | ||
293 | if (reallyInitialize) { | |
294 | // We successfully set the CLS_INITIALIZING bit. Initialize the class. | |
295 | ||
296 | // Record that we're initializing this class so we can message it. | |
297 | _setThisThreadIsInitializingClass(cls); | |
298 | ||
299 | // Send the +initialize message. | |
300 | // Note that +initialize is sent to the superclass (again) if | |
301 | // this class doesn't implement +initialize. 2157218 | |
302 | if (PrintInitializing) { | |
303 | _objc_inform("INITIALIZE: calling +[%s initialize]", | |
304 | _class_getName(cls)); | |
305 | } | |
306 | [(id)cls initialize]; | |
307 | ||
308 | // propagate finalization affinity. | |
309 | if (UseGC && supercls && _class_shouldFinalizeOnMainThread(supercls)) { | |
310 | _class_setFinalizeOnMainThread(cls); | |
311 | } | |
312 | ||
313 | // Done initializing. Update the info bits and notify waiting threads. | |
314 | OBJC_LOCK(&classInitLock); | |
315 | _class_setInitialized(cls); | |
316 | pthread_cond_broadcast(&classInitWaitCond); | |
317 | OBJC_UNLOCK(&classInitLock); | |
318 | _setThisThreadIsNotInitializingClass(cls); | |
319 | return; | |
320 | } | |
321 | ||
322 | else if (_class_isInitializing(cls)) { | |
323 | // We couldn't set INITIALIZING because INITIALIZING was already set. | |
324 | // If this thread set it earlier, continue normally. | |
325 | // If some other thread set it, block until initialize is done. | |
326 | // It's ok if INITIALIZING changes to INITIALIZED while we're here, | |
327 | // because we safely check for INITIALIZED inside the lock | |
328 | // before blocking. | |
329 | if (_thisThreadIsInitializingClass(cls)) { | |
330 | return; | |
331 | } else { | |
332 | OBJC_LOCK(&classInitLock); | |
333 | while (!_class_isInitialized(cls)) { | |
334 | pthread_cond_wait(&classInitWaitCond, &classInitLock); | |
335 | } | |
336 | OBJC_UNLOCK(&classInitLock); | |
337 | return; | |
338 | } | |
339 | } | |
340 | ||
341 | else if (_class_isInitialized(cls)) { | |
342 | // Set CLS_INITIALIZING failed because someone else already | |
343 | // initialized the class. Continue normally. | |
344 | // NOTE this check must come AFTER the ISINITIALIZING case. | |
345 | // Otherwise: Another thread is initializing this class. ISINITIALIZED | |
346 | // is false. Skip this clause. Then the other thread finishes | |
347 | // initialization and sets INITIALIZING=no and INITIALIZED=yes. | |
348 | // Skip the ISINITIALIZING clause. Die horribly. | |
349 | return; | |
350 | } | |
351 | ||
352 | else { | |
353 | // We shouldn't be here. | |
354 | _objc_fatal("thread-safe class init in objc runtime is buggy!"); | |
355 | } | |
356 | } |