actor

helpers for applying Actor model CSP for writing Go code without deadlocks, live locks or race conditions

git clone https://git.smesh.lol/actor.git

actor

Generic building blocks for CSP/actor-model concurrency in Go.

Each type maps to a common inter-goroutine communication pattern, eliminating the per-actor boilerplate of defining request structs, response channels, and caller methods.

Types

TypeCallerActorPattern
Func[Req, Resp]Call(req) -> respmsg.Req, msg.Reply(resp)function call
Query[Resp]Call() -> respmsg.Reply(resp)getter
Proc[Req]Call(req)msg.Req, msg.Done()setter / command
SignalCall()msg.Done()trigger / reset
Inbox[T]Send(v) / TrySend(v)<-i.Recv()fire-and-forget
LifecycleStop()<-Stopping(), defer MarkDone()shutdown

All synchronous types use unbuffered channels. The caller blocks until the actor processes the message. This eliminates races where a buffered write appears to succeed but the actor hasn't seen it yet.

Example

type Counter struct {
    inc   actor.Signal
    get   actor.Query[int]
    actor.Lifecycle
}

func NewCounter() *Counter {
    c := &Counter{
        inc:       actor.NewSignal(),
        get:       actor.NewQuery[int](),
        Lifecycle: actor.NewLifecycle(),
    }
    actor.Go(c.Lifecycle, c.run)
    return c
}

func (c *Counter) run() {
    var n int
    for {
        select {
        case <-c.Stopping():
            return
        case msg := <-c.inc.Recv():
            n++
            msg.Done()
        case msg := <-c.get.Recv():
            msg.Reply(n)
        }
    }
}

func (c *Counter) Inc()     { c.inc.Call() }
func (c *Counter) Get() int { return c.get.Call() }

Design Principles

  • No sync primitives. No mutexes, no atomics, no sync.Once. All

coordination is via channels and select.

  • Unbuffered by default. Synchronous types guarantee

happens-before: when Call returns, the actor has processed the message. Use Inbox for intentionally async paths.

  • State ownership. The actor goroutine owns all mutable state.

Callers get copies via response channels, never references.

  • Composable. Embed the channel types you need. No framework, no

interface to satisfy, no registration.

When to use what

  • Func - caller needs a computed result based on arguments (map

lookup, filtered query)

  • Query - caller needs current state, no arguments (get count, get

config)

  • Proc - caller needs to mutate state and wait for confirmation

(set value, record event)

  • Signal - caller needs to trigger an action and wait (reset,

flush, increment)

  • Inbox - caller must not block (logging, metrics, notifications).

Use TrySend to drop on backpressure.

Architecture Guide

This library implements the intra-domain actor patterns described in Moxie Architecture Patterns - specifically the actor loop (section 3.1), typed operation channels (section 3.2), the synchronous-mutation rule (section 3.3), and context-aware calls (section 3.6). The five channel types are a direct realization of the taxonomy from section 3.2.

Family

Same architecture, three languages:

  • actor (Go) - go get git.smesh.lol/actor
  • [pickle](https://git.smesh.lol/pickle) (Rust) - pickle = { git = "https://git.smesh.lol/pickle" }
  • [anneal](https://git.smesh.lol/anneal) (TypeScript) - npm install anneal

Install

go get git.smesh.lol/actor

License

AGPL-3.0-or-later

files