Concept
Grains are virtual actors: identity-addressed and managed by the framework. They are activated on first message and deactivated when idle. You don’t spawn them explicitly; you send to an identity and the framework routes to the right instance.The Grain interface
Every grain must implement theGrain interface:
GrainContext
InsideOnReceive, GrainContext provides:
Identity and messaging
Use the genericactor.GrainOf function to obtain an identity. The type parameter must be a pointer to your grain
struct; the framework derives the grain kind from it, so no factory is required. Then use TellGrain or AskGrain
with that identity. The instance name (e.g., "user-123") uniquely identifies the grain within its kind.
OnActivate and supply external resources (database handles, clients, configuration) through
WithGrainDependencies; they are available from the GrainProps passed to OnActivate. This is the same construction
contract the cluster applies when it recreates or relocates a grain on another node.
Calling
GrainOf automatically registers the grain kind on the calling node. In cluster mode, nodes that can host a
grain without ever resolving it themselves (for example, targets of relocation or remote activation) must register the
kind at startup, either with RegisterGrainKind or through ClusterConfig.WithGrains.Reentrancy
By default a grain processes one message at a time. Reentrancy lets a grain issue non-blocking requests to other grains or actors (RequestGrain, RequestActor) and keep
processing while the replies are in flight, which is what lets request cycles (A asks B while B
asks A back) complete instead of deadlocking. It also unlocks DeferResponse, which answers an
incoming Ask only after an outbound request completes.
Enable it at activation with WithGrainReentrancy or at runtime with ctx.EnableReentrancy.
Modes, per-call options, deferred replies, the passivation interaction, and a complete
request-cycle example are on the Grain Reentrancy page.
PipeTo
A grain processes one message at a time, so blocking on slow I/O insideOnReceive stalls every queued message.
PipeToSelf, PipeToGrain, and PipeToActor offload that work: the task runs in a goroutine outside the message
loop, the grain keeps processing, and the outcome comes back as an ordinary message (*StatusFailure on failure for
grain targets). Methods, failure semantics, options, and a complete example are on the
Grain PipeTo page.
When to use grains
- Entity-per-identity patterns (users, sessions, devices)
- Large populations that are mostly idle
- When you want the framework to manage lifecycle and placement
When to use actors
- Long-lived services
- Explicit lifecycle control
- Infrastructure components
Grain lifecycle
- First message arrives → framework activates the grain (calls
OnActivate) - Messages are routed to the grain’s
OnReceive - After idle timeout (passivation) → grain is deactivated (
OnDeactivate) - Next message → grain is reactivated
Activation guarantee
In cluster mode, the system claims grain ownership in the cluster registry using an atomic put-if-absent operation before activating locally. If another node already owns the grain, requests are forwarded to that owner. If local activation fails after a successful claim, the claim is removed. When configured, the grain activation barrier delays grain activations until the cluster reaches the minimum peers quorum (or until the barrier timeout elapses).See also
- Placement: Where grains activate in a cluster: strategies, roles, and ownership
- Grain Timers: Volatile, activation-scoped timers for periodic grain behavior
- Passivation: Grains use passivation for idle-based deactivation
- Actor Model: When to use actors vs grains