sup

package module
v0.0.14 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 23, 2026 License: MIT Imports: 10 Imported by: 0

README

Sup

Go Reference Test License

Sup is a high-performance, low-allocation Actor Model library for Go.

It provides a robust foundation for building highly concurrent, distributed, and fault-tolerant stateful applications. It achieves zero-allocation for asynchronous messages (Cast) and minimizes overhead for synchronous requests (Call) by utilizing internal resource pooling. It embraces standard Go idioms (select, channels, and context) rather than hiding them behind heavy frameworks.

Features

  • Idiomatic Go: Actors are just standard Goroutines running a select loop. No magic interfaces, no reflection, no global registries.
  • OTP Supervision: Built-in Erlang-style Supervisor trees. If an actor panics, the supervisor catches it and restarts it based on your defined policy (Permanent, Temporary, Transient).
  • No Goroutine Leaks: context.Context integration ensures all actors gracefully shut down when their parent context is canceled.

Quick start

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/webermarci/sup"
)

// 1. Define internal messages (unexported so they are hidden from the API)
type incrementMsg struct{ amount int }
type getCountMsg struct{}

// 2. Define your Actor
type Counter struct {
	// Embed the Mailbox.
	*sup.Mailbox
	count int
}

func NewCounter() *Counter {
	return &Counter{
		Mailbox: sup.NewMailbox(10),
	}
}

// 3. Clean API Methods (Encapsulation)
// The caller never needs to know about Cast, Call, or Mailboxes!

func (c *Counter) Increment(amount int) {
	// Fire and forget
	_ = sup.Cast(c.Mailbox, incrementMsg{amount: amount})
}

func (c *Counter) Get() (int, error) {
	// Synchronous request-reply
	return sup.Call[getCountMsg, int](c.Mailbox, getCountMsg{})
}

// 4. The Actor's Run loop is just a standard Go select statement
func (c *Counter) Run(ctx context.Context) error {
	for {
		select {
		case <-ctx.Done(): // Graceful shutdown
			return ctx.Err()

		case msg := <-c.Receive():
			switch m := msg.(type) {
			case sup.CastRequest[incrementMsg]:
				c.count += m.Payload().amount
			case sup.CallRequest[getCountMsg, int]:
				m.Reply(c.count, nil)
			}
		}
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	counter := NewCounter()

	supervisor := sup.NewSupervisor(
		sup.WithActor(counter),
		sup.WithPolicy(sup.Permanent),
		sup.WithRestartDelay(time.Second),
		sup.WithRestartLimit(5, 10 * time.Second),
	)
	
	supervisor.Run(ctx)

	counter.Increment(10)
	counter.Increment(32)

	count, err := counter.Get()
	if err != nil {
		panic(err)
	}

	fmt.Printf("Final count: %d\n", count)
	
	cancel()
	supervisor.Wait()
}

Benchmark

goos: darwin
goarch: arm64
pkg: github.com/webermarci/sup
cpu: Apple M5
Benchmark_Cast-10                       20938254    57.2 ns/op     0 B/op   0 allocs/op
Benchmark_Cast_Concurrent-10            10266757   122.5 ns/op     0 B/op   0 allocs/op
Benchmark_CastContext-10                21600847    55.9 ns/op     0 B/op   0 allocs/op
Benchmark_CastContext_Concurrent-10     15451960    74.3 ns/op     0 B/op   0 allocs/op
Benchmark_CastContext_Expired-10        24459242    48.9 ns/op     0 B/op   0 allocs/op
Benchmark_TryCast-10                   192998186     6.2 ns/op     0 B/op   0 allocs/op
Benchmark_TryCast_Concurrent-10         85340160    14.9 ns/op     0 B/op   0 allocs/op
Benchmark_TryCast_Full-10              244051416     4.9 ns/op     0 B/op   0 allocs/op
Benchmark_Call-10                        3548784   341.7 ns/op    16 B/op   1 allocs/op
Benchmark_Call_Concurrent-10             2394580   501.9 ns/op    16 B/op   1 allocs/op
Benchmark_CallContext-10                 3018014   397.2 ns/op    16 B/op   1 allocs/op
Benchmark_CallContext_Concurrent-10      1422726   842.3 ns/op    16 B/op   1 allocs/op
Benchmark_CallContext_Expired-10        19289618    62.2 ns/op    16 B/op   1 allocs/op
Benchmark_TryCall-10                     3556698   341.0 ns/op    16 B/op   1 allocs/op
Benchmark_TryCall_Concurrent-10          3028040   453.5 ns/op    16 B/op   1 allocs/op
Benchmark_Supervisor_SpawnAndExit-10     4608442   261.4 ns/op    72 B/op   2 allocs/op

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrMailboxFull is returned by TryCast when the mailbox buffer is full.
	ErrMailboxFull = errors.New("mailbox is full")
	// ErrMailboxClosed is returned when trying to send to a closed mailbox.
	ErrMailboxClosed = errors.New("mailbox is closed")
)
View Source
var (
	// ErrMaxRestartsExceeded is returned when the maximum number of restarts is exceeded within the restart window.
	ErrMaxRestartsExceeded = errors.New("max restarts exceeded")
)

