OS & Systems Fundamentals • I/O Models (Blocking, Non-blocking, Async)Hard⏱️ ~3 min
Reactor vs Proactor: Readiness vs Completion Driven I/O
The Reactor pattern (readiness driven) and Proactor pattern (completion driven) represent two fundamental approaches to non-blocking I/O multiplexing. In the Reactor model, a small number of event loops register interest in sockets and receive notifications when they become readable or writable. The application then performs the actual read or write operation in user space. The kernel tells you when you CAN do I/O, but you still have to do it.
The Proactor model inverts this relationship. The application submits I/O operations to the kernel (asynchronous read or write), and the kernel completes the operation in the background, eventually posting a completion notification to a queue. The application processes completed operations with data already transferred into application buffers. The kernel tells you when I/O HAS BEEN DONE.
Reactor patterns are widely portable and predictable. They map naturally to operating system primitives available across platforms. Modern readiness mechanisms can monitor 10,000 file descriptors in approximately 0.66 milliseconds with operations scaling by the number of ready descriptors, not total descriptors tracked. Proactor patterns can reduce system calls and memory copies because the kernel handles data transfer directly, but they shift complexity to completion handling. Applications must carefully manage buffer lifetimes and handle out of order completions.
In practice, most high scale systems use Reactor style architectures because of their predictability and broad OS support. Nginx, HAProxy, and most event driven servers follow the Reactor pattern. Proactor patterns appear in specialized contexts like Windows I/O Completion Ports or Linux asynchronous I/O (io_uring), where the potential for reduced system call overhead justifies the added complexity.
💡 Key Takeaways
•Reactor patterns notify when I/O is READY (you can read or write now). Proactor patterns notify when I/O is COMPLETE (data has been transferred). Reactor requires the application to perform the actual read or write; Proactor has the kernel do it.
•Readiness demultiplexing with modern mechanisms handles 10,000 descriptors in approximately 0.66 milliseconds versus 900 to 990 milliseconds with older mechanisms. This three order of magnitude difference enables services monitoring 100,000 plus concurrent connections.
•Proactor patterns can reduce system calls (one async operation versus separate wait and read/write calls) and avoid some user space to kernel space copies, but require careful buffer lifetime management and out of order completion handling.
•Most production high scale systems use Reactor architectures because of broad OS support and predictable behavior. Nginx and HAProxy both implement Reactor patterns. Proactor appears in specialized contexts like Windows I/O Completion Ports or Linux io_uring.
•Edge triggered readiness (Proactor like semantics for Reactor) requires fully draining buffers until you get EAGAIN or WOULDBLOCK errors. Failing to drain completely can starve events, causing subtle bugs under partial read or write scenarios.
📌 Examples
Nginx: Uses readiness based event loops across platforms. On Linux it leverages epoll to monitor tens of thousands of connections per worker process, achieving approximately 20,000 requests per second on commodity hardware.
Amazon API Gateway: Employs Reactor style event loops to multiplex hundreds of thousands of client connections to backend services, keeping memory footprint low and latency predictable even during traffic spikes.
Windows services: Often use I/O Completion Ports (Proactor model) where async operations post completions to queues. This reduces context switching but requires managing buffer pools and handling completion reordering.