The full picture
- Readiness vs completion — the taxonomy that organizes everything: epoll says 'FD is ready, now you do the read' (reactor pattern); io_uring/Windows IOCP say 'your read has finished, here's the data' (proactor). Files are never 'ready' in a useful sense — that's precisely why epoll can't do disk I/O and io_uring can.
- Edge vs level triggering: level = keep telling me while readable (forgiving); edge = tell me once on transition (fewer wakeups, but you must drain to EAGAIN or hang forever — the classic Netty/epoll-ET bug class).
- The thundering herd: many threads waiting on one socket, all woken per event, one wins — wasted switches; fixed by EPOLLEXCLUSIVE / SO_REUSEPORT sharding (nginx's accept_mutex story).
- Where it lands for a JVM engineer: Netty/event loops = epoll + reactor + you must never block the loop; virtual threads = keep blocking code, JVM parks/unmounts on readiness underneath. Same kernel machinery, opposite programming models — and io_uring adoption (Netty incubator, databases) is the current frontier.