Functions

func Call

func Call[T any, R any](mb *Mailbox, payload T) (R, error)

Call sends a message to an actor and waits indefinitely for a reply.

func CallContext added in v0.0.2

func CallContext[T any, R any](ctx context.Context, mb *Mailbox, payload T) (R, error)

CallContext sends a message to an actor and waits for a reply until the context expires.

func Cast added in v0.0.8

func Cast[T any](mb *Mailbox, payload T) error

Cast sends an asynchronous typed envelope, waiting until it can be enqueued or the mailbox is closed. It returns ErrMailboxClosed if the mailbox is closed.

func CastContext added in v0.0.8

func CastContext[T any](ctx context.Context, mb *Mailbox, payload T) error

CastContext sends an asynchronous typed envelope with context for enqueue cancellation. It returns ErrMailboxClosed if the mailbox is closed, or ctx.Err() if the context expires before the message is enqueued.

func TryCall added in v0.0.2

func TryCall[T any, R any](mb *Mailbox, payload T) (R, error)

TryCall attempts to enqueue a request without blocking.

func TryCallContext added in v0.0.6

func TryCallContext[T any, R any](ctx context.Context, mb *Mailbox, payload T) (R, error)

TryCallContext attempts to enqueue a request without blocking and waits for reply until ctx expires.

func TryCast added in v0.0.8

func TryCast[T any](mb *Mailbox, payload T) error

TryCast attempts to send an envelope without blocking. It returns ErrMailboxClosed if the mailbox is closed, or ErrMailboxFull immediately if the mailbox buffer is full.

func TryCastContext added in v0.0.8

func TryCastContext[T any](ctx context.Context, mb *Mailbox, payload T) error

TryCastContext attempts to send an envelope without blocking, but returns ctx.Err() if ctx is done. It returns ErrMailboxClosed if the mailbox is closed, or ErrMailboxFull immediately if the mailbox buffer is full.

Types

type Actor added in v0.0.13

type Actor interface {
	Run(context.Context) error
}

type ActorFunc added in v0.0.13

type ActorFunc func(context.Context) error

func (ActorFunc) Run added in v0.0.13

func (f ActorFunc) Run(ctx context.Context) error

type CallRequest added in v0.0.8

type CallRequest[T any, R any] struct {
	// contains filtered or unexported fields
}

CallRequest wraps a payload with a reply channel for synchronous calls.

func (CallRequest[T, R]) Payload added in v0.0.8

func (r CallRequest[T, R]) Payload() T

Payload returns the request's payload.

func (CallRequest[T, R]) Reply added in v0.0.8

func (r CallRequest[T, R]) Reply(value R, err error)

Reply sends the response back to the caller. The actor should call this exactly once per request.

type CastRequest added in v0.0.8

type CastRequest[T any] struct {
	// contains filtered or unexported fields
}

