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
- Off: Strict sequential;
Request/RequestNamedisabled. UseAsk/SendSyncfor sync calls. - AllowAll: All incoming messages can be processed while awaiting a response. Maximizes throughput.
- StashNonReentrant: User messages are stashed while any stash-mode request is in flight; only async responses and
system messages (e.g.
PoisonPill,HealthCheckRequest) are processed. No user message is handled while the actor waits, at the cost of latency.
Enabling reentrancy
Reentrancy is opt-in. PassWithReentrancy when spawning the actor:
Enabling reentrancy
- WithMode: Set reentrancy mode:
Off,AllowAll, orStashNonReentrant. - WithMaxInFlight: Cap the number of outstanding async requests. When reached,
Request/RequestNamereturnErrReentrancyInFlightLimit. Usen <= 0for no limit.
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:Runtime toggle
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
- ctx.Request: Send an async request to a PID. Returns a
RequestCallornilon failure. - ctx.RequestName: Send an async request to an actor by name (local or remote). Returns a
RequestCallornil. - ctx.RequestGrain: Send an async request to a grain, which activates on demand like any other grain send. Returns a
RequestCallornil.
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:
- Then: Register a continuation
funcinvoked when the request completes (success, error, timeout, or cancel). Only the first call is honored. If the request already completed, the callback runs immediately in the caller’s goroutine. - Cancel: Request cancellation. Best-effort; the request may still complete. Idempotent. Returns
nilif already completed or cancel requested.
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
- WithReentrancyMode: Override the actor-level mode for this request only. Cannot enable if actor has reentrancy off.
- WithRequestTimeout: Set a per-request timeout. On expiry, the request completes with
ErrRequestTimeout.<= 0disables.
Per-call options
Errors
- ErrReentrancyDisabled: The actor has no reentrancy policy (neither
WithReentrancyat spawn nor a laterEnableReentrancy), or the effective mode isOff. - ErrReentrancyInFlightLimit:
MaxInFlightcap reached; no new async requests until some complete. - ErrRequestTimeout: Request timed out (via
WithRequestTimeout). - ErrRequestCanceled: Request was canceled via
Cancel, or by the actor stopping or restarting. In that case the continuation does not run: the actor abandons its in-flight requests as it abandons its mailbox, and a caller waiting on a deferred reply times out, as for any ask to an actor that stops before answering.
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, the stashed messages are appended to the mailbox, oldest first. Messages that arrived after the
response but before the actor handled it are already in the mailbox, so they run before the stashed ones.
A restart cancels the request in flight and replays the stashed messages to the new incarnation, in order, after
PostStart, as it keeps every queued message. Messages the actor stashed on its own with WithStashing, with no
request in flight, are not replayed: they stay stashed for the new incarnation to unstash. A stop abandons the stash
with the mailbox.
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. - No interleaving when user messages must not run while a request is in flight: 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.- Passivation is not held for an in-flight request: an actor waiting on a reply is idle to its passivation strategy. Set the idle timeout above the request timeout, or the actor can be passivated before the reply arrives.
- 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.
Example
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.