]>
Commit | Line | Data |
---|---|---|
9ce05555 A |
1 | /* |
2 | * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. | |
7 | * | |
8 | * This file contains Original Code and/or Modifications of Original Code | |
9 | * as defined in and that are subject to the Apple Public Source License | |
10 | * Version 2.0 (the 'License'). You may not use this file except in | |
11 | * compliance with the License. Please obtain a copy of the License at | |
12 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
13 | * file. | |
14 | * | |
15 | * The Original Code and all software distributed under the License are | |
16 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
17 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
18 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
19 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
20 | * Please see the License for the specific language governing rights and | |
21 | * limitations under the License. | |
22 | * | |
23 | * @APPLE_LICENSE_HEADER_END@ | |
24 | */ | |
25 | /* CFMachPort.c | |
26 | Copyright 1998-2002, Apple, Inc. All rights reserved. | |
27 | Responsibility: Christopher Kane | |
28 | */ | |
29 | ||
30 | /* | |
31 | [The following dissertation was written mostly for the | |
32 | benefit of open source developers.] | |
33 | ||
34 | Writing a run loop source can be a tricky business, but | |
35 | for CFMachPort that part is relatively straightforward. | |
36 | Thus, it makes a good example for study. Particularly | |
37 | interesting for examination is the process of caching | |
38 | the objects in a non-retaining cache, the invalidation | |
39 | and deallocation sequences, locking for thread-safety, | |
40 | and how the invalidation callback is used. | |
41 | ||
42 | CFMachPort is a version 1 CFRunLoopSource, implemented | |
43 | by a few functions. See CFMachPortCreateRunLoopSource() | |
44 | for details on how the run loop source is setup. Note | |
45 | how the source is kept track of by the CFMachPort, so | |
46 | that it can be returned again and again from that function. | |
47 | This helps not only reduce the amount of memory expended | |
48 | in run loop source objects, but eliminates redundant | |
49 | registrations with the run loop and the excess time and | |
50 | memory that would consume. It also allows the CFMachPort | |
51 | to propogate its own invalidation to the run loop source | |
52 | representing it. | |
53 | ||
54 | CFMachPortCreateWithPort() is the funnel point for the | |
55 | creation of CFMachPort instances. The cache is first | |
56 | probed to see if an instance with that port is already | |
57 | available, and return that. The object is next allocated | |
58 | and mostly initialized, before it is registered for death | |
59 | notification. This is because cleaning up the memory is | |
60 | simpler than trying to get rid of the registration if | |
61 | memory allocation later fails. The new object must be at | |
62 | least partially initialized (into a harmless state) so | |
63 | that it can be safely invalidated/deallocated if something | |
64 | fails later in creation. Any object allocated with | |
65 | _CFRuntimeCreateInstance() may only be disposed by using | |
66 | CFRelease() (never CFAllocatorDeallocate!) so the class | |
67 | deallocation function __CFMachPortDeallocate() must be | |
68 | able to handle that, and initializing the object to have | |
69 | NULL fields and whatnot makes that possible. The creation | |
70 | eventually inserts the new object in the cache. | |
71 | ||
72 | A CFMachPort may be explicitly invalidated, autoinvalidated | |
73 | due to the death of the port (that process is not discussed | |
74 | further here), or invalidated as part of the deallocation | |
75 | process when the last reference is released. For | |
76 | convenience, in all cases this is done through | |
77 | CFMachPortInvalidate(). To prevent the CFMachPort from | |
78 | being freed in mid-function due to the callouts, the object | |
79 | is retained at the beginning of the function. But if this | |
80 | invalidation is due to the object being deallocated, a | |
81 | retain and then release at the end of the function would | |
82 | cause a recursive call to __CFMachPortDeallocate(). The | |
83 | retain protection should be immaterial though at that stage. | |
84 | Invalidation also removes the object from the cache; though | |
85 | the object itself is not yet destroyed, invalidation makes | |
86 | it "useless". | |
87 | ||
88 | The best way to learn about the locking is to look through | |
89 | the code -- it's fairly straightforward. The one thing | |
90 | worth calling attention to is how locks must be unlocked | |
91 | before invoking any user-defined callout function, and | |
92 | usually retaken after it returns. This supports reentrancy | |
93 | (which is distinct from thread-safety). | |
94 | ||
95 | The invalidation callback, if one has been set, is called | |
96 | at invalidation time, but before the object has been torn | |
97 | down so that the port and other properties may be retrieved | |
98 | from the object in the callback. Note that if the callback | |
99 | is attempted to be set after the CFMachPort is invalid, | |
100 | the function is simply called. This helps with certain | |
101 | race conditions where the invalidation notification might | |
102 | be lost. Only the owner/creator of a CFMachPort should | |
103 | really be setting the invalidation callback. | |
104 | ||
105 | Now, the CFMachPort is not retained/released around all | |
106 | callouts, but the callout may release the last reference. | |
107 | Also, sometimes it is friendly to retain/release the | |
108 | user-defined "info" around callouts, so that clients | |
109 | don't have to worry about that. These may be some things | |
110 | to think about in the future, but is usually overkill. | |
111 | ||
112 | Finally, one tricky bit mostly specific to CFMachPort | |
113 | deserves mention: use of __CFMachPortCurrentPID. Mach | |
114 | ports are not inherited by a child process created with | |
115 | fork(), unlike most other things. Thus, on fork(), in | |
116 | the child, all cached CFMachPorts should be invalidated. | |
117 | However, because the ports were never in the new task's | |
118 | port space, the Mach part of the kernel doesn't send | |
119 | dead-name notifications (which is used to autoinvalidate | |
120 | CFMachPorts) to the new task, which is reasonable. There | |
121 | is also no other notification that a fork() has occurred, | |
122 | though, so how is CFMachPort to deal with this? An | |
123 | atfork() function, similar to atexit(), would be nice, | |
124 | but is not available. So CFMachPort does the best it | |
125 | can, which is to compare the current process's PID with | |
126 | the last PID recorded and if different, invalidate all | |
127 | CFMachPorts, whenever various CFMachPort functions are | |
128 | called. To avoid going insane, I've assumed that clients | |
129 | aren't going to fork() as a result of callouts from this | |
130 | code, which in a couple places might actually cause trouble. | |
131 | It also isn't completely thread-safe. | |
132 | ||
133 | In general, with higher level functionalities in the system, | |
134 | it isn't even possible for a process to fork() and the child | |
135 | not exec(), but continue running, since the higher levels | |
136 | have done one-time initializations that aren't going to | |
137 | happen again. | |
138 | ||
139 | - Chris Kane | |
140 | ||
141 | */ | |
142 | ||
143 | #if defined(__MACH__) | |
144 | ||
145 | #include <CoreFoundation/CFMachPort.h> | |
146 | #include <CoreFoundation/CFRunLoop.h> | |
147 | #include <CoreFoundation/CFDictionary.h> | |
148 | #include <mach/mach.h> | |
149 | #include <mach/mach_error.h> | |
150 | #include <mach/notify.h> | |
151 | #include "CFInternal.h" | |
152 | ||
153 | static CFSpinLock_t __CFAllMachPortsLock = 0; | |
154 | static CFMutableDictionaryRef __CFAllMachPorts = NULL; | |
155 | static mach_port_t __CFNotifyRawMachPort = MACH_PORT_NULL; | |
156 | static CFMachPortRef __CFNotifyMachPort = NULL; | |
157 | static int __CFMachPortCurrentPID = 0; | |
158 | ||
159 | struct __CFMachPort { | |
160 | CFRuntimeBase _base; | |
161 | CFSpinLock_t _lock; | |
162 | mach_port_t _port; /* immutable; invalidated */ | |
163 | mach_port_t _oldnotify; /* immutable; invalidated */ | |
164 | CFRunLoopSourceRef _source; /* immutable, once created; invalidated */ | |
165 | CFMachPortInvalidationCallBack _icallout; | |
166 | CFMachPortCallBack _callout; /* immutable */ | |
167 | CFMachPortContext _context; /* immutable; invalidated */ | |
168 | }; | |
169 | ||
170 | /* Bit 0 in the base reserved bits is used for invalid state */ | |
171 | /* Bit 1 in the base reserved bits is used for has-receive-ref state */ | |
172 | /* Bit 2 in the base reserved bits is used for has-send-ref state */ | |
173 | /* Bit 3 in the base reserved bits is used for is-deallocing state */ | |
174 | ||
175 | CF_INLINE Boolean __CFMachPortIsValid(CFMachPortRef mp) { | |
176 | return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)mp)->_info, 0, 0); | |
177 | } | |
178 | ||
179 | CF_INLINE void __CFMachPortSetValid(CFMachPortRef mp) { | |
180 | __CFBitfieldSetValue(((CFRuntimeBase *)mp)->_info, 0, 0, 1); | |
181 | } | |
182 | ||
183 | CF_INLINE void __CFMachPortUnsetValid(CFMachPortRef mp) { | |
184 | __CFBitfieldSetValue(((CFRuntimeBase *)mp)->_info, 0, 0, 0); | |
185 | } | |
186 | ||
187 | CF_INLINE Boolean __CFMachPortHasReceive(CFMachPortRef mp) { | |
188 | return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)mp)->_info, 1, 1); | |
189 | } | |
190 | ||
191 | CF_INLINE void __CFMachPortSetHasReceive(CFMachPortRef mp) { | |
192 | __CFBitfieldSetValue(((CFRuntimeBase *)mp)->_info, 1, 1, 1); | |
193 | } | |
194 | ||
195 | CF_INLINE Boolean __CFMachPortHasSend(CFMachPortRef mp) { | |
196 | return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)mp)->_info, 2, 2); | |
197 | } | |
198 | ||
199 | CF_INLINE void __CFMachPortSetHasSend(CFMachPortRef mp) { | |
200 | __CFBitfieldSetValue(((CFRuntimeBase *)mp)->_info, 2, 2, 1); | |
201 | } | |
202 | ||
203 | CF_INLINE Boolean __CFMachPortIsDeallocing(CFMachPortRef mp) { | |
204 | return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)mp)->_info, 3, 3); | |
205 | } | |
206 | ||
207 | CF_INLINE void __CFMachPortSetIsDeallocing(CFMachPortRef mp) { | |
208 | __CFBitfieldSetValue(((CFRuntimeBase *)mp)->_info, 3, 3, 1); | |
209 | } | |
210 | ||
211 | CF_INLINE void __CFMachPortLock(CFMachPortRef mp) { | |
212 | __CFSpinLock(&(mp->_lock)); | |
213 | } | |
214 | ||
215 | CF_INLINE void __CFMachPortUnlock(CFMachPortRef mp) { | |
216 | __CFSpinUnlock(&(mp->_lock)); | |
217 | } | |
218 | ||
219 | // The only CFMachPort this releases is the notify port, which | |
220 | // no one else should have gotten their grubby hands on. Set the | |
221 | // new PID first, to avoid reentrant invocations of this function. | |
222 | static void __CFMachPortDidFork(void) { | |
223 | CFMachPortRef oldNotify; | |
224 | __CFMachPortCurrentPID = getpid(); | |
225 | __CFSpinLock(&__CFAllMachPortsLock); | |
226 | oldNotify = __CFNotifyMachPort; | |
227 | __CFNotifyMachPort = NULL; | |
228 | __CFNotifyRawMachPort = MACH_PORT_NULL; | |
229 | if (NULL != __CFAllMachPorts) { | |
230 | CFIndex idx, cnt; | |
231 | CFMachPortRef *mps, buffer[128]; | |
232 | cnt = CFDictionaryGetCount(__CFAllMachPorts); | |
233 | mps = (cnt <= 128) ? buffer : CFAllocatorAllocate(kCFAllocatorDefault, cnt * sizeof(CFMachPortRef), 0); | |
234 | CFDictionaryGetKeysAndValues(__CFAllMachPorts, NULL, (const void **)mps); | |
235 | CFRelease(__CFAllMachPorts); | |
236 | __CFAllMachPorts = NULL; | |
237 | __CFSpinUnlock(&__CFAllMachPortsLock); | |
238 | for (idx = 0; idx < cnt; idx++) { | |
239 | // invalidation must be outside the lock | |
240 | CFMachPortInvalidate(mps[idx]); | |
241 | } | |
242 | if (mps != buffer) CFAllocatorDeallocate(kCFAllocatorDefault, mps); | |
243 | } else { | |
244 | __CFSpinUnlock(&__CFAllMachPortsLock); | |
245 | } | |
246 | if (NULL != oldNotify) { | |
247 | // The global is NULL'd before this, just in case. | |
248 | // __CFNotifyMachPort was in the cache and was | |
249 | // invalidated above, but that's harmless. | |
250 | CFRelease(oldNotify); | |
251 | } | |
252 | } | |
253 | ||
254 | CF_INLINE void __CFMachPortCheckForFork(void) { | |
255 | if (getpid() != __CFMachPortCurrentPID) { | |
256 | __CFMachPortDidFork(); | |
257 | } | |
258 | } | |
259 | ||
260 | void _CFMachPortInstallNotifyPort(CFRunLoopRef rl, CFStringRef mode) { | |
261 | CFRunLoopSourceRef source; | |
262 | if (NULL == __CFNotifyMachPort) return; | |
263 | source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, __CFNotifyMachPort, -1000); | |
264 | CFRunLoopAddSource(rl, source, mode); | |
265 | CFRelease(source); | |
266 | } | |
267 | ||
268 | static void __CFNotifyDeadMachPort(CFMachPortRef port, void *msg, CFIndex size, void *info) { | |
269 | mach_msg_header_t *header = (mach_msg_header_t *)msg; | |
270 | if (header && header->msgh_id == MACH_NOTIFY_DEAD_NAME) { | |
271 | mach_port_t dead_port = ((mach_dead_name_notification_t *)msg)->not_port; | |
272 | CFMachPortRef existing; | |
273 | /* If the CFMachPort has already been invalidated, it won't be found here. */ | |
274 | __CFSpinLock(&__CFAllMachPortsLock); | |
275 | if (NULL != __CFAllMachPorts && CFDictionaryGetValueIfPresent(__CFAllMachPorts, (void *)dead_port, (const void **)&existing)) { | |
276 | CFDictionaryRemoveValue(__CFAllMachPorts, (void *)dead_port); | |
277 | CFRetain(existing); | |
278 | __CFSpinUnlock(&__CFAllMachPortsLock); | |
279 | CFMachPortInvalidate(existing); | |
280 | CFRelease(existing); | |
281 | } else { | |
282 | __CFSpinUnlock(&__CFAllMachPortsLock); | |
283 | } | |
284 | /* Delete port reference we got for this notification */ | |
285 | mach_port_deallocate(mach_task_self(), dead_port); | |
286 | } else if (header && header->msgh_id == MACH_NOTIFY_PORT_DELETED) { | |
287 | mach_port_t dead_port = ((mach_port_deleted_notification_t *)msg)->not_port; | |
288 | CFMachPortRef existing; | |
289 | /* If the CFMachPort has already been invalidated, it won't be found here. */ | |
290 | __CFSpinLock(&__CFAllMachPortsLock); | |
291 | if (NULL != __CFAllMachPorts && CFDictionaryGetValueIfPresent(__CFAllMachPorts, (void *)dead_port, (const void **)&existing)) { | |
292 | CFDictionaryRemoveValue(__CFAllMachPorts, (void *)dead_port); | |
293 | CFRetain(existing); | |
294 | __CFSpinUnlock(&__CFAllMachPortsLock); | |
295 | CFMachPortInvalidate(existing); | |
296 | CFRelease(existing); | |
297 | } else { | |
298 | __CFSpinUnlock(&__CFAllMachPortsLock); | |
299 | } | |
300 | /* Delete port reference we got for this notification */ | |
301 | // Don't do this, since this always fails, and could cause trouble | |
302 | // mach_port_deallocate(mach_task_self(), dead_port); | |
303 | } | |
304 | } | |
305 | ||
306 | static Boolean __CFMachPortEqual(CFTypeRef cf1, CFTypeRef cf2) { | |
307 | CFMachPortRef mp1 = (CFMachPortRef)cf1; | |
308 | CFMachPortRef mp2 = (CFMachPortRef)cf2; | |
309 | __CFMachPortCheckForFork(); | |
310 | return (mp1->_port == mp2->_port); | |
311 | } | |
312 | ||
313 | static CFHashCode __CFMachPortHash(CFTypeRef cf) { | |
314 | CFMachPortRef mp = (CFMachPortRef)cf; | |
315 | __CFMachPortCheckForFork(); | |
316 | return (CFHashCode)mp->_port; | |
317 | } | |
318 | ||
319 | static CFStringRef __CFMachPortCopyDescription(CFTypeRef cf) { | |
320 | CFMachPortRef mp = (CFMachPortRef)cf; | |
321 | CFStringRef result; | |
322 | const char *locked; | |
323 | CFStringRef contextDesc = NULL; | |
324 | locked = mp->_lock ? "Yes" : "No"; | |
325 | if (NULL != mp->_context.info && NULL != mp->_context.copyDescription) { | |
326 | contextDesc = mp->_context.copyDescription(mp->_context.info); | |
327 | } | |
328 | if (NULL == contextDesc) { | |
329 | contextDesc = CFStringCreateWithFormat(CFGetAllocator(mp), NULL, CFSTR("<CFMachPort context %p>"), mp->_context.info); | |
330 | } | |
331 | result = CFStringCreateWithFormat(CFGetAllocator(mp), NULL, CFSTR("<CFMachPort %p [%p]>{locked = %s, valid = %s, port = %p, source = %p, callout = %p, context = %@}"), cf, CFGetAllocator(mp), locked, (__CFMachPortIsValid(mp) ? "Yes" : "No"), mp->_port, mp->_source, mp->_callout, (NULL != contextDesc ? contextDesc : CFSTR("<no description>"))); | |
332 | if (NULL != contextDesc) { | |
333 | CFRelease(contextDesc); | |
334 | } | |
335 | return result; | |
336 | } | |
337 | ||
338 | static void __CFMachPortDeallocate(CFTypeRef cf) { | |
339 | CFMachPortRef mp = (CFMachPortRef)cf; | |
340 | __CFMachPortSetIsDeallocing(mp); | |
341 | CFMachPortInvalidate(mp); | |
342 | // MUST deallocate the send right FIRST if necessary, | |
343 | // then the receive right if necessary. Don't ask me why; | |
344 | // if it's done in the other order the port will leak. | |
345 | if (__CFMachPortHasSend(mp)) { | |
346 | mach_port_mod_refs(mach_task_self(), mp->_port, MACH_PORT_RIGHT_SEND, -1); | |
347 | } | |
348 | if (__CFMachPortHasReceive(mp)) { | |
349 | mach_port_mod_refs(mach_task_self(), mp->_port, MACH_PORT_RIGHT_RECEIVE, -1); | |
350 | } | |
351 | __CFMachPortCheckForFork(); | |
352 | } | |
353 | ||
354 | static CFTypeID __kCFMachPortTypeID = _kCFRuntimeNotATypeID; | |
355 | ||
356 | static const CFRuntimeClass __CFMachPortClass = { | |
357 | 0, | |
358 | "CFMachPort", | |
359 | NULL, // init | |
360 | NULL, // copy | |
361 | __CFMachPortDeallocate, | |
362 | __CFMachPortEqual, | |
363 | __CFMachPortHash, | |
364 | NULL, // | |
365 | __CFMachPortCopyDescription | |
366 | }; | |
367 | ||
368 | __private_extern__ void __CFMachPortInitialize(void) { | |
369 | __kCFMachPortTypeID = _CFRuntimeRegisterClass(&__CFMachPortClass); | |
370 | __CFMachPortCurrentPID = getpid(); | |
371 | } | |
372 | ||
373 | CFTypeID CFMachPortGetTypeID(void) { | |
374 | __CFMachPortCheckForFork(); | |
375 | return __kCFMachPortTypeID; | |
376 | } | |
377 | ||
378 | CFMachPortRef CFMachPortCreate(CFAllocatorRef allocator, CFMachPortCallBack callout, CFMachPortContext *context, Boolean *shouldFreeInfo) { | |
379 | CFMachPortRef result; | |
380 | mach_port_t port; | |
381 | kern_return_t ret; | |
382 | __CFMachPortCheckForFork(); | |
383 | if (shouldFreeInfo) *shouldFreeInfo = true; | |
384 | ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); | |
385 | if (KERN_SUCCESS != ret) { | |
386 | return NULL; | |
387 | } | |
388 | ret = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND); | |
389 | if (KERN_SUCCESS != ret) { | |
390 | mach_port_destroy(mach_task_self(), port); | |
391 | return NULL; | |
392 | } | |
393 | result = CFMachPortCreateWithPort(allocator, port, callout, context, shouldFreeInfo); | |
394 | if (NULL != result) { | |
395 | __CFMachPortSetHasReceive(result); | |
396 | __CFMachPortSetHasSend(result); | |
397 | } | |
398 | return result; | |
399 | } | |
400 | ||
401 | /* Note: any receive or send rights that the port contains coming in will | |
402 | * not be cleaned up by CFMachPort; it will increment and decrement | |
403 | * references on the port if the kernel ever allows that in the future, | |
404 | * but will not cleanup any references you got when you got the port. */ | |
405 | CFMachPortRef CFMachPortCreateWithPort(CFAllocatorRef allocator, mach_port_t port, CFMachPortCallBack callout, CFMachPortContext *context, Boolean *shouldFreeInfo) { | |
406 | CFMachPortRef memory; | |
407 | SInt32 size; | |
408 | Boolean didCreateNotifyPort = false; | |
409 | CFRunLoopSourceRef source; | |
410 | __CFMachPortCheckForFork(); | |
411 | if (shouldFreeInfo) *shouldFreeInfo = true; | |
412 | __CFSpinLock(&__CFAllMachPortsLock); | |
413 | if (NULL != __CFAllMachPorts && CFDictionaryGetValueIfPresent(__CFAllMachPorts, (void *)port, (const void **)&memory)) { | |
414 | __CFSpinUnlock(&__CFAllMachPortsLock); | |
415 | return (CFMachPortRef)CFRetain(memory); | |
416 | } | |
417 | size = sizeof(struct __CFMachPort) - sizeof(CFRuntimeBase); | |
418 | memory = (CFMachPortRef)_CFRuntimeCreateInstance(allocator, __kCFMachPortTypeID, size, NULL); | |
419 | if (NULL == memory) { | |
420 | __CFSpinUnlock(&__CFAllMachPortsLock); | |
421 | return NULL; | |
422 | } | |
423 | __CFMachPortUnsetValid(memory); | |
424 | memory->_lock = 0; | |
425 | memory->_port = port; | |
426 | memory->_source = NULL; | |
427 | memory->_icallout = NULL; | |
428 | memory->_context.info = NULL; | |
429 | memory->_context.retain = NULL; | |
430 | memory->_context.release = NULL; | |
431 | memory->_context.copyDescription = NULL; | |
432 | if (MACH_PORT_NULL == __CFNotifyRawMachPort) { | |
433 | kern_return_t ret; | |
434 | ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &__CFNotifyRawMachPort); | |
435 | if (KERN_SUCCESS != ret) { | |
436 | __CFSpinUnlock(&__CFAllMachPortsLock); | |
437 | CFRelease(memory); | |
438 | return NULL; | |
439 | } | |
440 | didCreateNotifyPort = true; | |
441 | } | |
442 | // Do not register for notifications on the notify port | |
443 | if (MACH_PORT_NULL != __CFNotifyRawMachPort && port != __CFNotifyRawMachPort) { | |
444 | mach_port_t old_port; | |
445 | kern_return_t ret; | |
446 | old_port = MACH_PORT_NULL; | |
447 | ret = mach_port_request_notification(mach_task_self(), port, MACH_NOTIFY_DEAD_NAME, 0, __CFNotifyRawMachPort, MACH_MSG_TYPE_MAKE_SEND_ONCE, &old_port); | |
448 | if (ret != KERN_SUCCESS) { | |
449 | __CFSpinUnlock(&__CFAllMachPortsLock); | |
450 | CFRelease(memory); | |
451 | return NULL; | |
452 | } | |
453 | memory->_oldnotify = old_port; | |
454 | } | |
455 | __CFMachPortSetValid(memory); | |
456 | memory->_callout = callout; | |
457 | if (NULL != context) { | |
458 | memmove(&memory->_context, context, sizeof(CFMachPortContext)); | |
459 | memory->_context.info = context->retain ? (void *)context->retain(context->info) : context->info; | |
460 | } | |
461 | if (NULL == __CFAllMachPorts) { | |
462 | __CFAllMachPorts = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL); | |
463 | _CFDictionarySetCapacity(__CFAllMachPorts, 20); | |
464 | } | |
465 | CFDictionaryAddValue(__CFAllMachPorts, (void *)port, memory); | |
466 | __CFSpinUnlock(&__CFAllMachPortsLock); | |
467 | if (didCreateNotifyPort) { | |
468 | // __CFNotifyMachPort ends up in cache | |
469 | CFMachPortRef mp = CFMachPortCreateWithPort(kCFAllocatorDefault, __CFNotifyRawMachPort, __CFNotifyDeadMachPort, NULL, NULL); | |
470 | __CFMachPortSetHasReceive(mp); | |
471 | __CFNotifyMachPort = mp; | |
472 | } | |
473 | if (NULL != __CFNotifyMachPort) { | |
474 | // We do this so that it gets into each thread's run loop, since | |
475 | // we don't know which run loop is the main thread's, and that's | |
476 | // not necessarily the "right" one anyway. This won't happen for | |
477 | // the call which creates the __CFNotifyMachPort itself, but that's | |
478 | // OK since it will happen in the invocation of this function | |
479 | // from which that call was triggered. | |
480 | source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, __CFNotifyMachPort, -1000); | |
481 | CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes); | |
482 | CFRelease(source); | |
483 | } | |
484 | if (shouldFreeInfo) *shouldFreeInfo = false; | |
485 | return memory; | |
486 | } | |
487 | ||
488 | mach_port_t CFMachPortGetPort(CFMachPortRef mp) { | |
489 | CF_OBJC_FUNCDISPATCH0(__kCFMachPortTypeID, mach_port_t, mp, "machPort"); | |
490 | __CFGenericValidateType(mp, __kCFMachPortTypeID); | |
491 | __CFMachPortCheckForFork(); | |
492 | return mp->_port; | |
493 | } | |
494 | ||
495 | void CFMachPortGetContext(CFMachPortRef mp, CFMachPortContext *context) { | |
496 | __CFGenericValidateType(mp, __kCFMachPortTypeID); | |
497 | CFAssert1(0 == context->version, __kCFLogAssertion, "%s(): context version not initialized to 0", __PRETTY_FUNCTION__); | |
498 | __CFMachPortCheckForFork(); | |
499 | memmove(context, &mp->_context, sizeof(CFMachPortContext)); | |
500 | } | |
501 | ||
502 | void CFMachPortInvalidate(CFMachPortRef mp) { | |
503 | CF_OBJC_FUNCDISPATCH0(__kCFMachPortTypeID, void, mp, "invalidate"); | |
504 | __CFGenericValidateType(mp, __kCFMachPortTypeID); | |
505 | __CFMachPortCheckForFork(); | |
506 | if (!__CFMachPortIsDeallocing(mp)) { | |
507 | CFRetain(mp); | |
508 | } | |
509 | __CFSpinLock(&__CFAllMachPortsLock); | |
510 | if (NULL != __CFAllMachPorts) { | |
511 | CFDictionaryRemoveValue(__CFAllMachPorts, (void *)(mp->_port)); | |
512 | } | |
513 | __CFSpinUnlock(&__CFAllMachPortsLock); | |
514 | __CFMachPortLock(mp); | |
515 | if (__CFMachPortIsValid(mp)) { | |
516 | CFRunLoopSourceRef source; | |
517 | void *info; | |
518 | mach_port_t old_port = mp->_oldnotify; | |
519 | CFMachPortInvalidationCallBack callout = mp->_icallout; | |
520 | __CFMachPortUnsetValid(mp); | |
521 | __CFMachPortUnlock(mp); | |
522 | if (NULL != callout) { | |
523 | callout(mp, mp->_context.info); | |
524 | } | |
525 | __CFMachPortLock(mp); | |
526 | // For hashing and equality purposes, cannot get rid of _port here | |
527 | source = mp->_source; | |
528 | mp->_source = NULL; | |
529 | info = mp->_context.info; | |
530 | mp->_context.info = NULL; | |
531 | __CFMachPortUnlock(mp); | |
532 | if (NULL != mp->_context.release) { | |
533 | mp->_context.release(info); | |
534 | } | |
535 | if (NULL != source) { | |
536 | CFRunLoopSourceInvalidate(source); | |
537 | CFRelease(source); | |
538 | } | |
539 | // here we get rid of the previous notify port | |
540 | // [cjk - somehow it is the right thing to do to | |
541 | // hold this until this point, then deallocate it, | |
542 | // though I don't understand what that triggers | |
543 | // with respect to the send-once right, and I | |
544 | // doubt people are doing the right thing about | |
545 | // handling the "death" (CFMachPort included) of | |
546 | // the send-once right.] | |
547 | if (MACH_PORT_NULL != old_port) { | |
548 | mach_port_deallocate(mach_task_self(), old_port); | |
549 | } | |
550 | } else { | |
551 | __CFMachPortUnlock(mp); | |
552 | } | |
553 | if (!__CFMachPortIsDeallocing(mp)) { | |
554 | CFRelease(mp); | |
555 | } | |
556 | } | |
557 | ||
558 | Boolean CFMachPortIsValid(CFMachPortRef mp) { | |
559 | CF_OBJC_FUNCDISPATCH0(__kCFMachPortTypeID, Boolean, mp, "isValid"); | |
560 | __CFGenericValidateType(mp, __kCFMachPortTypeID); | |
561 | __CFMachPortCheckForFork(); | |
562 | return __CFMachPortIsValid(mp); | |
563 | } | |
564 | ||
565 | CFMachPortInvalidationCallBack CFMachPortGetInvalidationCallBack(CFMachPortRef mp) { | |
566 | __CFGenericValidateType(mp, __kCFMachPortTypeID); | |
567 | __CFMachPortCheckForFork(); | |
568 | return mp->_icallout; | |
569 | } | |
570 | ||
571 | void CFMachPortSetInvalidationCallBack(CFMachPortRef mp, CFMachPortInvalidationCallBack callout) { | |
572 | __CFGenericValidateType(mp, __kCFMachPortTypeID); | |
573 | __CFMachPortCheckForFork(); | |
574 | if (!__CFMachPortIsValid(mp) && NULL != callout) { | |
575 | callout(mp, mp->_context.info); | |
576 | } else { | |
577 | mp->_icallout = callout; | |
578 | } | |
579 | } | |
580 | ||
581 | /* Returns the number of messages queued for a receive port. */ | |
582 | CFIndex CFMachPortGetQueuedMessageCount(CFMachPortRef mp) { | |
583 | mach_port_status_t status; | |
584 | mach_msg_type_number_t num = MACH_PORT_RECEIVE_STATUS_COUNT; | |
585 | kern_return_t ret; | |
586 | ret = mach_port_get_attributes(mach_task_self(), mp->_port, MACH_PORT_RECEIVE_STATUS, (mach_port_info_t)&status, &num); | |
587 | return (KERN_SUCCESS != ret) ? 0 : status.mps_msgcount; | |
588 | } | |
589 | ||
590 | void CFMachPortInvalidateAll(void) { | |
591 | // This function has been removed from the public API; | |
592 | // it was a very bad idea to call it. | |
593 | } | |
594 | ||
595 | static mach_port_t __CFMachPortGetPort(void *info) { | |
596 | CFMachPortRef mp = info; | |
597 | __CFMachPortCheckForFork(); | |
598 | return mp->_port; | |
599 | } | |
600 | ||
601 | static void *__CFMachPortPerform(void *msg, CFIndex size, CFAllocatorRef allocator, void *info) { | |
602 | CFMachPortRef mp = info; | |
603 | void *context_info; | |
604 | void (*context_release)(const void *); | |
605 | __CFMachPortCheckForFork(); | |
606 | __CFMachPortLock(mp); | |
607 | if (!__CFMachPortIsValid(mp)) { | |
608 | __CFMachPortUnlock(mp); | |
609 | return NULL; | |
610 | } | |
611 | if (NULL != mp->_context.retain) { | |
612 | context_info = (void *)mp->_context.retain(mp->_context.info); | |
613 | context_release = mp->_context.release; | |
614 | } else { | |
615 | context_info = mp->_context.info; | |
616 | context_release = NULL; | |
617 | } | |
618 | __CFMachPortUnlock(mp); | |
619 | mp->_callout(mp, msg, size, mp->_context.info); | |
620 | if (context_release) { | |
621 | context_release(context_info); | |
622 | } | |
623 | return NULL; | |
624 | } | |
625 | ||
626 | CFRunLoopSourceRef CFMachPortCreateRunLoopSource(CFAllocatorRef allocator, CFMachPortRef mp, CFIndex order) { | |
627 | CFRunLoopSourceRef result = NULL; | |
628 | __CFGenericValidateType(mp, __kCFMachPortTypeID); | |
629 | __CFMachPortCheckForFork(); | |
630 | __CFMachPortLock(mp); | |
631 | #if 0 | |
632 | #warning CF: adding ref to receive right is disabled for now -- doesnt work in 1F | |
633 | if (!__CFMachPortHasReceive(mp)) { | |
634 | kern_return_t ret; | |
635 | ||
636 | // this fails in 1F with KERN_INVALID_VALUE -- only 0 and -1 are valid for delta | |
637 | ret = mach_port_mod_refs(mach_task_self(), mp->_port, MACH_PORT_RIGHT_RECEIVE, +1); | |
638 | if (KERN_SUCCESS != ret) { | |
639 | __CFMachPortUnlock(mp); | |
640 | return NULL; | |
641 | } | |
642 | __CFMachPortSetHasReceive(mp); | |
643 | } | |
644 | #endif | |
645 | if (NULL == mp->_source) { | |
646 | CFRunLoopSourceContext1 context; | |
647 | context.version = 1; | |
648 | context.info = (void *)mp; | |
649 | context.retain = (const void *(*)(const void *))CFRetain; | |
650 | context.release = (void (*)(const void *))CFRelease; | |
651 | context.copyDescription = (CFStringRef (*)(const void *))__CFMachPortCopyDescription; | |
652 | context.equal = (Boolean (*)(const void *, const void *))__CFMachPortEqual; | |
653 | context.hash = (CFHashCode (*)(const void *))__CFMachPortHash; | |
654 | context.getPort = __CFMachPortGetPort; | |
655 | context.perform = __CFMachPortPerform; | |
656 | mp->_source = CFRunLoopSourceCreate(allocator, order, (CFRunLoopSourceContext *)&context); | |
657 | } | |
658 | if (NULL != mp->_source) { | |
659 | result = (CFRunLoopSourceRef)CFRetain(mp->_source); | |
660 | } | |
661 | __CFMachPortUnlock(mp); | |
662 | return result; | |
663 | } | |
664 | ||
665 | #endif /* __MACH__ */ | |
666 |