Reactor and proactor ownership
Compare where an operation executes, which state a handler receives, and how both patterns preserve request identity.
On this page
The question for today
What you’ll understand
- Trace reactor and proactor request lifecycles.
- Separate operation execution from completion dispatch.
- Preserve identity and ownership across out-of-order results.
The words reactor and proactor describe a division of responsibilities, not a ranking of fashionable libraries. A reactor tells application code when it should attempt work. A proactor dispatches application code after an asynchronous operation produces a result. Both can hide waiting, and both can fail badly when state, ownership, and backpressure are left implicit. Compare their traces before comparing their throughput.
Place the operation on the trace
In a reactor design, a dispatcher waits for resource conditions and invokes the associated handler. That handler attempts I/O, interprets the result, and updates application state. A connection can therefore have readable input without containing a complete protocol frame. Separate the generic dispatcher from connection-specific parsing and request state. The dispatcher should not need to understand every message format, but its registration and lifetime rules must make it impossible to dispatch into an already-destroyed handler.
In a proactor design, an initiator requests an asynchronous operation and supplies enough state to associate a later result with its continuation. A completion dispatcher delivers the resulting status and progress. The operation might be carried out by an operating-system facility or by workers beneath an asynchronous facade. Application code must understand the actual contract rather than infer hardware behavior from the pattern name. Completion ordering and buffer lifetime remain explicit obligations even when initiating code looks sequential.
A result belongs to an operation, not just a connection
Several operations may reference the same connection while completing in a different order from submission. Store a stable operation identity and the expected transition with each request. Do not confuse a reused descriptor number with the original connection object. A generation number or another lifetime-safe handle can reject stale callbacks. The application must also decide whether two writes are allowed concurrently and how their ordering relates to its protocol; the dispatch pattern alone does not establish message order.
Draw ownership changes alongside control flow. Before submission, the application prepares a request and its buffer. While an operation is outstanding, permitted readers and writers follow the API contract. On a terminal result, the owner interprets progress and releases or transfers resources exactly once. If a task times out, some underlying work may still exist. A timeout continuation cannot free memory merely because the caller has stopped waiting. Error paths must respect the same terminal boundary as successful paths.
Choose interfaces from constraints
A readiness-based socket service may already have a good protocol state machine and modest copying costs. A completion-based storage pipeline may benefit from explicitly managing a bounded set of outstanding reads. Neither observation proves a universal winner. Compare supported operations, runtime portability, cancellation semantics, submission costs, memory pressure, and the complexity of shutdown. A hybrid often exists: one layer exposes futures while another layer uses readiness or a blocking worker pool. Review the boundary that your team actually owns.
The queue lab isolates only offered work and draining capacity. Press Reset, set arrival rate to 4 and service rate to 3, predict growth from the initial four-job backlog, then press Play. For a separate draining experiment, press Reset again, set arrival rate to 2 and service rate to 4, predict the slope, then press Play. Reset both rates for each new run rather than treating a slider edit as a mid-run capacity change. This can motivate bounding outstanding operations, but it cannot choose reactor versus proactor or count per-request notifications. For that choice, write two traces of the same request and mark the instruction that obtains its result, the component allowed to release its buffer, and the error path that removes its registration.
Work it through
Audit outstanding operation accounting
A completion-oriented toy service begins with no live operations. It submits 120 distinct one-shot reads. During the observed interval, 90 finish successfully, 10 finish with errors, and cancellation is requested for 8 others without their terminal results yet arriving.
Count terminal operations: 90 successful plus 10 failed equals 100. An error is still terminal when the API says the operation has finished.
Subtract terminal operations from submissions: 120 − 100 = 20 operations remain live. The eight cancellation requests do not yet subtract from this number.
Partition the 20 as eight cancellation-requested and twelve other outstanding operations. Continue retaining their required resources until their own terminal contracts are satisfied.
Twenty operations remain live. Counting cancellation requests as completed cleanup would undercount by eight and could cause premature release.
The living notebook
Explore the model
Interactive lab · queue
Patterns still need bounded outstanding work
Press Reset, explicitly set arrival rate to 4 and service rate to 3, predict growth, then press Play. Start the draining comparison with another Reset and both rates set to 2 and 4. This is a capacity illustration from four initial jobs, not a reactor/proactor execution trace or API throughput comparison.
- Fluid backlog
- 4
- Net rate · λ − μ
- −1 /s
- M/M/1 mean residence
- 1 s
Fluid backlog starts at 4 and follows max(0, 4 + (λ − μ)t). The separate M/M/1 residence-time formula assumes Poisson arrivals, exponential service and λ < μ. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forChanging notification style does not remove the need to bound and account for live operations.
Pause & reason
Check your understanding
Your turn
Try it yourself
A service submits 70 one-shot requests, observes 45 successful terminals and 5 error terminals, then requests cancellation of 12 remaining requests. How many are still live before further results arrive?
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Classify terminal outcomes before considering cancellation intent.
Compare with the solution
There are 50 terminal requests, so 70 − 50 = 20 remain live. Twelve of those have cancellation requested. Their request objects and API-required buffers remain owned until a terminal result or another documented quiescence guarantee permits release. Do not subtract a cancellation-control completion if it does not itself establish the target operation’s termination.
Keep the idea
Take it with you
- Reactor handlers attempt operations; proactor handlers receive prior operation results.
- Stable identities and explicit lifetimes matter under either pattern.
- A cancellation request is not interchangeable with a terminal operation result.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.