Skip to main content
Stashing lets an actor temporarily buffer messages instead of processing them. Use it when the actor cannot handle certain messages in its current state (e.g., waiting for a resource, or during a behavior transition).

Enabling stashing

Pass WithStashing() as a SpawnOption:
pid, err := system.Spawn(ctx, "my-actor", actor, actor.WithStashing())
Without WithStashing(), calls to Stash, Unstash, or UnstashAll record ErrStashBufferNotSet.

API

MethodPurpose
ctx.Stash()Buffer the current message. It will not be processed until unstashed. The message is moved to a side buffer; the actor continues with the next message.
ctx.Unstash()Dequeue the oldest stashed message and prepend it to the mailbox. Processes one message.
ctx.UnstashAll()Move all stashed messages back to the mailbox in arrival order.
Stashed messages are processed in FIFO order when unstashed.

When to use

  • Behavior transitions β€” Stash messages you cannot handle in the current behavior; unstash after switching.
  • Conditional processing β€” Stash messages until a condition is met (e.g., dependency ready, lock acquired).
  • Reentrancy β€” With StashNonReentrant mode, in-flight Ask requests cause user messages to be stashed automatically until the request completes.

Example

A gate actor that buffers requests until opened:
package main

import (
    "context"
    "fmt"
    "time"

    "github.com/tochemey/goakt/v4/actor"
)

type Open struct{}
type Request struct{ Id string }

type GateActor struct {
    open   bool
    served []string
}

func (a *GateActor) PreStart(ctx *actor.Context) error { return nil }
func (a *GateActor) PostStop(ctx *actor.Context) error  { return nil }

func (a *GateActor) Receive(ctx *actor.ReceiveContext) {
    switch msg := ctx.Message().(type) {
    case *Open:
        a.open = true
        ctx.UnstashAll()
    case *Request:
        if a.open {
            a.served = append(a.served, msg.Id)
        } else {
            ctx.Stash()
        }
    default:
        ctx.Unhandled()
    }
}

func main() {
    ctx := context.Background()
    system, _ := actor.NewActorSystem("stash-demo", actor.WithLoggingDisabled())
    _ = system.Start(ctx)
    defer system.Stop(ctx)

    gate := &GateActor{}
    pid, _ := system.Spawn(ctx, "gate", gate, actor.WithStashing())

    // Request arrives before gate is open -> stashed
    _ = actor.Tell(ctx, pid, &Request{Id: "req-1"})
    _ = actor.Tell(ctx, pid, &Request{Id: "req-2"})
    time.Sleep(10 * time.Millisecond)

    // Open gate -> unstashes and processes req-1, req-2
    _ = actor.Tell(ctx, pid, &Open{})
    time.Sleep(10 * time.Millisecond)

    fmt.Println("Served:", gate.served) // [req-1 req-2]
}

Stash size

pid.StashSize() returns the number of messages currently in the stash. Useful for metrics or backpressure.