CastRequest wraps a payload for asynchronous calls without expecting a reply.

func (CastRequest[T]) Payload added in v0.0.8

func (r CastRequest[T]) Payload() T

Payload returns the request's payload.

type Mailbox

type Mailbox struct {
	// contains filtered or unexported fields
}

Mailbox is a thread-safe message queue for actors.

func NewMailbox

func NewMailbox(size int) *Mailbox

NewMailbox creates a new mailbox with the specified buffer size. A size of 0 means unbuffered.

func (*Mailbox) Cap added in v0.0.3

func (m *Mailbox) Cap() int

Cap returns the total capacity of the mailbox buffer.

func (*Mailbox) Close

func (m *Mailbox) Close()

Close safely closes the mailbox. Subsequent sends fail.

func (*Mailbox) IsClosed added in v0.0.4

func (m *Mailbox) IsClosed() bool

IsClosed checks if the mailbox has been closed.

func (*Mailbox) Len added in v0.0.3

func (m *Mailbox) Len() int

Len returns the current number of messages in the mailbox buffer.

func (*Mailbox) Receive

func (m *Mailbox) Receive() <-chan any

Receive returns the read-only channel to consume messages.

type RepliableRequest added in v0.0.9

type RepliableRequest[R any] interface {
	Reply(value R, err error)
}

RepliableRequest represents a request that can be replied to.

type RestartPolicy

type RestartPolicy uint8
const (
	Permanent RestartPolicy = iota // Always restart, even on clean exits
	Transient                      // Restart on errors/panics, but not on clean exits (nil)
	Temporary                      // Never restart
)

type Supervisor

type Supervisor struct {
	// contains filtered or unexported fields
}

Supervisor manages the lifecycle of actor Run loops.

func NewSupervisor added in v0.0.7

func NewSupervisor(opts ...SupervisorOption) *Supervisor

NewSupervisor creates a new Supervisor with the given options. Panics if the provided options are invalid.

func (*Supervisor) Run added in v0.0.14

func (s *Supervisor) Run(ctx context.Context) error

Run starts all actors under supervision and blocks until the context is canceled or all actors have stopped.

func (*Supervisor) Running added in v0.0.4

func (s *Supervisor) Running() int

Running returns the number of currently running actors under supervision.

func (*Supervisor) Spawn added in v0.0.14

func (s *Supervisor) Spawn(ctx context.Context, actor Actor)

Spawn starts the given actor under supervision. It will be restarted according to the supervisor's policy if it returns an error or panics.

func (*Supervisor) Wait

func (s *Supervisor) Wait()

Wait blocks until all actors managed by this supervisor have stopped.

type SupervisorOption added in v0.0.7

type SupervisorOption func(*Supervisor)

Option configures a Supervisor.

func WithActor added in v0.0.14

func WithActor(a Actor) SupervisorOption

WithActor adds an actor to be supervised. Can be called multiple times to add multiple actors.

func WithActors added in v0.0.14

func WithActors(actors ...Actor) SupervisorOption

WithActors adds multiple actors to be supervised.

func WithOnError added in v0.0.7

func WithOnError(fn func(error)) SupervisorOption

WithOnError sets a callback invoked when an actor returns an error or panics.

func WithOnRestart added in v0.0.7

func WithOnRestart(fn func()) SupervisorOption

WithOnRestart sets a callback invoked just before an actor is restarted.

func WithPolicy added in v0.0.7

func WithPolicy(p RestartPolicy) SupervisorOption

WithPolicy sets the restart policy.

func WithRestartDelay added in v0.0.7

func WithRestartDelay(d time.Duration) SupervisorOption

WithRestartDelay sets the delay between restarts.

func WithRestartLimit added in v0.0.7

func WithRestartLimit(maxRestarts int, window time.Duration) SupervisorOption

WithRestartLimit sets the maximum number of restarts allowed within a window. Both maxRestarts and window must be positive; otherwise NewSupervisor panics.

Directories

Path Synopsis
mesh module
modbus module
ws module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL