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:
@@ -1,6 +1,7 @@
|
||||
#include "server.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <csignal>
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
@@ -135,13 +136,26 @@ int Server::run() {
|
||||
// Arm programmatic stop (for tests and embedders).
|
||||
{
|
||||
std::lock_guard lk(stop_mutex_);
|
||||
stop_fn_ = [&io] { io.stop(); };
|
||||
stop_fn_ = [&] {
|
||||
acceptor.stop();
|
||||
media_relay->stop();
|
||||
io.stop();
|
||||
};
|
||||
}
|
||||
|
||||
// Notify caller of the actual bound port (matters when bind_port==0).
|
||||
uint16_t bound = acceptor.local_port();
|
||||
if (cfg_.on_ready) cfg_.on_ready(bound);
|
||||
|
||||
#ifndef _WIN32
|
||||
// POSIX/macOS: ignore SIGPIPE — writing to a disconnected client's TCP socket returns
|
||||
// EPIPE instead of killing the server process. On macOS SIGPIPE is delivered by
|
||||
// default (unlike Windows where the signal doesn't exist). Must be set before any
|
||||
// async writes are posted. Process-global; safe alongside the asio::signal_set below
|
||||
// (which only catches SIGINT/SIGTERM).
|
||||
std::signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
// Graceful shutdown on SIGINT/SIGTERM
|
||||
asio::signal_set signals(io, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](std::error_code, int sig) {
|
||||
@@ -184,6 +198,13 @@ int Server::run() {
|
||||
|
||||
io.run();
|
||||
|
||||
// Close all connections and block until their TLS I/O threads have finished.
|
||||
// MUST happen before io (and its reactor) is destroyed — the TLS read threads do
|
||||
// blocking I/O (not async on io_context) and touch the reactor on socket close.
|
||||
// On macOS kqueue the reactor pointer is null'd immediately on io_context destruction,
|
||||
// causing a segfault; latent on Windows IOCP / Linux epoll where timing is more forgiving.
|
||||
acceptor.shutdown();
|
||||
|
||||
{
|
||||
std::lock_guard lk(stop_mutex_);
|
||||
stop_fn_ = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user