Io_uring can keep track of events through using event_fds, but yes, that is perhaps not optimal.
- kqueue only tells you there's data to read (/space in the write buffer). You still need to call read() or write(), including paying the cost of the syscall. io_uring lets you batch a lot of read/write calls together and either issue a single syscall to the kernel for all calls, or have the kernel poll and never syscall at all.
- kqueue doesn't let you issue fsync, or any of the other syscalls now in io_uring. fsync is essential on the write path for correctness in lots of cases, and for that you still need to dispatch to a local thread pool or something.
So yeah, I prefer kqueue over linux's epoll. But io_uring seems like the new king.