3.7 KiB
3.7 KiB
Cycle 15: Ask Pattern for Typed Request-Response — Development History
Commit:
902471b· 3 files · 166 insertions, 1 deletion
Motivation
Request-response is one of the most common actor communication patterns: "send a question, wait for the answer." Before this change, implementing request-response in swactor required manual inbox creation, message construction with a reply-to address, sending, ticking, and polling — a verbose 5-step process. Every mature actor framework provides a convenience wrapper for this pattern.
Competitor Analysis
| Framework | Pattern | Mechanism | Synchronous? |
|---|---|---|---|
| Erlang | gen_server:call |
From + gen_server:reply |
Blocks caller (with timeout) |
| Akka | ask |
Temporary actor + Future |
Returns Future |
| Ractor | call |
RpcReplyPort (oneshot channel) |
Returns JoinHandle |
| Kameo | ask |
Async + Reply trait |
Returns Future |
| xactor | Handler::handle |
Return value auto-routed | Implicit |
| Swactor | rt.ask() |
Inbox + closure | recv_ticking (tick-driven) |
Key Findings
- Swactor's synchronous tick model requires explicit
reply_to— there's no async runtime to suspend the caller - Implicit auto-reply rejected — would add magic to the message pipeline and complicate the actor interface
- Decision: convenience wrapper over existing inbox pattern (not a new mechanism)
Implementation
Ask<R> Struct
- Wraps an
Inbox<R>with convenience methods try_recv()— poll without ticking (works in both single and multi-threaded modes)recv_ticking(rt, max_ticks)— tick the runtime until a response arrives or timeout (single-threaded only)reply_addr()— access the inbox address for manual use
Runtime::ask()
rt.ask(addr, |reply_to| Msg { reply_to })— one-line request-response- Creates inbox, builds message via closure (user provides the reply_to field), sends, returns
Ask<R> - Purely sugar over the existing
new_inbox → send_to → tick → try_recvpattern
No Internal Changes
- Zero changes to
ContextInnerorActorInterface - No implicit auto-reply magic
- Actors reply by explicitly sending to the
reply_toaddress (same as before)
Key files modified: src/runtime.rs, tests/runtime_api.rs
Design Decisions
- Closure-based message construction —
rt.ask(addr, |reply_to| Msg { reply_to })lets the user embed the reply address in any message shape. No trait requirements on the message type (beyondMessage). recv_tickingfor single-threaded — in single-threaded mode, the runtime must be ticked for the target actor to process the request and reply.recv_tickingdoes this automatically. In multi-threaded mode, usetry_recvwith your own tick loop.- No implicit reply — frameworks like xactor auto-route the handler's return value as a reply. This is magical and doesn't fit swactor's explicit model. The ask pattern wraps existing mechanics without adding new ones.
- max_ticks timeout — instead of wall-clock timeout, uses tick count for deterministic behavior (consistent with Cycle 10 timers).
Tests Added
5 new tests (122 → 127 total):
ask_recv_ticking_returns_response— basic PingPong ask roundtripask_multiple_times_tracks_state— 3 sequential asks to CounterActor, state incrementsask_timeout_when_no_response— ask dead actor → timeout errorask_try_recv_returns_none_before_tick— poll before tick → None, after tick → Someask_reply_addr_is_accessible— reply address is valid for manual use
Result
- 127 tests pass (120 behavioral + 7 proptest)
- All workspace crates compile, zero warnings