libkqueue
April 29, 2026 ยท View on GitHub
This documents the current behavioural differences or defficiencies of libkqueue vs the native BSD kqueue implementations.
Common
-
A handful of stub functions still silently fail or succeed instead of emitting
FIXME -- UNIMPLEMENTED. Rungrep STUB src/*/*.cfor the current list. -
fork(2)without a subsequentexec(3)leaks libkqueue's internal heap allocations in the child. POSIX restrictspthread_atforkchild handlers to async-signal-safe functions, andfree(3)is not AS-safe, so the child handler can onlyclose(2)inherited file descriptors (kqueue'sepollfdon Linux,pipefd[0..1],signalfdfor EVFILT_SIGNAL, etc.) but cannot release the per-knote / per-filter heap state. Per-knotepidfds and per- filtereventfds are similarly orphaned because walking the knote tree to close them would require malloc-protected lock acquisitions. The accepted contract is: a forked child mustexec()(which replaces the address space) or_exit()shortly afterward. This matches FreeBSD native kqueue behaviour. Running libkqueue in a long-lived forked child withoutexec()is unsupported.
POSIX
-
EVFILT_PROC- The POSIX implmentation requires thatSIGCHLDbe delivered to its global waiter thread so that the waiter can discover a when child process exits. To preventSIGCHLDbeing delivered to another threadsigprocmask(2)is used to maskSIGCHLDat a process level.
If the application unmasksSIGCHLDor installs a handler for it, the POSIXEVFILT_PROCcode will not function. -
EVFILT_PROC- If using the POSIXEVFILT_PROCthe number of monitored processes should be kept low (< 100). Because the Linux kernel coalescesSIGCHLD(and other signals), the only way to reliably determine if a monitored process has exited, is to loop through all PIDs registered by any kqueue when we receive aSIGCHLD. This involves many calls towaitid(2)and may have a negative performance impact. -
EVFILT_PROC- The notification list of the global waiter thread and the ready lists of individual kqueues share the same mutex. This may cause performance issues where large numbers of processes are monitored.
Linux
-
EVFILT_SIGNAL- The signalfd-backed implementation requires a signal to be blocked on every thread that could otherwise take delivery, otherwise the kernel runs the disposition before the signal lands in the pending queue that signalfd reads from. POSIX has no process-wide block primitive:pthread_sigmask(2)(andsigprocmask(2), whose behaviour is undefined in multithreaded programs) only sets the mask on the calling thread. When the first knote for a signal is registered, libkqueue blocks that signal on the registering thread; threads spawned afterwards inherit the mask, but pre-existing threads are unaffected. Applications that registerEVFILT_SIGNALafter spawning worker threads mustpthread_sigmask(SIG_BLOCK, ...)the monitored signals on those threads themselves. -
EVFILT_SIGNAL- Thread-targeted signals (pthread_kill,tgkill) sent to a thread other than libkqueue's internal signal dispatch thread will NOT be observed via kqueue. The kernel routes thread-targeted signals into the target thread's per-thread pending queue, which only that thread's signalfd reader can drain. Process-targeted signals (kill(getpid(), ...)) land in the per-process pending queue where the dispatcher sees them normally. Native BSD/macOS kqueue is in-kernel and observes both. -
EVFILT_SIGNALonSIGCHLDis rejected withEINVALwhen libkqueue is built withoutpidfd_open(2)(kernels < 5.3, or builds where the syscall is unavailable). In that configuration the POSIXEVFILT_PROCwaitersigwaitinfosSIGCHLDto detect child exits; permittingEVFILT_SIGNALto also watch it would race the two consumers for each fire, with the loser observing nothing. Builds withpidfd_open(2)use the pidfd-basedEVFILT_PROCbackend, where this constraint doesn't apply. -
EVFILT_SIGNALonSIGRTMIN+1is rejected withEINVAL. The monitoring threadsigwaitinfos this signal to detect kqueue fd closures (set per-pipefd viafcntl(F_SETSIG)); a parallelEVFILT_SIGNALreader would race it and leave kqueue cleanup blind to closures. -
If a file descriptor outside of kqueue is closed, the internal kqueue state is not cleaned up. On Linux this is pretty much impossible to fix as there's no mechanism to retrieve file descriptor reference counts and no way to be notified of when a file descriptor is closed. Applications should ensure that file descriptors are removed from the kqueue before they are closed.
-
EVFILT_PROC- OnlyNOTE_EXITis currently supported. Other functionality is possible, but it requires integrating with netlink to receive notifications. If building against Kernels < 5.3 (wherepidfd_open()is not available) the POSIXEVFILT_PROCcode is used. See the POSIX section above for limitations of the POSIXEVFILT_PROCcode. -
EVFILT_PROC- Native kqueue provides notifications for any process that is visible to the application process. On Linux/POSIX platforms only direct children of the application process can be monitored for exit.
Solaris
-
EVFILT_SIGNAL- The signalfd-backed implementation requires a signal to be blocked on every thread that could otherwise take delivery, otherwise the kernel runs the disposition before the signal lands in the pending queue that signalfd reads from. POSIX has no process-wide block primitive:pthread_sigmask(2)(andsigprocmask(2), whose behaviour is undefined in multithreaded programs) only sets the mask on the calling thread. When the first knote for a signal is registered, libkqueue blocks that signal on the registering thread; threads spawned afterwards inherit the mask, but pre-existing threads are unaffected. Applications that registerEVFILT_SIGNALafter spawning worker threads mustpthread_sigmask(SIG_BLOCK, ...)the monitored signals on those threads themselves. -
EVFILT_SIGNAL- Thread-targeted signals (pthread_kill,tgkill) sent to a thread other than libkqueue's internal signal dispatch thread will NOT be observed via kqueue. The kernel routes thread-targeted signals into the target thread's per-thread pending queue, which only that thread's signalfd reader can drain. Process-targeted signals (kill(getpid(), ...)) land in the per-process pending queue where the dispatcher sees them normally. Native BSD/macOS kqueue is in-kernel and observes both. -
EVFILT_SIGNALonSIGCHLDis rejected withEINVAL. illumos has nopidfd_open(2)equivalent so the POSIXEVFILT_PROCwaitersigwaitinfosSIGCHLDto detect child exits; permittingEVFILT_SIGNALto also watch it would race the two consumers for each fire, with the loser observing nothing. -
If a file descriptor outside of kqueue is closed, the internal kqueue state is not cleaned up. Applications should ensure that file descriptors are removed from the kqueue before they are closed.
Windows
- If a file descriptor outside of kqueue is closed, the internal kqueue state is not cleaned up. Applications should ensure that file descriptors are removed from the kqueue before they are closed.