Course contentsShow
← Rust
Lesson 1272 of 1,91011. Asynchronous ProgrammingFree lesson

Structuring Reactor-Executor Systems

Design modular systems where separate reactors handle I/O events while executors drive business logic.

In this lesson, you will learn how to decouple low-level event monitoring from high-level task execution by splitting your system into a Reactor and an Executor.

The Decoupling Pattern

In a monolithic async runtime, the logic that polls for I/O readiness (the reactor) is often tightly coupled with the logic that schedules tasks (the executor). By structuring these components separately, you create a modular system where the reactor only cares about "is this socket readable?" while the executor only cares about "is this future ready to advance?"

  1. The Reactor: Its sole responsibility is to interface with OS-level primitives like epoll, kqueue, or IOCP. It maintains a map of interest handles and pushes notifications to a shared queue when an event occurs.
  2. The Executor: This component sits in a loop, pulling tasks from a queue and calling poll on them. It does not know how I/O is monitored; it only knows that when a task returns Poll::Pending, it should register interest with the reactor.

Concrete Implementation Sketch

Conceptually, the interaction looks like this:

// The Reactor manages OS notifications
fn reactor_loop(waker_map: Arc<WakerMap>) {
    loop {
        let events = wait_for_io(); // e.g., epoll_wait
        for event in events {
            // Wake the specific task that was waiting on this I/O
            waker_map.wake(event.id);
        }
    }
}

// The Executor drives the business logic
fn executor_loop(task_queue: Receiver<Task>) {
    while let Ok(task) = task_queue.recv() {
        // When poll returns Pending, the task has registered 
        // its Waker with the Reactor
        let _ = task.poll(); 
    }
}

Why This Matters

When you structure your system this way, you gain runtime independence. Because your business logic is strictly decoupled from the I/O event loop, you can swap out your reactor implementation (for example, moving from a standard poll-based reactor to a high-performance io-uring reactor) without ever touching your high-level business code. This separation of concerns simplifies testing and allows you to optimize I/O polling mechanisms independently of your task scheduling policies.

Key Takeaway: By isolating I/O registration in a Reactor and task progress in an Executor, you create a clean, maintainable architecture where hardware-level event handling remains decoupled from application business logic.