Concept
By default, an actor processes messages strictly one at a time. Reentrancy allows an actor to process other messages while waiting for an async response fromRequest or RequestName.
Request and RequestName are the non-blocking counterparts of Ask and SendSync. They return immediately with a RequestCall handle instead of blocking until a reply arrives. The reply is delivered back through the actor’s mailbox, preserving single-threaded processing.
Modes
Enabling reentrancy
Reentrancy is opt-in. PassWithReentrancy when spawning the actor:
Without a reentrancy policy, calls to
Request, RequestName, or RequestGrain fail with ErrReentrancyDisabled.
Runtime toggle
An actor spawned without reentrancy can enable it from inside a handler when the capability is only needed for a particular case, and turn it off again afterward:EnableReentrancy installs the policy or retunes an existing one (mode, MaxInFlight) and takes effect for requests issued from that point on. DisableReentrancy flips the default mode back to Off: requests already in flight complete with the mode they were admitted with, stashed messages are released once the last blocking request finishes, and new requests fail with ErrReentrancyDisabled. After a runtime disable, a per-call WithReentrancyMode override still admits an individual request.
Both methods also exist on GrainContext; see Grain Reentrancy.
Request, RequestName, and RequestGrain
Requests are location transparent: when the requester and the target sit on different nodes, the request and its reply travel over remoting, so cross-node payloads must be protobuf messages, like every other remote send.
On failure to initiate the request,
Err is set on the context and the returned call is nil. Check ctx.Err() or use ctx.getError() in tests.
RequestCall
The returnedRequestCall lets you:
Continuations registered with
Then run on the actor’s mailbox thread when the request completes, preserving single-threaded access to actor state. Call Then from within Receive to ensure correct execution.
Per-call options
Errors
StashNonReentrant and stashing
InStashNonReentrant mode, user messages are automatically stashed while any stash-mode request is in flight. A stash buffer is created on demand; you do not need WithStashing() for reentrancy-driven stashing. When the last blocking request completes, stashed messages are unstashed and processed in order.
When to use
- Actors that make async requests and need to stay responsive (e.g. fan-out, long I/O via PipeTo).
- Avoiding deadlock in call cycles (A -> B -> A). Use
AllowAllso A can process B’s reply while waiting. - Strict ordering when you must not interleave user messages with async responses: use
StashNonReentrant.
Production notes
- Prefer AllowAll for throughput and to avoid deadlocks in call cycles.
- Use StashNonReentrant only when strict message ordering is required. Pair it with:
- A finite
MaxInFlightlimit to bound memory. - Per-request timeouts (
WithRequestTimeout) to avoid unbounded stashing if dependencies stall.
- A finite
AllowAllcan introduce state races if your logic assumes strict ordering between request and response.- Mixed-version clusters may decode unknown modes as
Off, disabling async requests.
Example
Reentrancy enables safe request/response cycles without deadlock. Flow: Client → ActorA → ActorB → ActorA → Client. ActorA usesRequestName (non-blocking); ActorB uses Ask (blocking). Without reentrancy, ActorA would block and the cycle would deadlock.
See also
- Grain Reentrancy for the grain-side API:
RequestGrain/RequestActorfrom grains,DeferResponse, and paused-consumption stash semantics. - Stashing: Manual stashing with
Stash,Unstash,UnstashAll;StashNonReentrantuses stashing internally. - Messaging:
Ask,Tell,PipeTo. - Behaviors:
Become,UnBecomefor state transitions.