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?"
- The Reactor: Its sole responsibility is to interface with OS-level primitives like
epoll,kqueue, orIOCP. It maintains a map of interest handles and pushes notifications to a shared queue when an event occurs. - The Executor: This component sits in a loop, pulling tasks from a queue and calling
pollon them. It does not know how I/O is monitored; it only knows that when a task returnsPoll::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.