On macOS (kqueue backend), a task woken by an in-process event (e.g. flume channel send) is not scheduled promptly when the runtime is blocked in kevent(). The waker notification does not interrupt the kernel poll, so the woken task only runs after an unrelated I/O event or timer fires. On the io_uring backend (Linux), the same scenario works correctly.
Repro:
use std::time::Duration;
#[compio::main]
async fn main() {
let (tx, rx) = flume::bounded::<u32>(1);
// Spawned task: wait for a message, then print it.
compio::runtime::spawn(async move {
let val = rx.recv_async().await.unwrap();
println!("received: {val}");
})
.detach();
// Let the spawned task park on recv_async.
compio::time::sleep(Duration::from_millis(50)).await;
// Send a message. flume wakes the spawned task's waker.
tx.send(42).unwrap();
// This sleep should let the spawned task run, but on macOS
// the runtime enters kevent() without checking for ready tasks,
// so the spawned task never runs until this timer fires.
compio::time::sleep(Duration::from_millis(500)).await;
// On Linux: "received: 42" prints almost instantly.
// On macOS: "received: 42" prints after the full 500ms,
// or not at all if replaced with a future that
// blocks on the spawned task's output.
}
Replace the final sleep with a future that depends on the spawned task completing (e.g. waiting for data the spawned task would write to a socket), and the program deadlocks on macOS.
Expected behavior
When a task's Waker is invoked (by flume, tokio::sync, event-listener, or any other in-process notification), the runtime should break out of kevent() and schedule the woken task before re-entering the kernel poll.
io_uring backends typically use an eventfd registered with the submission queue for this purpose. The kqueue equivalent would be EVFILT_USER or a self-pipe.
Observed behavior
On macOS, the woken task is not scheduled until the next unrelated I/O completion or timer expiry causes kevent() to return. If no other event is pending, the woken task starves.
Environment
- compio 0.19.0-rc.2 / compio-driver 0.12.0-rc.2
- macOS 15.7.3
rustc 1.95.0 (59807616e 2026-04-14) (Homebrew)
On macOS (kqueue backend), a task woken by an in-process event (e.g. flume channel send) is not scheduled promptly when the runtime is blocked in
kevent(). The waker notification does not interrupt the kernel poll, so the woken task only runs after an unrelated I/O event or timer fires. On the io_uring backend (Linux), the same scenario works correctly.Repro:
Replace the final
sleepwith a future that depends on the spawned task completing (e.g. waiting for data the spawned task would write to a socket), and the program deadlocks on macOS.Expected behavior
When a task's
Wakeris invoked (by flume, tokio::sync, event-listener, or any other in-process notification), the runtime should break out ofkevent()and schedule the woken task before re-entering the kernel poll.io_uring backends typically use an eventfd registered with the submission queue for this purpose. The kqueue equivalent would be
EVFILT_USERor a self-pipe.Observed behavior
On macOS, the woken task is not scheduled until the next unrelated I/O completion or timer expiry causes
kevent()to return. If no other event is pending, the woken task starves.Environment
rustc 1.95.0 (59807616e 2026-04-14) (Homebrew)