feat(macos): validate dev + apple-dev presets on macOS, fix 3 cross-platform bugs
macOS port groundwork — core, server, tools, and tests now build and run on macOS 26.5 / Apple Silicon. ctest --preset dev green 21/21 (2 consecutive runs). apple-dev produces valid arm64 libvoicecat.a + XCFramework for the Swift Package. Three real cross-platform bugs found and fixed (all latent on Windows/Linux): 1. test_m2_voice.cpp POSIX branch missing <netdb.h> — Linux glibc transitively includes it, macOS doesn't. Would fail on any strict POSIX system. 2. SIGPIPE killing processes on macOS — writing to a closed TCP socket raises SIGPIPE by default (doesn't exist on Windows, benign on Linux). Fixed by ignoring SIGPIPE in both core client init and server startup (POSIX-only, #ifndef _WIN32). Production fix, not just tests. 3. Use-after-free of Asio's kqueue reactor on server shutdown — the deterministic test_tofu_flow segfault. TcpServerConn's tls_read_loop runs on a blocking-I/O thread; when Server::run() returned, io_context was destroyed while those threads were still running. On macOS kqueue the reactor pointer is null'd immediately -> segfault in socket.close(). Latent on Windows IOCP and Linux epoll. Fix: TcpAcceptor now tracks connections; new shutdown() closes all + joins threads before io is destroyed; Server::stop() now closes acceptor + media_relay too (was just io.stop()). Verified: dev + apple-dev presets build green, 21/21 tests pass, server starts + two vccli text chat over TLS (M1 on Mac), vccli --voice starts MIC stream via CoreAudio (M2 protocol-level), vccli --list-devices enumerates CoreAudio devices, xcodebuild -create-xcframework produces valid VoiceCatCore.xcframework. No ABI or proto changes. Docs updated: building.md, clients/apple/README.md, PROGRESS.md, CLAUDE.md status line.
This commit is contained in:
67
PROGRESS.md
67
PROGRESS.md
@@ -10,6 +10,73 @@ up instantly. Newest status at the top.
|
||||
|
||||
## ▶ Where we left off / next action
|
||||
|
||||
- **Done:** **macOS port — `dev` + `apple-dev` presets validated** (2026-06-18). The core,
|
||||
server, tools, and tests now build and run on macOS (Apple Silicon, macOS 26.5, Apple clang
|
||||
21). This lays the groundwork for the macOS/iOS Swift client. Three real bugs found and
|
||||
fixed (all were cross-platform issues that manifested on macOS but were latent on
|
||||
Windows/Linux):
|
||||
1. **Missing `<netdb.h>` in `test_m2_voice.cpp` POSIX branch** — the raw-socket test's
|
||||
`#else` branch included `<arpa/inet.h>`/`<netinet/in.h>`/`<sys/socket.h>`/`<unistd.h>`
|
||||
but not `<netdb.h>` (needed for `addrinfo`/`getaddrinfo`/`freeaddrinfo`). On Linux glibc
|
||||
these headers transitively include `<netdb.h>`; on macOS they don't. Fixed by adding
|
||||
`# include <netdb.h>` to the POSIX branch (mirrors `core/src/core/client.cpp:16` which
|
||||
already had it). Real latent bug — would fail on any strict POSIX system.
|
||||
2. **SIGPIPE killing processes on macOS** — on macOS, writing to a closed TCP socket
|
||||
raises `SIGPIPE` by default (unlike Windows where it doesn't exist, or Linux where it's
|
||||
often benign). This killed `test_tofu_flow` (intermittent SIGPIPE/SEGFAULT) and would
|
||||
also kill `voicecat-server` and `vccli` in production when a peer dropped mid-write.
|
||||
Fixed by ignoring SIGPIPE (`std::signal(SIGPIPE, SIG_IGN)`) in both the core client
|
||||
init (`core/src/core/client.cpp` POSIX branch of the `#ifdef _WIN32` WSAStartup block)
|
||||
and the server startup (`server/src/server.cpp` before the `asio::signal_set`). Both are
|
||||
POSIX-only (`#ifndef _WIN32`), process-global, and idempotent. The server's
|
||||
`asio::signal_set(SIGINT, SIGTERM)` is unaffected (independent signals).
|
||||
3. **Use-after-free of Asio's kqueue reactor on server shutdown** — the root cause of the
|
||||
deterministic `test_tofu_flow` segfault (EXC_BAD_ACCESS in
|
||||
`kqueue_reactor::deregister_descriptor(this=0x0000000000000000)`). `TcpServerConn`'s
|
||||
`tls_read_loop` runs on a dedicated blocking-I/O thread (not async on `io_context`).
|
||||
When `Server::stop()` → `io.stop()` → `Server::run()` returned, the local
|
||||
`asio::io_context` was destroyed while `tls_read_loop` threads were still running. When
|
||||
a thread detected the disconnect and called `TcpServerConn::close()` → `socket.close()`
|
||||
→ Asio tried to deregister from the kqueue reactor — but the reactor (owned by
|
||||
`io_context`) was already destroyed, and on macOS kqueue the reactor pointer is null'd
|
||||
immediately. Latent on Windows (IOCP) and Linux (epoll) where the timing is more
|
||||
forgiving. **Fix:** `TcpAcceptor` now tracks its connections (new `conns_` vector +
|
||||
`conns_mu_`); `TcpAcceptor::stop()` closes all tracked connections while `io_context` is
|
||||
still alive; new `TcpAcceptor::shutdown()` method calls `stop()` then
|
||||
`wait_closed()` on each connection (new `TcpServerConn::wait_closed()` joins
|
||||
`tls_thread_`); `Server::run()` calls `acceptor.shutdown()` after `io.run()` returns and
|
||||
before `io` is destroyed; `Server::stop()`'s `stop_fn_` now calls `acceptor.stop()` +
|
||||
`media_relay->stop()` + `io.stop()` (was just `io.stop()`). After `acceptor.shutdown()`,
|
||||
when `tls_read_loop` threads exit and call `on_disconnected` → `ConnSession::close()` →
|
||||
`TcpServerConn::close()`, the `close()` is a no-op (`closing_.exchange(true)` returns
|
||||
true) — no reactor access occurs after `io` is destroyed.
|
||||
- **Environment setup:** vcpkg cloned to `~/code/vcpkg` + bootstrapped. `VCPKG_ROOT` must
|
||||
be set. Homebrew `autoconf-archive` is required (vcpkg's libsodium port needs it for
|
||||
autoreconf — `brew install autoconf-archive`). `autoconf`/`automake`/`libtool` were
|
||||
already installed; `glibtoolize` (Homebrew's macOS name for `libtoolize`) is handled by
|
||||
vcpkg automatically.
|
||||
- **Verified:** `cmake --preset dev` + `cmake --build --preset dev` green (21 binaries).
|
||||
`ctest --preset dev --parallel 1` — **21/21 green** (2 consecutive runs, 64s each).
|
||||
`cmake --preset apple-dev` + `cmake --build --preset apple-dev` green → valid 1.9 MB
|
||||
arm64 `libvoicecat.a` (167 exported C ABI symbols, correct visibility).
|
||||
`xcodebuild -create-xcframework` → valid `VoiceCatCore.xcframework` (macOS-arm64 slice
|
||||
with `voicecat.h` headers). Server runtime: `voicecat-server` starts, generates identity,
|
||||
SQLite, Lobby, binds TCP+UDP, clean SIGINT shutdown. Two `vccli` text chat over TLS
|
||||
(M1 exit criterion on Mac). `vccli --voice --input-mode vad` starts a MIC stream via
|
||||
CoreAudio (M2 protocol-level on Mac — ear test pending). `vccli --list-devices`
|
||||
enumerates 4 CoreAudio input + 3 output devices with correct defaults.
|
||||
- **Apple framework linking:** NOT needed — modern macOS ld (Xcode 26.5) auto-discovers
|
||||
CoreAudio/CoreFoundation frameworks in `/System/Library/Frameworks` without explicit
|
||||
`-framework` flags. miniaudio's `MINIAUDIO_IMPLEMENTATION` compiles the CoreAudio calls
|
||||
inline, and the linker resolves them automatically. No `if(APPLE)` CMake block was added.
|
||||
- **Docs updated:** `docs/building.md` (`apple-dev` row + §6 updated from "scaffolding" to
|
||||
"validated"), `clients/apple/README.md` (macOS slice build confirmed, iOS slices still
|
||||
scaffolding), `PROGRESS.md` (this entry).
|
||||
- **Still deferred (per scope):** macOS `SCREEN_AUDIO` loopback via ScreenCaptureKit (stub
|
||||
returns `false` — per `docs/voice.md §9`); iOS cross-compile presets (`apple-ios`/
|
||||
`apple-ios-sim` — scaffolding); `vc_audio_suspend`/`vc_audio_resume` ABI hooks (defer to
|
||||
iOS client milestone, keep ABI stable).
|
||||
|
||||
- **Done:** **CMake preset cleanup + cross-platform build config** (2026-06-18). The preset
|
||||
set was a mess — `dev` (never used), `m1-dev` (the one everyone used), `m2-dev`
|
||||
(cache-identical to `m1-dev`, never used), no optimized+tests preset, no stripping.
|
||||
|
||||
Reference in New Issue
Block a user