Reactor and Proactor Patterns
Reactor demultiplexes readiness events and dispatches handlers that perform the I/O themselves; proactor demultiplexes completion events for I/O the kernel already performed asynchronously.
Definition
Reactor is the readiness pattern: an event loop waits on a multiplexer (epoll, kqueue), notifies handlers that a descriptor is ready, and the handler executes the non-blocking read or write. Proactor is the completion pattern: the initiator posts an asynchronous operation with buffers, the OS completes it, and the completion dispatcher invokes the handler with results. POSIX reactors dominate Linux; Windows IOCP is proactor-native.
Context and scope
Node.js and nginx are reactor-shaped; Windows IOCP, Boost.Asio async operations, and io_uring lean proactor. The choice shapes buffer ownership and error semantics.
Common misconceptions
- Reactor and proactor are synonyms — reactor notifies readiness, you do the I/O; proactor notifies completion, the kernel did the I/O
- Linux cannot do proactor — io_uring brings true asynchronous completion semantics to Linux, beyond readiness polling
- The pattern dictates throughput — buffer management and handler duration dominate; the pattern sets who owns buffers and where errors surface
Anti-patterns
- Doing heavy computation inside reactor handlers and starving the event loop
- Reusing completion buffers after posting an asynchronous write and racing the kernel
- Porting reactor code to a proactor API without rethinking buffer ownership and lifetime
Related concepts
Related examples
Authoritative source
What it is
Two answers to the same question: how does one thread manage thousands of I/O operations. Reactor waits for readiness and delegates the action; proactor initiates the action with its buffers and waits for the kernel to report it done.
How it works
Reactor: register handlers per descriptor; on readiness, dispatch; handler reads or writes non-blocking, handling EAGAIN and partial transfers itself. Proactor: post operation plus buffer; kernel DMA fills or drains it; completion queue delivers results; handler processes finished data. io_uring unified submission and completion rings make proactor-style programming natural on Linux.
When it matters
Designing network runtimes and understanding buffer lifetimes: reactor handlers own buffers during the call, while proactor buffers are pinned from post until completion — an ocean of subtle lifetime bugs for the unprepared. The pattern choice also decides where errors surface and how cancellation propagates.
Pitfalls
Reactor loops serialize — one slow handler delays every other ready descriptor, so offload compute. Proactor buffer reuse before completion corrupts in-flight data; cancellation semantics differ per platform and often leave operations completing anyway. Error reporting splits: readiness errors on dispatch, completion errors in results, and half of the ecosystem documentation conflates them.