Files are not sockets
Readiness based multiplexing works beautifully for sockets because a socket is either ready or not. Regular files behave differently. A file on disk is almost always reported as ready, yet the read can still block while the disk seeks and the data loads. So epoll style readiness does not actually save you from blocking on file IO.
The historical workaround
For years the practical answer was a thread pool. The event loop handed file reads to worker threads that issued ordinary blocking reads. This works but spends a thread per outstanding file operation, which is exactly the cost async IO was meant to avoid.
True async file IO
Real completion based interfaces fix this. io uring on Linux and overlapped IO with completion ports on Windows can perform file reads and writes asynchronously, signaling completion when the data is in the buffer. The older Linux aio interface offered this only for direct unbuffered IO with sharp restrictions, which limited its use.
- Readiness models do not help with disk files, because they report ready while still blocking
- Thread pools emulate async file IO at the cost of a thread per operation
- io uring delivers genuine async buffered file IO on Linux
Key idea
Disk files do not fit readiness based multiplexing, so true async file IO needs completion interfaces like io uring or a thread pool to emulate it.