forked from swiftlang/swift-corelibs-foundation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCFMachPort_Lifetime.c
293 lines (257 loc) · 11.4 KB
/
CFMachPort_Lifetime.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
/*
CFMachPort_Lifetime.h
Copyright (c) 1998-2018, Apple Inc. and the Swift project authors
All of the functions in this file exist to orchestrate the exact time/circumstances we decrement the port references.
*/
#include "CFMachPort_Lifetime.h"
#include <mach/mach.h>
#include "CFInternal.h"
// Records information relevant for cleaning up after a given mach port. Here's
// a summary of its life cycle:
// A) _cfmp_deallocation_record created and stored in _cfmp_records
// This means a dispatch source has been created to track dead name for
// the port.
// B) There is no record for a given port in _cfmp_records
// This means either: the dispatch source above has been cancelled, or that
// there was a never a dispatch source in the first place.
//
// For pure CFMachPorts with no Foundation NSPort references, the flags doSend
// and doReceive record the kind of rights a given port should clear up when
// the cancel handler is called.
//
// The reason for this is that the Deallocate of a CFMachPort can happen before
// the cancel handler has been called, and historically the deallocate was the
// where the rights would be decremented.
//
// When NSPort's are involved, we track a few other bits for exactly the same
// reasons. The reason these are tracked separately is out of an abundance of
// caution - by all reads, the two mechanisms do not overlap, but tracking them
// separate is more debugable.
//
typedef struct {
mach_port_t port; // which port (contributes to identity)
uint8_t client; // which subsystem is tracking (contributes to identity)
// should be kept in sync with MAX_CLIENTS constants below
uint8_t inSet:1; // helps detect invariant violations
uint8_t deallocated:1;
uint8_t invalidated:1;
uint8_t doSend:1;
uint8_t doReceive:1;
// indicates that there is additional invalidation requested by NSMachPort
uint8_t nsportIsInterested:1;
uint8_t nsportDoSend:1;
uint8_t nsportDoReceive:1;
} _cfmp_deallocation_record;
#define MAX_CLIENTS 256
#define MAX_CLIENTS_BITS 8
#pragma mark - Port Right Modification
CF_INLINE void _cfmp_mod_refs(mach_port_t const port, const Boolean doSend, const Boolean doReceive) {
// NOTE: do receive right first per: https://howto.apple.com/wiki/pages/r853A7H2j/Mach_Ports_and_You.html
if (doReceive) {
mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_RECEIVE, -1);
}
if (doSend) {
mach_port_deallocate(mach_task_self(), port);
}
}
#pragma mark - Logging
CF_BREAKPOINT_FUNCTION(void _CFMachPortDeallocationFailure(void));
static void _cfmp_log_failure(char const *const msg, _cfmp_deallocation_record *const pr, _CFMPLifetimeClient const client, mach_port_t const port) {
if (pr) {
_cfmp_deallocation_record const R = *pr;
os_log_error(OS_LOG_DEFAULT, "*** %{public}s break on '_CFMachPortDeallocationFailure' to debug: {p:%{private}d c:%d is:%d <i:%d,d:%d> s:%d,r:%d nsi:%d,nss:%d,nsr:%d - ic:%d,ip:%d}", msg, R.port, R.client, R.inSet, R.invalidated, R.deallocated, R.doSend, R.doReceive, R.nsportIsInterested, R.nsportDoSend, R.nsportDoReceive, client, port);
} else {
os_log_error(OS_LOG_DEFAULT, "*** %{public}s break on '_CFMachPortDeallocationFailure' to debug: {null - ic:%d,ip:%d}", msg, client, port);
}
_CFMachPortDeallocationFailure();
}
#pragma mark - _cfmp_deallocation_record CFSet Callbacks
// Various CFSet callbacks for _cfmp_deallocation_record
static CFTypeRef _cfmp_deallocation_record_retain(CFAllocatorRef allocator, CFTypeRef const cf) {
_cfmp_deallocation_record *const pr = (_cfmp_deallocation_record *)cf;
if (pr->inSet) {
HALT_MSG("refcnt overflow");
}
pr->inSet = 1;
return pr;
}
static Boolean _cfmp_equal(void const *const value1, void const *const value2) {
Boolean equal = false;
if (value1 == value2) {
equal = true;
} else if (value1 && value2){
_cfmp_deallocation_record const R1 = *(_cfmp_deallocation_record *)value1;
_cfmp_deallocation_record const R2 = *(_cfmp_deallocation_record *)value2;
equal = R1.port == R2.port && R1.client == R2.client;
}
return equal;
}
static CFHashCode _cfmp_hash(void const *const value) {
CFHashCode hash = 0;
if (value) {
_cfmp_deallocation_record const R = *(_cfmp_deallocation_record *)value;
hash = _CFHashInt(R.port << MAX_CLIENTS_BITS | R.client);
}
return hash;
}
static void _cfmp_deallocation_record_release(CFAllocatorRef const allocator, void const *const value) {
_cfmp_deallocation_record *pr = (_cfmp_deallocation_record *)value;
if (!pr->inSet) {
_cfmp_log_failure("Freeing a record not in the set", pr, pr->client, pr->port);
}
free((_cfmp_deallocation_record *)value);
}
static CFStringRef _cfmp_copy_description(const void *value) {
CFStringRef s = CFSTR("{null}");
if (value) {
_cfmp_deallocation_record const R = *(_cfmp_deallocation_record *)value;
s = CFStringCreateWithFormat(NULL, NULL, CFSTR("{p:%d c:%d is:%d <i:%d,d:%d> s:%d,r:%d nsi:%d,nss:%d,nsr:%d}"), R.port, R.client, R.inSet, R.invalidated, R.deallocated, R.doSend, R.doReceive, R.nsportIsInterested, R.nsportDoSend, R.nsportDoReceive);
}
return s;
}
#pragma mark - Deallocation Records
// Pending deallocations/invalidations are recorded in the global set returned by _cfmp_records, whose access should be protected by _cfmp_records_lock
static os_unfair_lock _cfmp_records_lock = OS_UNFAIR_LOCK_INIT;
static CFMutableSetRef _cfmp_records(void) {
static CFMutableSetRef oRecords;
static dispatch_once_t oGuard;
dispatch_once(&oGuard, ^{
CFSetCallBacks const cb = {
.version = 0,
.retain = _cfmp_deallocation_record_retain,
.release = _cfmp_deallocation_record_release,
.copyDescription = _cfmp_copy_description,
.equal = _cfmp_equal,
.hash = _cfmp_hash
};
oRecords = CFSetCreateMutable(NULL, 16, &cb);
});
return oRecords;
};
CF_INLINE _cfmp_deallocation_record *const _cfmp_find_record_for_port(CFSetRef const records, _CFMPLifetimeClient const client, mach_port_t const port) {
_cfmp_deallocation_record const lookup = {.port = port, .client = client};
_cfmp_deallocation_record *const pr = (_cfmp_deallocation_record *)CFSetGetValue(records, &lookup);
return pr;
}
#pragma mark - Lifetime Management
CF_PRIVATE void _cfmp_cleanup(_cfmp_deallocation_record const R) {
_cfmp_mod_refs(R.port, R.doSend, R.doReceive);
if (R.nsportIsInterested) {
_cfmp_mod_refs(R.port, R.nsportDoSend, R.nsportDoReceive);
}
}
/// Records that a given mach_port has been deallocated.
void _cfmp_record_deallocation(_CFMPLifetimeClient const client, mach_port_t const port, Boolean const doSend, Boolean const doReceive) {
if (port == MACH_PORT_NULL) { return; }
if (doSend == false && doReceive == false) { return; }
// now that we know we're not a no-op, look for an existing deallocation record
CFMutableSetRef records = _cfmp_records();
Boolean cleanupNow = false;
_cfmp_deallocation_record R = {0};
os_unfair_lock_lock(&_cfmp_records_lock);
_cfmp_deallocation_record *const pr = _cfmp_find_record_for_port(records, client, port);
if (pr) {
if (pr->invalidated) {
// it's already been invalidated, so can tidy up now
R = *(_cfmp_deallocation_record *)pr;
CFSetRemoveValue(records, pr);
cleanupNow = true;
} else {
// we're expecting invalidation, record that we want clean up doSend/Receive for later
pr->deallocated = true;
pr->doSend = doSend;
pr->doReceive = doReceive;
}
} else {
R.port = port;
R.doSend = doSend;
R.doReceive = doReceive;
cleanupNow = true;
}
os_unfair_lock_unlock(&_cfmp_records_lock);
if (cleanupNow) {
_cfmp_cleanup(R);
}
}
void _cfmp_record_intent_to_invalidate(_CFMPLifetimeClient const client, mach_port_t const port) {
if (port == MACH_PORT_NULL) { return; }
_cfmp_deallocation_record *pr = calloc(1, sizeof(_cfmp_deallocation_record));
if (pr == NULL) {
HALT_MSG("Unable to allocate mach_port deallocation record");
}
pr->port = port;
pr->client = client;
CFMutableSetRef const records = _cfmp_records();
os_unfair_lock_lock(&_cfmp_records_lock);
CFSetAddValue(records, pr);
os_unfair_lock_unlock(&_cfmp_records_lock);
}
void _cfmp_source_invalidated(_CFMPLifetimeClient const client, mach_port_t port) {
Boolean cleanupNow = false;
_cfmp_deallocation_record R = {0};
CFMutableSetRef const records = _cfmp_records();
os_unfair_lock_lock(&_cfmp_records_lock);
_cfmp_deallocation_record *pr = _cfmp_find_record_for_port(records, client, port);
if (pr == NULL) {
_cfmp_log_failure("not expecting invalidation", pr, client, port);
} else {
if (pr->deallocated) {
cleanupNow = true;
R = *(_cfmp_deallocation_record *)pr;
CFSetRemoveValue(records, pr);
} else {
pr->invalidated = true;
}
}
os_unfair_lock_unlock(&_cfmp_records_lock);
if (cleanupNow) {
_cfmp_cleanup(R);
}
}
void _cfmp_record_nsmachport_is_interested(_CFMPLifetimeClient const client, mach_port_t const port) {
if (port == MACH_PORT_NULL) { return; }
// now that we know we're not a no-op, look for an existing deallocation record
CFMutableSetRef records = _cfmp_records();
os_unfair_lock_lock(&_cfmp_records_lock);
_cfmp_deallocation_record *const pr = _cfmp_find_record_for_port(records, client, port);
if (pr) {
// we're expecting invalidation. record that nsport is interested.
pr->nsportIsInterested = true;
}
os_unfair_lock_unlock(&_cfmp_records_lock);
}
void _cfmp_record_nsmachport_deallocation(_CFMPLifetimeClient const client, mach_port_t const port, Boolean const doSend, Boolean const doReceive) {
if (port == MACH_PORT_NULL) { return; }
if (doSend == false && doReceive == false) { return; }
CFMutableSetRef records = _cfmp_records();
Boolean cleanupNow = false;
_cfmp_deallocation_record R = {0};
os_unfair_lock_lock(&_cfmp_records_lock);
_cfmp_deallocation_record *const pr = _cfmp_find_record_for_port(records, client, port);
if (pr == NULL) {
R.port = port;
R.nsportDoSend = doSend;
R.nsportDoReceive = doReceive;
cleanupNow = true;
} else {
// we're expecting invalidation. record that we want to doSend/Receive for nsport later
// but first make sure we were expecting an NSMachPort at all
if (!pr->nsportIsInterested) {
_cfmp_log_failure("setting nsport state - when its not interested", pr, client, port);
} else if (pr->invalidated) {
// it's already been invalidated, so can tidy up now
R = *(_cfmp_deallocation_record *)pr;
CFSetRemoveValue(records, pr);
cleanupNow = true;
} else {
// we're expecting invalidation, record that we want clean up doSend/Receive for later
pr->deallocated = true;
pr->nsportDoSend = doSend;
pr->nsportDoReceive = doReceive;
}
}
os_unfair_lock_unlock(&_cfmp_records_lock);
if (cleanupNow) {
_cfmp_cleanup(R);
}
}