8.4 KiB
8.4 KiB
macOS kqueue Migration Plan
Tracking checklist for replacing the Linux-specific signal/select loop with a kqueue-backed implementation on macOS while keeping the public Loop API intact. Each item should be checked once the code or documentation change is complete.
Prep Work
- Confirm platform detection (
__APPLE__,CMAKE_SYSTEM_NAME, etc.) so the kqueue path only compiles on Darwin targets. (__APPLE__is already used throughoutsrc/*.cpp/src/*.h, and CMake exposes theAPPLEvariable; both can gate the macOS-specific code.) - Document current Loop/UdpServer responsibilities (fd readiness, timers, thread notifications, admin signals) to verify nothing is lost in the port. (
Loopcurrently handles fd readiness viaselect()andfd_sets, timers/quickpoll throughsetitimer+ sleep callbacks, thread/admin notifications via SIGCHLD/SIG* handlers, andUdpServerdepends ong_loopfor dispatchingMsg*handlers insrc/UdpServer.cpp.)
Core Loop Changes
- Add
m_kq(kqueue descriptor) and any supporting structures (e.g., per-fd bookkeeping, timer heaps) insideLoopbehind#ifdef __APPLE__guards. (src/Loop.hnow declaresm_kqplus per-fd enable flags and helpers guarded by__APPLE__;src/Loop.cppdefines the helper functions.) - Update
Loop::constructor/resetto create/closem_kqon macOS and to initialize new data structures. (Loop::Loopzeroes the kqueue state andLoop::reset/Loop::~Loopclose/clear it.) - Replace
setNonBlockingbehavior on macOS so it only setsO_NONBLOCKand skipsO_ASYNC/signal wiring. (Loop::setNonBlockingnow conditionally dropsO_ASYNCon Darwin.) - Modify
registerReadCallback/registerWriteCallbackto submit EV_ADD kevents (EVFILT_READ/EVFILT_WRITE) withudatapointing at theSlot, and add matching EV_DELETE calls in the unregister functions. (Both register methods now callregisterKqueueEventafter inserting the slot;unregisterCallbackinvokesunregisterKqueueEventwhen the final watcher goes away.) - Implement a macOS-only
doPoll()body that:- Calls
kevent(m_kq, ...)with an appropriate timeout (mirroring quickpoll behavior), - Distinguishes niceness levels (invoke only niceness 0 callbacks while
m_inQuickPoll), - Dispatches callbacks by unpacking
udataand reusingcallCallbacks_ass. (The new#ifdef __APPLE__block inLoop::doPollperforms the kevent wait, splits read/write events, and reuses the existing callback machinery.)
- Calls
- Ensure the kevent loop respects
g_someAreQueued/g_udpServer.needBottom()semantics, calling into UdpServer before sleeping as the current code does. (The mac path mirrors the pre-loop bookkeeping before blocking onkevent.)
Timers & Quickpoll
- Replace
setitimer-based heartbeat/quickpoll logic with kqueue timers: register EVFILT_TIMER events for quickpoll interval and CPU accounting updates, pointingudatato dedicated handler routines. (Mac builds now add two kqueue timers—KQ_TIMER_QUICKat 10 ms andKQ_TIMER_REALat 1 ms—andLoop::doPolldispatches them through the refactoredquickpollTimerCore/realTimerCorehelpers.) - Rework
registerSleepCallbackso macOS either:- Schedules an individual EVFILT_TIMER per sleep callback using
s->m_tick, or - Keeps the existing MAX_NUM_FDS mechanism but drives it with a single periodic timer event. (We chose the single periodic option: the quickpoll timer now guarantees
doPollwakes at least every 10 ms, so the existings_lastTime/m_minTicklogic still fires sleep callbacks at the requested cadence without additional per-callback timers.)
- Schedules an individual EVFILT_TIMER per sleep callback using
- Update
Loop::disableTimer/enableTimerso the macOS path arms/disarms the relevant timers instead of callingsetitimer. (Loop::disableTimernow deletes the kqueue timers andenableTimerre-adds them if needed; Linux retains thesetitimercalls.)
Cross-thread and Admin Signals
- Provide a replacement for the SIGCHLD/sigqueue wakeups from
Threads::startUp:- Either register an
EVFILT_USERevent and have threads callkevent(NOTE_TRIGGER)wheng_threads.m_needsCleanupflips, or - Use a self-pipe/eventfd watched via kqueue; document the chosen approach. (Chose
EVFILT_USER:Loop::initregistersKQ_EVENT_WAKE,Loop::wakeup()triggers NOTE_TRIGGER, and the mac path inThreads::startUpnow calls it instead of sending SIGCHLD.)
- Either register an
- Re-register SIGHUP/SIGTERM/SIGINT handlers with
EVFILT_SIGNALif macOS supports it, som_shutdownstill flips when those signals arrive. (Loop::initadds EVFILT_SIGNAL entries for those signals and the kevent loop invokes the same shutdown logic the old signal handlers used.) - Audit calls to
Loop::interruptsOn/Offandg_isHot; provide no-ops or mutex-based guards on macOS where signal masking is irrelevant. (On macOS the interrupt toggles now short-circuit without touchingsigprocmask, since kevent is used instead of async signals.)
UdpServer & Dependent Subsystems
- Confirm no
UdpServercode path assumes_isHot/SIGRT semantics (e.g.,sendPoll_ass,makeCallbacks_ass). If so, provide macOS alternatives or gate the logic by#ifndef __APPLE__. (UdpServeronly checksg_isHotto decide whether to enter “real-time” mode; we now defaultg_isHotto false on macOS so those code paths fall back to the non-signal behavior.) - Verify that
g_loop.registerSleepCallbackusage in other subsystems (PingServer, Process heartbeat, etc.) still behaves with the new timer implementation. (Sleep callbacks still run off the shared periodic timer, and the kevent loop triggers them via the existings_lastTime/m_minTicklogic, so no subsystem changes were required.)
Testing & Validation
- Build and run
./gbon macOS with the kqueue backend; ensure startup succeeds and admin UI is reachable. - Add a URL to spider; confirm the spider queue advances and
UdpServerhandlers fire by watching logs. - Trigger
Save & Exitfrom the admin console: verify the process shuts down cleanly. - Send SIGINT (Ctrl-C) on the console: confirm
m_shutdownpath executes and the process exits. - Exercise any background tasks that rely on timers (ping server, heartbeat, log flushing) to ensure the new events fire at the expected cadence.
Stretch Goals / Cleanup
- Consider adding an epoll backend for Linux (optional) so both platforms share the same evented design, leaving the signal/select code as a fallback only.
- Update developer docs (README or
html/developer.html) to mention macOS support status and the new event loop architecture.
Linux epoll Backend Plan
Tracking tasks to migrate the Linux build off the legacy select/signal loop to an epoll-based backend that mirrors the macOS kqueue implementation.
- Audit the existing select/signal code path to identify all touch points (fd registration, timers, SIG handlers, cross-thread wakeups) that need epoll equivalents.
- Introduce an epoll descriptor (
m_epollFd) and supporting data structures inLoopunder a#ifdef __linux__guard, similar to them_kqadditions. - Change
setNonBlockingfor Linux to skipO_ASYNCand rely purely on non-blocking sockets, matching the epoll model. - Update
registerReadCallback/registerWriteCallbackand the unregister logic to add/removeEPOLLIN/EPOLLOUTevents with edge-triggering, storing theSlotpointer inepoll_event.data. - Implement the Linux
doPoll()branch usingepoll_wait, dispatching niceness level callbacks the same way the kqueue path does, and honoringg_udpServer.needBottom()/g_someAreQueuedbefore sleeping. - Replace the quickpoll/CPU timers with
timerfdoreventfdsources monitored by epoll, so we can dropsetitimerfor Linux as well. - Rework cross-thread wakeups (currently via sigqueue/SIGCHLD) to use an
eventfdor pipe watched by epoll, mirroring the kqueueEVFILT_USERsolution. - Register SIGHUP/SIGTERM/SIGINT via
signalfdor a small signal-handling shim that writes to the epoll wakeup fd, so the main loop no longer relies on signal handlers interruptingselect. - Ensure
Loop::interruptsOn/Offbecome no-ops on Linux once the signal dependency is removed, just as we did on macOS. - Test the epoll backend on a Linux build: verify spidering, HTTPS fetches, admin UI, Ctrl-C handling, and any timer-driven tasks operate correctly; keep the old select path behind a build flag for fallback.