NextBSD · architecture review · 3-agent deep review of nextbsd-userland + nextbsd-kernel

Is the Mach fork of libdispatch still needed — or can we drop it for Gershwin's stock libdispatch?

Sources: nextbsd-userland/src/libdispatch, consumers across src/*, and the kernel Mach overlay · 2026‑07‑06
Answer

Don't swap to stock/Gershwin libdispatch — it would break boot and all XPC IPC. Six production components depend on libdispatch's Mach sources (DISPATCH_SOURCE_TYPE_MACH_RECV / dispatch_mach_t), and a stock (HAVE_MACH=0) build can't compile or link them. But your instinct is half-right: the fork's original rationale — self-contain Mach without touching the kernel — is now obsolete (the kernel was modified), and the fd-source bug is a design mismatch introduced by turning Mach on, fixable cleanly without dropping the fork. The right move is fix the flag poisoning properly and keep the fork (Option A), with a principled reshape available later (Option C).

Keep this separate from the menu bug

We already proved the patched libdispatch delivers X PropertyNotify 5/5 in the exact WindowMonitor pattern, yet the running Menu still doesn't track. So neither fixing nor replacing libdispatch fixes the menu — that failure is downstream. This review is the libdispatch architecture decision on its own merits, not the menu fix.

01 The "fork" is barely a fork

It's upstream apple/swift-corelibs-libdispatch (vendored at bc13504) built HAVE_MACH=ON on FreeBSD, plus a set of "task #39" glue shims to make Apple's Mach path compile and link against libmach. Not a rewrite — a build-config choice plus glue. The Mach machinery (src/mach.c 107 KB, voucher.c, the event_kevent.c EVFILT_MACHPORT source types) is upstream Apple code that generic-BSD builds simply never compile.

02 The fd-source bug is a side-effect of enabling Mach — not a typo

This is the crux, and it reframes the whole question. Upstream, on a generic BSD with no system EV_UDATA_SPECIFIC, zeroes both EV_UDATA_SPECIFIC and EV_VANISHED, so fd sources go out as the portable EV_ADD|EV_ENABLE|EV_DISPATCH — no bug. But Mach has a hard gate:

// upstream: mach support requires the Darwin direct-knote flag
#if !EV_UDATA_SPECIFIC
#  error mach support requires EV_UDATA_SPECIFIC
#endif

To turn Mach on, the fork bypassed that #error (5e9a460) and gave the flag a truthy value — event_config.h:213, commit ca62703: "Drop to Apple's canonical 0x0100, which fits uint16 and doesn't collide with FreeBSD's EV_* bits." The author checked the userspace‑visible flag list and missed EV_FORCEONESHOT 0x0100 and EV_KEEPUDATA 0x0200. Because EV_UDATA_SPECIFIC is a global flag on all direct-knote sources — not just Mach — forcing it to 0x0100 re-poisoned every fd source:

fd source flags = EV_UDATA_SPECIFIC(0x100) | EV_DISPATCH(0x80) | EV_VANISHED(0x200) | EV_ADD | EV_ENABLE
FreeBSD kernel reads 0x100 = EV_FORCEONESHOT, 0x200 = EV_KEEPUDATA  ->  EINVAL  ->  every READ/WRITE source silently dead

Corollary (worth an audit): the same poisoned flags ride the other direct-knote sources — proc, signal, vnode, memorystatus — so those may also be silently EINVAL-dead. The impact is potentially broader than fd read/write.

03 Who actually depends on the Mach libdispatch

This is what blocks a stock swap. Six production consumers use Mach dispatch sources / the channel API:

ConsumerUsesLoad-bearing?
libxpc/xpc_connection.c:241MACH_RECV recv source — the XPC transportYes — all XPC IPC
launchd/runtime.c:232MACH_RECV on the demand-port-setYes (but thin — just a wakeup; job dispatch already runs off synthetic kevents)
notifyd.c:1435/1442full dispatch_mach_t channel protocolYes — hardest to replace
IOKitNotify.c:337, notify_client.c:943, SCNotify.c:351MACH_RECV notification deliveryYes for those subsystems
CoreFoundation / desktop runlooppipe+kqueue __CFPort, plain kevent()No dependency — not a blocker
MACH_SEND / vouchers / firehosetests + internal onlyNo production use

A stock HAVE_MACH=0 libdispatch would fail to compile/link libxpc, launchd, notifyd, IOKit, Libnotify, SystemConfiguration → broken boot + all XPC IPC. Notably, Gershwin/CoreFoundation itself does not depend on it (it uses pipe+kqueue), so the desktop is not the blocker — the system daemons are.

04 The kernel was modified — self-containment is obsolete

Your recollection is confirmed in the kernel Mach overlay and history:

What still legitimately needs the fork: the HAVE_MACH build (for the Mach source types the six consumers use) and the libmach shim that issues the Mach syscalls. The original "don't touch the kernel" reason is gone; the Mach feature set is what remains load-bearing.

05 Options

A · Keep the fork, fix the flags right recommended now

Restore upstream's per-path zeroing so EV_UDATA_SPECIFIC/EV_VANISHED reach only EVFILT_MACHPORT (Mach) knotes, and fd/timer/portable sources emit plain EV_ADD|EV_ENABLE|EV_DISPATCH. Cleaner than the one-line strip and it also un-breaks the other direct-knote sources (proc/signal/vnode). Then audit those for the same EINVAL.

Cost: low. Keeps the large Apple surface (mach.c/voucher.c) that upstream doesn't test on BSD.

C · Keep HAVE_MACH, drop the direct-knote/QoS imports long-term

Now that the kernel has a native filt_machport, carry Mach source types on the portable kqueue path (no EV_UDATA_SPECIFIC, no kevent_id/QoS, no direct-knotes). Removes the exact machinery that caused the bug while keeping Mach features.

Cost: medium — must prove dispatch_mach_t re-arm survives without direct-knote (event_kevent.c:444‑473). Best long-term shape.

B · Swap to stock/Gershwin libdispatch blocked / risky

Eliminates the bug class by construction, but a HAVE_MACH=0 libdispatch strands six consumers. Viable only after porting each Mach-recv consumer onto the native path (libxpc/IOKit/SC moderate; notifyd's channel state machine hardest) — re-introducing bespoke mach_msg loops that #168 just removed.

Cost: high, bounded. Large regression risk. Only if eliminating Mach from libdispatch is an explicit goal.

06 Recommendation

Fix the flag poisoning properly and keep the fork (Option A) now. The Mach machinery is load-bearing today (XPC/launchd/notifyd/ IOKit/SC), so it can't be dropped for stock libdispatch without a substantial, boot-critical rewrite of those consumers. The clean fix — restore per-path zeroing so the Darwin flags only ever reach Mach knotes — is low-cost, structurally correct, and likely fixes a broader class than just fd sources (audit proc/signal/vnode next). Option C is the principled follow-up if reducing the Apple-specific surface is a goal, and it's newly feasible because the kernel now ships a native filt_machport. Option B (swap to Gershwin's libdispatch) is not worth it — it breaks Mach IPC, and it wouldn't fix the menu anyway.