-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfreebsd-libxpc-libdispatch-mach-spike.html
More file actions
643 lines (557 loc) · 43 KB
/
Copy pathfreebsd-libxpc-libdispatch-mach-spike.html
File metadata and controls
643 lines (557 loc) · 43 KB
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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>libdispatch Mach backend spike for freebsd-libxpc</title>
<style>
:root {
--fg: #1a1a1a;
--fg-muted: #555;
--bg: #fafaf7;
--accent: #b8472a;
--accent-soft: #f3e7df;
--border: #d8d4c8;
--code-bg: #f0ece2;
--table-stripe: #f4efe5;
--warn: #8a5a00;
--good: #2d6f3b;
--bad: #a23030;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body { font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, sans-serif; color: var(--fg); background: var(--bg); line-height: 1.55; margin: 0; padding: 0; }
.wrap { max-width: 880px; margin: 0 auto; padding: 48px 32px 96px; }
h1 { font-size: 2.1rem; line-height: 1.2; margin: 0 0 8px; letter-spacing: -0.01em; }
h2 { font-size: 1.45rem; margin: 56px 0 12px; padding-top: 18px; border-top: 2px solid var(--border); letter-spacing: -0.005em; }
h3 { font-size: 1.15rem; margin: 32px 0 10px; color: var(--accent); }
h4 { font-size: 1rem; margin: 24px 0 6px; color: var(--fg); }
p { margin: 0 0 14px; }
ul, ol { margin: 0 0 14px 22px; padding: 0; }
li { margin-bottom: 4px; }
code, pre { font-family: "SF Mono", Menlo, Consolas, monospace; background: var(--code-bg); color: var(--fg); }
code { padding: 1px 5px; border-radius: 3px; font-size: 0.92em; }
pre { padding: 14px 18px; border-radius: 4px; overflow-x: auto; font-size: 0.86rem; line-height: 1.45; margin: 0 0 18px; }
pre code { background: transparent; padding: 0; font-size: inherit; }
a { color: var(--accent); }
a:hover { text-decoration: underline; }
.lede { font-size: 1.05rem; color: var(--fg-muted); margin-bottom: 32px; }
table { width: 100%; border-collapse: collapse; margin: 14px 0 22px; font-size: 0.9rem; }
th, td { text-align: left; padding: 8px 10px; border: 1px solid var(--border); vertical-align: top; }
th { background: var(--accent-soft); font-weight: 600; }
tr:nth-child(even) td { background: var(--table-stripe); }
.callout { border-left: 3px solid var(--accent); background: var(--accent-soft); padding: 14px 18px; margin: 18px 0 22px; border-radius: 0 4px 4px 0; }
.callout p:last-child { margin-bottom: 0; }
.callout-warn { border-left-color: var(--warn); background: #fbf3df; }
.callout-good { border-left-color: var(--good); background: #e8f1e3; }
.callout-bad { border-left-color: var(--bad); background: #f7e0e0; }
.pill { display: inline-block; font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; padding: 2px 8px; border-radius: 10px; margin-right: 8px; }
.pill-good { background: #d6ead0; color: var(--good); }
.pill-warn { background: #f4dfbf; color: var(--warn); }
.pill-bad { background: #f0c8c8; color: var(--bad); }
.pill-neutral { background: #ddd; color: #333; }
.toc { background: white; border: 1px solid var(--border); border-radius: 4px; padding: 18px 24px 14px 36px; margin: 0 0 36px; font-size: 0.95rem; }
.toc h2 { margin: 0 0 8px; padding-top: 0; border-top: none; font-size: 1rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--fg-muted); margin-left: -14px; }
.toc ol { margin: 0 0 0 6px; }
.toc li { margin-bottom: 4px; }
.toc a { color: var(--fg); text-decoration: none; }
.toc a:hover { text-decoration: underline; }
.footnote { font-size: 0.85rem; color: var(--fg-muted); border-top: 1px solid var(--border); margin-top: 48px; padding-top: 16px; }
.verdict { font-size: 1.1rem; font-weight: 600; margin: 8px 0 14px; }
.verdict-go { color: var(--good); }
.verdict-warn { color: var(--warn); }
.verdict-no { color: var(--bad); }
@media (max-width: 600px) { .wrap { padding: 24px 18px 64px; } table { font-size: 0.82rem; } }
</style>
</head>
<body>
<div class="wrap">
<h1>libdispatch Mach backend spike for <code>freebsd-libxpc</code></h1>
<p class="lede">Spike answering: do we really need to patch libdispatch to support <code>DISPATCH_SOURCE_TYPE_MACH_RECV</code> on FreeBSD? Who actually uses it? What happens if we don't? And could simpler per-consumer modifications work instead? Empirical grep over the local NextBSD, ravynOS, freebsd-launchd, and gershwin-developer trees plus a trade-off matrix for three implementation paths.</p>
<div class="toc">
<h2>Contents</h2>
<ol>
<li><a href="#why">Why libdispatch needs Mach support</a></li>
<li><a href="#who">Who actually uses <code>DISPATCH_SOURCE_TYPE_MACH_RECV</code></a></li>
<li><a href="#nothing">What happens if we don't add Mach support</a></li>
<li><a href="#options">Three options for adding it</a></li>
<li><a href="#all-apps">All in-scope apps — dispatch + Mach-dispatch requirements</a></li>
<li><a href="#install-path">Install path: <code>/System</code> vs unix paths</a></li>
<li><a href="#recommendation">Recommendation</a></li>
<li><a href="#refs">References</a></li>
</ol>
</div>
<h2 id="why">1. Why libdispatch needs Mach support</h2>
<p>libdispatch provides <strong>asynchronous event delivery</strong> through a uniform "source" abstraction: a consumer creates a <code>dispatch_source_t</code> for some event type (timer, fd-readable, signal, kqueue note, Mach message arrival), registers an event handler block, and the source delivers events on a target queue with no polling code in the consumer.</p>
<p><code>DISPATCH_SOURCE_TYPE_MACH_RECV</code> is the source type for "a message arrived on this Mach port." Consumer code looks like:</p>
<pre><code>port = mach_reply_port();
src = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, port, 0, queue);
dispatch_source_set_event_handler(src, ^{
/* called when a message arrives — drain it with mach_msg(MACH_RCV_MSG) */
});
dispatch_resume(src);</code></pre>
<p>On Apple platforms libdispatch hooks this up via <code>EVFILT_MACHPORT</code> (a Darwin-only kevent filter) so the kernel signals the dispatch worker when a message hits the port. On FreeBSD, with <code>HAVE_MACH</code> off, the source type symbol doesn't exist at all — <code>_dispatch_source_type_mach_recv</code> is gated by <code>#if HAVE_MACH</code> in <code>src/event/event_kevent.c:3249</code> with no fallback.</p>
<h2 id="who">2. Who actually uses <code>DISPATCH_SOURCE_TYPE_MACH_RECV</code></h2>
<p>Earlier loose reading of the codebase suggested "only libxpc cares about this." Empirical grep across all local trees shows that's wrong:</p>
<table>
<thead><tr><th>Component</th><th>File / line</th><th>Type</th><th>What for</th></tr></thead>
<tbody>
<tr>
<td><strong>libxpc</strong></td>
<td><code>lib/libxpc/xpc_connection.c:214</code></td>
<td>MACH_RECV</td>
<td>Async event delivery on a connection's local receive port</td>
</tr>
<tr>
<td><strong>libnotify</strong></td>
<td><code>lib/libnotify/notify_client.c:355</code></td>
<td>MACH_RECV</td>
<td>Notification reception via the global Mach notify port</td>
</tr>
<tr>
<td><strong>notifyd</strong></td>
<td><code>usr.sbin/notifyd/notifyd.c:1230</code></td>
<td>MACH_RECV</td>
<td>Daemon's server-port receive loop</td>
</tr>
<tr>
<td>notifyd</td>
<td><code>usr.sbin/notifyd/notify_proc.c:352</code></td>
<td>MACH_SEND</td>
<td>Dead-name and send-possible notifications on client ports</td>
</tr>
<tr>
<td>notifyutil</td>
<td><code>usr.bin/notifyutil/notifyutil.c:374</code></td>
<td>MACH_RECV</td>
<td>CLI tool's notification reception</td>
</tr>
<tr>
<td><strong>SystemConfiguration framework</strong></td>
<td><code>configd/SystemConfiguration.fproj/SCNetworkConnection.c:2396</code></td>
<td>MACH_RECV</td>
<td>Client subscribing to SC connection-state notifications</td>
</tr>
<tr>
<td>SystemConfiguration framework</td>
<td><code>configd/SystemConfiguration.fproj/SCDNotifierInformViaCallback.c:610</code></td>
<td>MACH_RECV</td>
<td>SCDynamicStore change callbacks via Mach</td>
</tr>
</tbody>
</table>
<div class="callout callout-warn">
<p><strong>At least 5 distinct components in our porting scope use <code>DISPATCH_SOURCE_TYPE_MACH_RECV</code>:</strong> libxpc, libnotify, notifyd, notifyutil, SystemConfiguration framework. Each consumer that arrives later (mDNSResponder bridges, IPConfiguration helpers, future libxpc consumers) adds to this count. The expectation "Mach-receive sources Just Work" is baked into modern Apple-style C IPC code.</p>
</div>
<h2 id="nothing">3. What happens if we don't add Mach support</h2>
<p>With <code>HAVE_MACH=0</code> on FreeBSD (the current default for swift-corelibs-libdispatch on non-Darwin), the symbol <code>_dispatch_source_type_mach_recv</code> simply does not exist in <code>libdispatch.so</code>. Consequences for each consumer:</p>
<table>
<thead><tr><th>Consumer</th><th>Effect at build time</th><th>Effect at runtime</th></tr></thead>
<tbody>
<tr>
<td>libxpc</td>
<td><span class="pill pill-bad">FAILS</span> to link — undefined reference to <code>_dispatch_source_type_mach_recv</code></td>
<td>library never produced</td>
</tr>
<tr>
<td>libnotify</td>
<td><span class="pill pill-bad">FAILS</span> same way</td>
<td>library never produced</td>
</tr>
<tr>
<td>notifyd / notifyutil</td>
<td><span class="pill pill-bad">FAILS</span></td>
<td>daemon / CLI tool never produced</td>
</tr>
<tr>
<td>SystemConfiguration framework</td>
<td><span class="pill pill-bad">FAILS</span> — <code>SCNetworkConnection</code> and <code>SCDNotifierInformViaCallback</code> both reference the symbol</td>
<td>framework can't be built — clients can't link <code>-lSystemConfiguration</code></td>
</tr>
<tr>
<td>Apple launchd (clean <code>launchd-842.92.1</code> import)</td>
<td><span class="pill pill-good">unaffected</span> — the 2014 source predates dispatch Mach sources; uses its own <code>mach_msg</code> loop in <code>runtime.c</code></td>
<td>fine</td>
</tr>
<tr>
<td>asl / syslogd / aslmanager / libasl</td>
<td><span class="pill pill-good">unaffected</span> — pure C, no libdispatch, no Mach</td>
<td>fine</td>
</tr>
</tbody>
</table>
<p><strong>Net effect of doing nothing:</strong> libxpc, libnotify, notifyd, notifyutil, and the SystemConfiguration framework cannot be built at all. The Apple-source daemon ecosystem (other than the 2014 launchd and the legacy ASL daemons) stops being available.</p>
<p>That makes the libdispatch Mach work effectively <strong>load-bearing for the whole post-Phase-B roadmap</strong>, not just a libxpc concern.</p>
<h2 id="options">4. Three options for adding it</h2>
<h3>Option 1 — Patch libdispatch with a new FreeBSD-Mach backend</h3>
<p>Add a new <code>src/event/event_mach_freebsd.c</code> to swift-corelibs-libdispatch, gated on <code>__FreeBSD__ && HAVE_LIBMACH</code>. Defines <code>_dispatch_source_type_mach_recv</code> (and <code>_send</code>). Implementation: per-source polling thread that calls <code>mach_msg_trap(MACH_RCV_MSG | MACH_RCV_TIMEOUT, timeout=small)</code> and dispatches received messages to the source's target queue. Ship as a new patch in <code>gershwin-developer/Library/Patches/</code> alongside the existing <code>swift-corelibs-libdispatch.patch</code>.</p>
<table>
<tbody>
<tr><td><strong>Code added</strong></td><td>~500-1000 lines in one new file</td></tr>
<tr><td><strong>Code modified</strong></td><td>1 line in CMakeLists.txt</td></tr>
<tr><td><strong>Consumers affected</strong></td><td>0 — consumers write vanilla <code>dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, ...)</code> code</td></tr>
<tr><td><strong>Upstream coordination</strong></td><td>One patch to gershwin-developer; PR-able to swift-corelibs-libdispatch long-term</td></tr>
<tr><td><strong>Cost when adding consumer N+1</strong></td><td>zero — new consumer just uses the existing source type</td></tr>
</tbody>
</table>
<p>Pros: aligned with Apple's design (libxpc, libnotify, notifyd, SystemConfiguration framework code stays as-is); one place to fix bugs, optimize, profile. Cons: more total code than a single per-consumer patch; touches a shared library that other gershwin components also link.</p>
<h3>Option 2 — Patch each consumer to use its own polling thread</h3>
<p>Modify libxpc, libnotify, notifyd, notifyutil, and SystemConfiguration framework to skip <code>dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, ...)</code> and instead spawn a polling thread that calls <code>mach_msg_trap</code> and re-enqueues messages onto the consumer's target queue via <code>dispatch_async</code>. libdispatch stays vanilla.</p>
<table>
<tbody>
<tr><td><strong>Code added per consumer</strong></td><td>~100-200 lines for the polling thread + dispatch_async glue</td></tr>
<tr><td><strong>Total across 5 consumers</strong></td><td>~500-1000 lines</td></tr>
<tr><td><strong>Consumers affected</strong></td><td>5 — each gets its own polling thread implementation</td></tr>
<tr><td><strong>Upstream coordination</strong></td><td>5 patches in 5 different projects; some sit in Apple-source-imports we re-pull on each version bump — rebase burden</td></tr>
<tr><td><strong>Cost when adding consumer N+1</strong></td><td>+100-200 lines per new consumer — scales linearly with daemon count</td></tr>
</tbody>
</table>
<p>Pros: libdispatch stays vanilla (no gershwin patch); each consumer is self-contained; works incrementally (port one daemon at a time). Cons: diverges from Apple's design in 5+ places; per-consumer thread overhead (each connection or each daemon has its own polling thread); patches to verbatim Apple imports complicate re-imports; scales linearly with new ports.</p>
<h3>Option 3 — Build a small Mach-to-dispatch bridge library</h3>
<p>Ship a new tiny library (e.g. <code>libmach_dispatch.so</code>) that exposes a clean API like:</p>
<pre><code>typedef struct mach_dispatch_source *mach_dispatch_source_t;
mach_dispatch_source_t
mach_dispatch_source_create_recv(mach_port_t port, dispatch_queue_t queue,
void (^handler)(mach_dispatch_source_t));
void mach_dispatch_source_resume(mach_dispatch_source_t src);
void mach_dispatch_source_cancel(mach_dispatch_source_t src);
void mach_dispatch_source_release(mach_dispatch_source_t src);</code></pre>
<p>Implementation: polling thread that calls <code>mach_msg_trap</code>, dispatches via <code>dispatch_async</code>. libdispatch stays vanilla. Consumers (libxpc, libnotify, notifyd, etc.) substitute <code>mach_dispatch_source_create_recv</code> for <code>dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, ...)</code>.</p>
<table>
<tbody>
<tr><td><strong>Code added in the bridge</strong></td><td>~300-500 lines (smaller than libdispatch backend since we own a simpler API)</td></tr>
<tr><td><strong>Code modified per consumer</strong></td><td>~10-20 lines to switch API calls</td></tr>
<tr><td><strong>Total across 5 consumers</strong></td><td>~50-100 lines of consumer changes + 300-500 lines of bridge</td></tr>
<tr><td><strong>Consumers affected</strong></td><td>5 — small API substitution each</td></tr>
<tr><td><strong>Upstream coordination</strong></td><td>Self-contained library in our repo + small patches to each consumer (still patches Apple-source imports)</td></tr>
<tr><td><strong>Cost when adding consumer N+1</strong></td><td>~10-20 lines per new consumer</td></tr>
</tbody>
</table>
<p>Pros: smaller bridge code than libdispatch patch (we own a simpler API); libdispatch stays vanilla; consumers don't have to know polling-thread details. Cons: still patches Apple-source imports (rebase burden); diverges from Apple's API names (<code>mach_dispatch_source_create_recv</code> vs <code>dispatch_source_create</code>); new consumers have to learn our bridge API.</p>
<h3>Trade-off matrix</h3>
<table>
<thead><tr><th>Dimension</th><th>Opt 1 patch libdispatch</th><th>Opt 2 per-consumer poll</th><th>Opt 3 bridge library</th></tr></thead>
<tbody>
<tr><td>Total new code (1st cut)</td><td>~500-1000 lines, 1 file</td><td>~500-1000 lines across 5 consumers</td><td>~350-600 lines (bridge + consumer changes)</td></tr>
<tr><td>Apple-source-imports left untouched</td><td><span class="pill pill-good">yes</span></td><td><span class="pill pill-bad">no</span> — patches verbatim Apple imports</td><td><span class="pill pill-bad">no</span> — patches verbatim Apple imports</td></tr>
<tr><td>Rebase burden on Apple re-imports</td><td>low</td><td>high — 5+ patches to rebase</td><td>medium — 5+ small patches</td></tr>
<tr><td>Diverges from Apple's API</td><td><span class="pill pill-good">no</span></td><td><span class="pill pill-good">no (uses different internal mechanism, same Apple call site shape)</span></td><td><span class="pill pill-bad">yes</span> — consumer uses our API names</td></tr>
<tr><td>Cost per future consumer</td><td><span class="pill pill-good">~0</span></td><td><span class="pill pill-bad">+100-200 lines</span></td><td><span class="pill pill-warn">+10-20 lines</span></td></tr>
<tr><td>Polling thread count</td><td>1 per dispatch source</td><td>1 per consumer per source</td><td>1 per bridge source</td></tr>
<tr><td>libdispatch stays vanilla</td><td><span class="pill pill-bad">no</span> — gershwin patch</td><td><span class="pill pill-good">yes</span></td><td><span class="pill pill-good">yes</span></td></tr>
<tr><td>Smoke-testable in isolation</td><td><span class="pill pill-good">yes</span></td><td>each consumer separately</td><td><span class="pill pill-good">yes</span></td></tr>
</tbody>
</table>
<h2 id="all-apps">5. All in-scope apps — dispatch + Mach-dispatch requirements</h2>
<p>Comprehensive matrix covering every component we plan to port (or have already ported) to FreeBSD as part of the broader Apple-source-services roadmap. Counts are from <code>grep -rE "dispatch_[a-z_]*\("</code> over the local NextBSD, ravynOS, freebsd-launchd, and freebsd-launchd-mach trees; DiskArbitration counts are from spot-reads of <code>apple-oss-distributions/DiskArbitration</code> source on GitHub (no local clone yet).</p>
<table>
<thead><tr><th>Component</th><th>Plain libdispatch use</th><th>Mach dispatch (<code>SOURCE_TYPE_MACH_RECV</code>/<code>_SEND</code>)</th><th>Porting status</th></tr></thead>
<tbody>
<tr><td colspan="4"><strong>Libraries we build/ship</strong></td></tr>
<tr>
<td><code>mach.ko</code> (kernel)</td>
<td><span class="pill pill-good">none</span> — kernel can't link userland libdispatch</td>
<td><span class="pill pill-good">n/a</span></td>
<td><span class="pill pill-good">done</span> — Phase B + Tier 1</td>
</tr>
<tr>
<td><code>libmach</code></td>
<td><span class="pill pill-good">none</span> — pure syscall wrappers</td>
<td><span class="pill pill-good">n/a</span></td>
<td><span class="pill pill-good">done</span> — Phase C1</td>
</tr>
<tr>
<td><strong><code>libdispatch</code> itself</strong> (swift-corelibs-libdispatch + Mach backend patch)</td>
<td>3956 internal refs — <em>this is the library</em></td>
<td><strong>required to provide</strong> the symbol — this spike's whole point</td>
<td><span class="pill pill-warn">Phase 0</span> — ship Mach backend as gershwin patch</td>
</tr>
<tr>
<td><strong><code>libxpc</code></strong></td>
<td>moderate — 33 calls, 14 block literals, semaphores + queues + async</td>
<td><strong>1 RECV</strong> in <code>xpc_connection.c:214</code></td>
<td><span class="pill pill-warn">Phase 2-4</span></td>
</tr>
<tr>
<td><code>liblaunch</code></td>
<td>10 calls — light (queue create + retain/release only)</td>
<td><span class="pill pill-good">none</span></td>
<td>existing legacy <code>launch_data_t</code> client; minimal forward port</td>
</tr>
<tr>
<td><code>libnotify</code></td>
<td>35 calls — moderate (2 sources, 13 blocks)</td>
<td><strong>1 RECV</strong> in <code>notify_client.c:355</code></td>
<td>Phase 7+ (after libxpc)</td>
</tr>
<tr>
<td><code>libasl</code></td>
<td>50 calls — moderate (1 source, 23 blocks)</td>
<td><span class="pill pill-good">none</span> — uses READ source type, not Mach</td>
<td>Phase 7+</td>
</tr>
<tr>
<td><code>libSystemConfiguration</code> (configd's client lib)</td>
<td>5 calls — light</td>
<td><span class="pill pill-good">none directly</span> — transitively via <code>SystemConfiguration.framework</code></td>
<td>Phase 6</td>
</tr>
<tr>
<td><strong><code>SystemConfiguration.framework</code></strong> (configd's API surface)</td>
<td><strong>heavy</strong> — 124 calls, 60 blocks, 17 semaphores, 4 sources</td>
<td><strong>2 RECV</strong> (<code>SCNetworkConnection.c:2396</code>, <code>SCDNotifierInformViaCallback.c:610</code>)</td>
<td>Phase 6</td>
</tr>
<tr>
<td><strong><code>DiskArbitration.framework</code></strong></td>
<td>moderate — <code>DASessionSetDispatchQueue</code> is core API; dual-mode CFRunLoop / dispatch-queue</td>
<td><strong>at least 1 RECV</strong> in <code>DASession.c</code> (from <code>DASessionSetDispatchQueue</code> impl on GitHub)</td>
<td>Phase 8+ — deferred; may keep a sockets-based alternative implementation that sidesteps Mach IPC entirely</td>
</tr>
<tr><td colspan="4"><strong>Daemons</strong></td></tr>
<tr>
<td>Apple <code>launchd-842.92.1</code> (clean import for Phase 5)</td>
<td>~10 real calls after filtering (dispatch_main, dispatch_once, 1 PROC source)</td>
<td><span class="pill pill-good">none</span> — 2014 source predates dispatch Mach migration; uses its own <code>mach_msg</code> loop</td>
<td><span class="pill pill-warn">Phase 5</span></td>
</tr>
<tr>
<td><code>freebsd-launchd</code> minimal rewrite (current shipping)</td>
<td>27 calls — SIGNAL + READ + TIMER sources, dispatch_main</td>
<td><span class="pill pill-good">none</span> — AF_UNIX IPC, not Mach</td>
<td><span class="pill pill-good">shipping today</span></td>
</tr>
<tr>
<td><code>launchctl</code></td>
<td><span class="pill pill-good">none</span></td>
<td><span class="pill pill-good">none</span></td>
<td>Phase 5 (with launchd)</td>
</tr>
<tr>
<td><code>asl</code> (Apple syslogd-replacement daemon)</td>
<td><strong>heavy</strong> — 145 calls, 16 sources (TIMER/READ/SIGNAL/VNODE), 66 blocks</td>
<td><span class="pill pill-good">none</span> — uses TIMER/READ/SIGNAL/VNODE source types only</td>
<td>Phase 7+</td>
</tr>
<tr>
<td><code>syslogd</code> (FreeBSD native)</td>
<td><span class="pill pill-good">none</span> — pure FreeBSD, no dispatch</td>
<td><span class="pill pill-good">none</span></td>
<td>n/a — we're replacing this with Apple's <code>asl</code>-aware version</td>
</tr>
<tr>
<td><code>aslmanager</code></td>
<td>4 calls — light (queue + dispatch_main)</td>
<td><span class="pill pill-good">none</span></td>
<td>Phase 7+ (with asl)</td>
</tr>
<tr>
<td><code>notifyd</code></td>
<td><strong>heavy</strong> — 114 calls, 14 sources (TIMER/SIGNAL/DATA/VNODE/PROC), 31 blocks</td>
<td><strong>1 RECV</strong> (<code>notifyd.c:1230</code>) + <strong>1 SEND</strong> (<code>notify_proc.c:352</code>)</td>
<td>Phase 7+</td>
</tr>
<tr>
<td><code>notifyutil</code> (CLI tool)</td>
<td>light</td>
<td><strong>1 RECV</strong> (<code>notifyutil.c:374</code>)</td>
<td>Phase 7+ (with notifyd)</td>
</tr>
<tr>
<td><code>configd</code> daemon (<code>configd.tproj</code>)</td>
<td>4 calls — light direct use (SIGNAL + dispatch_main); heavy through the framework</td>
<td><span class="pill pill-good">none directly</span> — works through <code>SystemConfiguration.framework</code></td>
<td>Phase 6</td>
</tr>
<tr>
<td><code>configd</code> Plugins (IPMonitor, KernelEventMonitor, etc.)</td>
<td>54 calls — moderate (TIMER + READ sources, blocks)</td>
<td><span class="pill pill-good">none</span> directly</td>
<td>Phase 6 (partial — IPMonitor's <code>NWNetworkAgentRegistration</code> blocker per the Foundation spike)</td>
</tr>
<tr>
<td><code>scutil</code> CLI</td>
<td>7 calls — light</td>
<td><span class="pill pill-good">none</span></td>
<td>Phase 6</td>
</tr>
<tr>
<td><strong><code>diskarbitrationd</code></strong></td>
<td>heavy — uses <code>dispatch_mach_create_f</code>, <code>dispatch_mach_connect</code>, <code>IONotificationPortSetDispatchQueue</code>, <code>xpc_set_event_stream_handler</code></td>
<td><strong>uses <code>dispatch_mach_t</code></strong> channels — even higher-level than MACH_RECV sources; requires the <code>DISPATCH_MACH_SPI</code> private API</td>
<td>Phase 8+ — deferred; possibly replaced with a sockets-based daemon that sidesteps Mach entirely</td>
</tr>
<tr>
<td><code>DiskArbitrationAgent</code> (per-user GUI)</td>
<td>likely heavy (Apple Cocoa app)</td>
<td>likely yes (uses <code>DASessionSetDispatchQueue</code> from the framework)</td>
<td>Phase 8+; needs GNUstep Foundation/AppKit, separate concern</td>
</tr>
<tr>
<td><strong><code>mDNSResponder</code></strong> daemon core (<code>mDNSCore/</code>, <code>mDNSPosix/</code>)</td>
<td>moderate-to-heavy in <code>mDNSMacOSX/</code> (Apple-only); pure POSIX in <code>mDNSPosix/</code></td>
<td>likely some in <code>mDNSMacOSX/</code>; <strong>none in <code>mDNSPosix/</code></strong></td>
<td>Phase 7+ — we'd port the <code>mDNSPosix/</code> path, not <code>mDNSMacOSX/</code></td>
</tr>
<tr>
<td><strong><code>IPConfiguration</code></strong> daemon (Apple's bootp client)</td>
<td>heavy — <code>timer.c</code> + <code>FDSet.c</code> ~494 LoC of dispatch usage per the IPConfiguration porting plan</td>
<td>uncertain — per the plan, MIG/Mach IPC heavy; on modern Apple the server.c uses XPC which sits on dispatch+Mach</td>
<td>Phase 8+ — deferred; current ISO uses <code>dhcpcd</code> from FreeBSD ports</td>
</tr>
<tr><td colspan="4"><strong>Future / optional</strong></td></tr>
<tr>
<td><code>libxpc</code> bootstrap server (Phase 3)</td>
<td>moderate — would use dispatch sources for the bootstrap port</td>
<td><strong>required</strong> — bootstrap port is exactly the kind of long-lived Mach receive that needs dispatch</td>
<td>Phase 3</td>
</tr>
<tr>
<td><code>libCoreFoundation</code> (swift-corelibs CF) supplementary system library</td>
<td>some — for CFRunLoop version1 sources if we route through dispatch</td>
<td>yes — CFMachPort and CFRunLoop v1 sources are Mach-backed</td>
<td>Phase 5.5 (now driven by <a href="freebsd-launchctl-corefoundation-spike.html">launchctl-corefoundation-spike</a>; replaces the older <code>libCFRuntime</code> proposal)</td>
</tr>
<tr>
<td>NSXPCConnection (downstream GNUstep contribution)</td>
<td>moderate — wraps libxpc, inherits its dispatch use</td>
<td>transitively via libxpc</td>
<td>Optional / downstream / not on critical path</td>
</tr>
</tbody>
</table>
<h3>Summary by impact</h3>
<table>
<thead><tr><th>Category</th><th>Components</th><th>Implication for Phase 0</th></tr></thead>
<tbody>
<tr>
<td><strong>Need Mach dispatch (MACH_RECV or higher)</strong></td>
<td>libxpc, libnotify, notifyd, notifyutil, SystemConfiguration framework, DiskArbitration framework (if we use Apple-shape impl), diskarbitrationd (if Apple-shape), libxpc bootstrap server</td>
<td>Phase 0 blocks all of these. ~8 components.</td>
</tr>
<tr>
<td><strong>Use libdispatch but NOT Mach dispatch</strong></td>
<td>Apple launchd-842 (light), freebsd-launchd rewrite, asl (heavy with TIMER/READ/SIGNAL/VNODE), aslmanager, configd daemon, configd plugins, scutil, mDNSResponder mDNSPosix, IPConfiguration (timer/FD), libasl, libSystemConfiguration, liblaunch</td>
<td>Phase 0 unblocks the Mach RECV symbol but these components don't need it. They use plain libdispatch which builds fine on FreeBSD today (subject to the gershwin kqueue patch).</td>
</tr>
<tr>
<td><strong>No libdispatch at all</strong></td>
<td>mach.ko, libmach, launchctl, syslogd (FreeBSD native)</td>
<td>Unaffected by the Phase 0 / libdispatch path entirely.</td>
</tr>
</tbody>
</table>
<h3>Caveats and alternatives</h3>
<ul>
<li><strong>DiskArbitration sockets alternative:</strong> if we choose a sockets-based <code>diskarbitrationd</code> (replacing Apple's MIG + XPC transport with AF_UNIX), DiskArbitration drops out of the Mach-dispatch dependency list. The <code>DASessionSetDispatchQueue</code> public API still exists for clients but the implementation routes through sockets rather than Mach. Open question; the existing <a href="freebsd-disk-arbitration-plan.html">DiskArbitration plan</a> stays the authority on that decision.</li>
<li><strong>mDNSResponder <code>mDNSPosix/</code> path:</strong> the POSIX implementation Apple ships in their open-source tree doesn't use libdispatch at all (uses <code>select()</code>/<code>poll()</code>). We'd port that one, not <code>mDNSMacOSX/</code>. So mDNSResponder may end up with zero dispatch dependency depending on which path we take.</li>
<li><strong>IPConfiguration:</strong> the existing porting plan flags libdispatch as a real dependency for the timer / FDSet subsystems. If we ever do the port. Currently <code>dhcpcd</code> covers the use case.</li>
<li><strong>aslmanager:</strong> can probably be replaced by FreeBSD's standard newsyslog if we don't want the full Apple log-rotation policy machinery. Trade-off: lose feature parity, drop the libdispatch dependency. Probably not worth it.</li>
</ul>
<h2 id="install-path">6. Install path, vendoring, and build integration</h2>
<div class="callout callout-good">
<p><strong>Decided</strong> (per <a href="freebsd-libxpc-install-layout-spike.html#libdispatch">install layout spike §4</a>): libdispatch installs as <code>/usr/lib/system/libsystem_dispatch.so</code> with Apple-canonical naming, alongside <code>libsystem_kernel</code>, <code>libsystem_xpc</code>, <code>libsystem_launch</code>, and <code>libsystem_blocks</code>. Headers at the standard <code>/usr/include/{dispatch,os}/*.h</code>. Build pipeline: vendored under <code>freebsd-launchd-mach/src/libdispatch/</code> and built in our <code>build.sh</code> chroot, not via gershwin-developer.</p>
</div>
<p>Earlier revisions of this spike weighed four candidate paths (<code>/System/Library/Libraries/</code>, <code>/usr/lib/</code>, <code>/usr/local/lib/</code>, and the layout-spike's later addition of <code>/usr/lib/system/</code>). The Libsystem-prefix path won on all the criteria that mattered: Apple-canonical naming, coherent grouping with the rest of the libsystem_* family, isolated namespace away from FreeBSD base proper, no pkgbase-collision risk for the already-shipped libBlocksRuntime, and clean ldconfig setup via a single drop-in.</p>
<h3>6.1. Source vendoring</h3>
<p>swift-corelibs-libdispatch is committed verbatim under <code>freebsd-launchd-mach/src/libdispatch/</code>. Two patches are applied as commits on top of the upstream source in our tree:</p>
<ol>
<li><strong>FreeBSD performance / correctness patch</strong> (gershwin-developer's existing <code>swift-corelibs-libdispatch.patch</code>) — see §6.4. Applied first.</li>
<li><strong>Mach backend</strong> (this spike's deliverable) — new <code>src/event/event_mach_freebsd.c</code> + minimal <code>CMakeLists.txt</code> wiring, gated on <code>__FreeBSD__</code> & <code>HAVE_LIBMACH</code>. Links against <code>-lsystem_kernel</code> from our libmach build (<a href="freebsd-libxpc-install-layout-spike.html#libmach">layout spike §3</a>).</li>
</ol>
<p>Both patches travel as ordinary commits in our git history (no separate <code>.patch</code> file in <code>Library/Patches/</code> — the gershwin patch-script workflow is retired for libdispatch in this project; gershwin-developer can either consume our vendored tree or keep its own copy). Vendoring trades repo size for hermetic builds, no network at build time, and audit-friendly diffs.</p>
<h3>6.2. Block.h coordination — we ship it now</h3>
<p>Reversing the prior recommendation. Earlier revisions said "defer to FreeBSD pkgbase's <code>FreeBSD-libblocksruntime-15.0</code> for <code>/usr/include/Block.h</code> and <code>/usr/lib/libBlocksRuntime.so</code>." The new direction (per <a href="freebsd-libxpc-install-layout-spike.html#blocks">install layout spike §15</a>):</p>
<ul>
<li><strong>Drop <code>FreeBSD-libblocksruntime</code></strong> from <code>pkglist-base.txt</code>. We don't depend on pkgbase shipping it.</li>
<li><strong>Ship <code>Block.h</code> from libdispatch's bundled BlocksRuntime sources</strong> (which is upstream Apple compiler-rt — the same code FreeBSD-libblocksruntime is built from). Install to <code>/usr/include/Block.h</code> as part of the libdispatch install (<code>INSTALL_BLOCK_HEADERS_DIR=/usr/include</code>).</li>
<li><strong>libBlocksRuntime semantics:</strong> the libdispatch CMake build already knows how to embed BlocksRuntime (<code>EMBEDDED_BLOCKS_RUNTIME=ON</code>) so <code>libsystem_dispatch.so</code> contains the symbols and self-links cleanly. Optionally also install a separate <code>/usr/lib/system/libsystem_blocks.so</code> for non-libdispatch consumers; if needed, add a compatibility symlink at <code>/usr/lib/libBlocksRuntime.so</code> for clang's implicit <code>-fblocks</code> link line.</li>
</ul>
<p>Net effect: Block.h on the system comes from <em>our</em> build, not pkgbase. Single source of truth. No collision because pkgbase isn't installed. The "single Block.h on the system" property the prior recommendation valued is preserved — we just own the source rather than borrowing from pkgbase.</p>
<h3>6.3. Build integration in <code>build.sh</code></h3>
<p>libdispatch builds as a new step <strong>3d</strong> in <code>freebsd-launchd-mach/build.sh</code>, immediately after libmach (libsystem_kernel) installs. Outline:</p>
<ol>
<li><code>rsync src/libdispatch -> chroot:/tmp/libdispatch</code> (chroot stays git-free).</li>
<li><code>chroot $WORK/rootfs cmake</code> with <code>-DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib/system -DINSTALL_BLOCK_HEADERS_DIR=/usr/include -DINSTALL_DISPATCH_HEADERS_DIR=/usr/include/dispatch -DINSTALL_OS_HEADERS_DIR=/usr/include/os -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release</code> — uses cmake/ninja already in <code>buildpkgs.txt</code> and clang already in <code>buildpkgs-base.txt</code>.</li>
<li>Built artifacts land at <code>/usr/lib/system/libsystem_dispatch.so</code> + sonname symlink, <code>/usr/include/dispatch/*.h</code>, <code>/usr/include/os/*.h</code>, <code>/usr/include/Block.h</code>.</li>
<li>Verification asserts (mirroring libmach's): file existence, sonname symlink correct, ldd of the test binary resolves <code>libsystem_dispatch.so.0</code> into <code>/usr/lib/system/</code>.</li>
</ol>
<p><strong>ldconfig hint reuses the existing drop-in</strong> at <code>/usr/local/libdata/ldconfig/freebsd-launchd-mach</code> (added for libmach — it lists <code>/usr/lib/system</code>). No new ldconfig setup needed; the runtime linker already finds the directory.</p>
<h3>6.4. Required FreeBSD performance / correctness patch</h3>
<p>swift-corelibs-libdispatch needs <code>gershwin-developer/Library/Patches/swift-corelibs-libdispatch.patch</code> applied for FreeBSD to perform correctly. Hunks below; all are FreeBSD-kqueue accommodations and have <strong>nothing to do with Mach</strong>:</p>
<table>
<thead><tr><th>Hunk</th><th>File</th><th>Fix</th><th>Why it matters</th></tr></thead>
<tbody>
<tr>
<td>1</td>
<td><code>src/event/event_kevent.c</code></td>
<td>FreeBSD-specific timer fflags init macros that exclude <code>NOTE_ABSOLUTE</code> on FreeBSD (other systems use it)</td>
<td>FreeBSD's kqueue <code>NOTE_*</code> constants differ from Apple's; without the patch, timers either don't compile or use wrong semantics.</td>
</tr>
<tr>
<td>2</td>
<td><code>src/event/event_kevent.c</code></td>
<td>Replace zero-timeout <code>kevent()</code> polling with a 1ms minimum delay on non-QOS systems (FreeBSD/Linux)</td>
<td>Without this, libdispatch worker threads tight-spin on <code>kevent()</code> with no events, burning CPU.</td>
</tr>
<tr>
<td>3</td>
<td><code>src/event/event_kevent.c</code></td>
<td>Enforce a minimum timer delay (default 10ms, configurable via <code>LIBDISPATCH_TIMER_MIN_DELAY_MS</code> env var) to prevent scheduling timers at or before now</td>
<td>Without this, timer programming triggers immediate re-arms and tight kevent loops on FreeBSD's kqueue.</td>
</tr>
<tr>
<td>4</td>
<td><code>src/event/workqueue.c</code></td>
<td>Fix a loop-variable typo: <code>for (int j = 0; i < count; ++i)</code> → <code>j < count; ++j</code> in <code>_dispatch_workq_count_runnable_workers()</code></td>
<td>Pre-existing bug in libdispatch's FreeBSD workqueue thread-counting; would scan past array bounds. Pure correctness fix.</td>
</tr>
</tbody>
</table>
<p>Applied via the existing <code>gershwin-developer/Library/Patches/apply_swift-corelibs-libdispatch_patch.sh</code> at vendoring time (one-shot), then committed into our tree as part of the initial vendor. Subsequent maintenance: rebase onto upstream when bumping the libdispatch version.</p>
<h3>6.5. Live-reinstall semantics under our chroot model</h3>
<p>The earlier "<code>/System</code> live-reinstall fragility" discussion is no longer load-bearing because libdispatch ships inside the rootfs.uzip on the live ISO — consumers see the file via the in-kernel unionfs, and it's installed once at build time, not interactively. Wholesale-replace concerns from the gershwin <code>/System</code> path don't apply.</p>
<p>For an installed (non-live) FreeBSD where freebsd-launchd-mach is the system source: <code>pkg upgrade</code> handles <code>/usr/lib/system/libsystem_dispatch.so</code> atomically (pkg replaces files via a temp-then-rename pattern). Already-running processes keep their mapped libdispatch; new processes pick up the upgraded version. Standard FreeBSD pkg semantics, no atomic-rename ceremony needed.</p>
<h3>6.6. Recommendation</h3>
<div class="callout callout-good">
<p><strong>Recommended (and decided):</strong></p>
<ul>
<li><strong>Install path:</strong> <code>/usr/lib/system/libsystem_dispatch.so</code> — canonical Apple Libsystem naming, isolated namespace, ldconfig already wired.</li>
<li><strong>Vendoring:</strong> commit swift-corelibs-libdispatch under <code>freebsd-launchd-mach/src/libdispatch/</code>; apply gershwin's FreeBSD perf patch as a commit; add Mach backend as a commit.</li>
<li><strong>Block.h:</strong> ship from libdispatch's bundled BlocksRuntime (Apple compiler-rt source); drop <code>FreeBSD-libblocksruntime</code> from <code>pkglist-base.txt</code>.</li>
<li><strong>Build:</strong> cmake/ninja in chroot as build.sh step 3d, after libmach.</li>
</ul>
</div>
<h2 id="recommendation">7. Recommendation</h2>
<div class="callout callout-good">
<p><strong>Option 1 — patch libdispatch.</strong> The dominant factor is "every Apple-source consumer expects <code>DISPATCH_SOURCE_TYPE_MACH_RECV</code> to exist." Option 1 honors that expectation; Options 2 and 3 force every Apple-source import to be modified, which complicates re-imports forever.</p>
</div>
<p>The earlier mental model "only libxpc cares" understated the scope. With 5+ consumers in scope already (libxpc, libnotify, notifyd, notifyutil, SystemConfiguration framework) and more arriving as we port additional daemons, the per-consumer linear cost of Options 2 and 3 is real. Option 1's higher upfront cost (~500-1000 lines of one new file in a patch) amortizes across every present and future consumer.</p>
<p>Secondary factor: <strong>Apple-import rebase burden.</strong> Our project's overall pattern is "import Apple source verbatim, patch for FreeBSD adaptation as a separate layer." Options 2 and 3 force patches into the verbatim Apple files themselves; every time we re-pull from <code>apple-oss-distributions/<daemon></code>, the patches need rebasing. Option 1 isolates the FreeBSD-specific work in <code>gershwin-developer/Library/Patches/</code> — the same place gershwin already maintains a libdispatch patch.</p>
<p>Tertiary factor: <strong>consistency with the broader project pattern.</strong> The libxpc plan's <a href="freebsd-libxpc-plan.html#phases">Phase 0</a> already specifies a libdispatch Mach backend patch in gershwin-developer; this spike confirms that choice was right and tightens its scope.</p>
<h3>What stays unchanged from the libxpc plan</h3>
<ul>
<li>Phase 0 in <a href="freebsd-libxpc-plan.html">freebsd-libxpc-plan</a> still: write <code>event_mach_freebsd.c</code>, ship as a second patch in <code>gershwin-developer/Library/Patches/</code>, iterate via the reclone-every-time workflow.</li>
<li>The 5-test smoke harness still measures source creation, single-message receive, N-message receive, cancellation cleanup, and no port-table leak.</li>
<li>The <code>mach_port_allocate</code> + <code>mach_port_insert_right</code> mach.ko expansions are still prerequisites for the self-send smoke tests.</li>
</ul>
<h3>What this spike adds to the libxpc plan</h3>
<ul>
<li><strong>Justification.</strong> The 5+ consumer count makes the libdispatch patch the cheap option in aggregate. Previously framed as "libxpc needs this"; now framed as "the Apple-source ecosystem needs this."</li>
<li><strong>Fallback rejected.</strong> Per-consumer polling-thread workarounds (Option 2) and bridge-library (Option 3) explicitly considered and rejected for the reasons in the trade-off table.</li>
<li><strong>Doing-nothing isn't viable.</strong> Section 3 makes the "what if we don't" path concrete: libxpc, libnotify, notifyd, notifyutil, and SystemConfiguration framework all fail to link. The whole post-Phase-B daemon roadmap stops.</li>
</ul>
<h2 id="refs">6. References</h2>
<h3>Local trees grep'd</h3>
<pre><code>grep -rn "DISPATCH_SOURCE_TYPE_MACH_RECV\|DISPATCH_SOURCE_TYPE_MACH_SEND" \
nextbsd/ ravynos/ freebsd-launchd/ gershwin-developer/</code></pre>
<h3>Related plans</h3>
<ul>
<li><a href="freebsd-libxpc-plan.html"><code>freebsd-libxpc-plan</code></a> — parent plan; Phase 0 = the libdispatch Mach backend work this spike justifies</li>
<li><a href="freebsd-libxpc-foundation-spike.html"><code>freebsd-libxpc-foundation-spike</code></a> — companion spike on Foundation / CoreFoundation choices</li>
<li><a href="freebsd-launchd-mach-plan.html"><code>freebsd-launchd-mach-plan</code></a> — mach.ko (kernel side)</li>
</ul>
<p class="footnote">Last updated 2026-05-12. Spike compiled from empirical grep over the local NextBSD, ravynOS, freebsd-launchd, and gershwin-developer trees plus prior research at <a href="freebsd-libxpc-plan.html#deps">freebsd-libxpc-plan §6</a>. The 5-consumer count for <code>DISPATCH_SOURCE_TYPE_MACH_RECV</code> is reproducible: grep the trees, count distinct source files, get the same 7 unique sites. Be aware that more consumers will be added as we port mDNSResponder, IPConfiguration, and additional Apple-source daemons; the cost arguments for Option 1 strengthen with each.</p>
</div>
</body>
</html>