Skip to main content

States

A PID tracks its state through a set of flags. The main states:
  • Running: Actor is active and processing messages.
  • Stopping: Shutdown or passivation in progress.
  • Suspended: Suspended by the supervisor after a failure.
  • Passivating: Idle timeout; about to be stopped.
Use pid.IsRunning(), pid.IsSuspended(), pid.IsStopping() to query state when needed.

Lifecycle hooks

  • PreStart: Called once when the actor is spawned. Use for initialization.
  • Receive: Called for each message. This is where your logic lives.
  • PostStop: Called once when the actor is about to stop. Use for cleanup.

Spawn and stop

  • Spawn: Creates the actor, registers it, calls PreStart, and starts the mailbox dispatch loop.
  • Stop: Stops the actor at once, runs PostStop, and removes it from the tree. See the warning under Lifecycle hooks for the message being processed and the messages still queued.
When a parent stops, all children stop first (depth-first). The order among descendants is not guaranteed. The name of a stopped actor is free as soon as the stop returns. Once Kill, Shutdown, or a parent’s Stop of a child returns, ActorOf and ActorExists no longer find the actor, and spawning the same name creates a new actor. This holds even when PostStop returns an error: the actor is dead, its watchers still receive Terminated, and the error is returned to the caller. Passivation frees the name the same way. A restart does not free the name. The restarted actor and the children it restarts keep their names, and in cluster mode their registry records, for the whole restart, so a lookup from another node keeps resolving them. If the restart fails, for example because PreStart returns an error, the whole subtree is stopped: no actor of it is left running, their watchers receive Terminated, and their names are free.
Without a cluster, spawning a name that a running actor holds returns that actor. Spawning a name held by an actor that is not running, because it is suspended, stopping or restarting, fails with ErrActorAlreadyExists and starts nothing: reinstate, restart or stop that actor first. In cluster mode a taken name always fails with ErrActorAlreadyExists, even when the actor holding it runs on the same node; use ActorOf to get that actor.
In cluster mode the stopped actor’s registry record is removed in the background. A spawn of the same name on the same node writes its own record over it, so Kill followed by Spawn works on one node as it does without a cluster. A spawn of the name on another node can still see the old record for a short while and fail with ErrActorAlreadyExists.

Lifecycle hooks (Actor interface)

The Actor interface defines three lifecycle hooks. See Actor Model for the full interface.
  • PreStart: called before first message. Initialize DB, caches, recover state. Return error to abort startup.
  • Receive: called for each message. Process messages. State is private; no locks needed.
  • PostStop: called before termination. Release resources, flush buffers, notify other systems.
PID.Shutdown, ActorSystem.Stop, system eviction and a parent stopping its children stop an actor at once: they do not wait for the message it is processing, so PostStop can run while Receive is still handling that message. State shared by the two must be safe for concurrent use. A PoisonPill, ctx.Shutdown() and per-actor passivation stop the actor from its own turn or while it is idle, so PostStop never runs concurrently with Receive. In every case, user messages still queued are discarded.
If PreStart returns an error, the actor is not started and the supervisor handles the failure.

Init timeout

A failed PreStart is retried. The init timeout bounds those retries: a new attempt starts only while the timeout has not elapsed, up to the configured number of attempts (WithActorInitMaxRetries, 5 by default), and when the last one fails the actor is not started. The system-wide default is set with WithActorInitTimeout on the actor system; override it per actor with the WithInitTimeout spawn option:
Init timeout
The init timeout does not interrupt a PreStart attempt in progress, and the context PreStart receives carries no deadline from it. A PreStart that runs past the timeout and then succeeds starts the actor. Bound slow initialization work, such as connecting to a database, with a deadline of your own inside PreStart.
A non-positive duration is ignored, leaving the system-wide timeout in effect. Child actors and relocated actors honor an explicit WithInitTimeout; without one they use the hosting node’s system-wide timeout. The override travels with remote spawns and cluster relocation so an actor keeps its configured init timeout wherever it is placed.

System messages

The framework injects system messages into the actor’s mailbox to drive lifecycle and coordination. These are plain Go structs in the actor package. Handle them in Receive when you need to react.

Lifecycle control

  • PostStart: Delivered as the first message after the actor is spawned or restarted. Use it to capture ctx.Self, set up references, or perform logic that must run after the actor is fully started. No other message overtakes it, including system messages such as Terminated from a child that stopped right away, so state set in PostStart is always ready when the next message arrives. When PostStart runs, the actor is in the actor tree and, in a cluster, its registry record is written, so ActorExists, Partition and lookups from other nodes already answer for its name. A message that reaches the actor by name while the spawn is still publishing it can run PostStart earlier.
  • PoisonPill: Gracefully stops the actor. It is a system message: it skips the user mailbox and is handled as soon as the actor finishes the message in hand, ahead of any user messages still queued. The actor then stops its children, runs PostStop and notifies its watchers. User messages still in the mailbox are discarded, not processed. For an ordered shutdown that first handles everything sent before it, define your own stop message and call ctx.Shutdown() when the actor receives it: it travels through the mailbox in order with the other user messages.

Death watch

  • Terminated: Sent to every actor that is watching another actor when that watched actor stops. Use it to clean up references, restart dependents, or trigger failover. Subscribe via ctx.Watch; cancel via ctx.UnWatch. See Death Watch.

Event stream (observability)

These events are published to the event stream for subscribers. You typically do not handle them in Receive unless you have a specific need:
  • ActorStarted: Actor has started.
  • ActorStopped: Actor has stopped.
  • ActorChildCreated: Child actor was spawned.
  • ActorPassivated: Actor was passivated (idle timeout).
  • ActorRestarted: Actor was restarted by supervisor.
  • ActorSuspended: Actor was suspended after a failure.
  • ActorReinstated: Actor was reinstated by supervisor.
  • NodeJoined / NodeLeft: Cluster membership change.

Handling system messages

Add a case for the system messages you care about. For messages you don’t handle, call ctx.Unhandled() so the framework can apply default behavior (e.g., logging, event publishing).