remote

package
v4.2.3 Latest Latest
Warning

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

Go to latest
Published: May 9, 2026 License: MIT Imports: 26 Imported by: 21

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrCBORNilMessage is returned by [CBORSerializer.Serialize] when the
	// supplied message is nil.
	ErrCBORNilMessage = errors.New("remote: CBOR message is nil")

	// ErrCBORSerializeFailed is returned when CBOR marshaling fails. It wraps
	// the underlying CBOR library error.
	ErrCBORSerializeFailed = errors.New("remote: failed to serialize CBOR message")

	// ErrCBORDeserializeFailed is returned when CBOR unmarshaling fails. It
	// wraps the underlying CBOR library error.
	ErrCBORDeserializeFailed = errors.New("remote: failed to deserialize CBOR message")

	// ErrCBORTypeNotRegistered is returned when the message type is not in the
	// global types registry. Register types via [WithSerializers] or [WithClientSerializers].
	ErrCBORTypeNotRegistered = errors.New("remote: CBOR type not registered")

	// ErrCBORInvalidFrame is returned by [CBORSerializer.Deserialize] when the
	// byte slice is too short or whose length header fields are inconsistent
	// with the actual payload size.
	ErrCBORInvalidFrame = errors.New("remote: malformed or truncated CBOR frame")
)

CBORSerializer errors.

View Source
var (
	// ErrNotProtoMessage is returned by [ProtoSerializer.Serialize] when the
	// supplied value does not implement [proto.Message]. All messages sent
	// through the remoting layer must be concrete protobuf types.
	ErrNotProtoMessage = errors.New("remote: message must implement proto.Message")

	// ErrUnknownMessageType is returned when the type name embedded in a frame
	// cannot be resolved to a registered [proto.Message] type. This typically
	// means the receiving node does not have the proto descriptor compiled in.
	ErrUnknownMessageType = errors.New("remote: unknown or unregistered proto message type")

	// ErrSerializeFailed is returned when protobuf marshaling fails. It wraps
	// the underlying proto library error.
	ErrSerializeFailed = errors.New("remote: failed to serialize proto message")

	// ErrDeserializeFailed is returned when protobuf unmarshaling fails. It
	// wraps the underlying proto library error.
	ErrDeserializeFailed = errors.New("remote: failed to deserialize proto message")

	// ErrInvalidFrame is returned when [ProtoSerializer.Deserialize] receives
	// a byte slice that is too short or whose length header fields are
	// inconsistent with the actual payload size.
	ErrInvalidFrame = errors.New("remote: malformed or truncated proto frame")
)

ProtoSerializer errors.

Functions

This section is empty.

Types

type ActorState

type ActorState uint32

ActorState represents a queryable aspect of an actor's lifecycle or configuration on a remote node. It mirrors the internalpb.State proto enum and is used as a parameter to RemoteState to specify which state to check.

Each value corresponds to a predicate that can be evaluated against a remote actor: the server returns true if the actor satisfies that predicate, false otherwise. Use ActorStateRunning to check if an actor is active and processing messages.

Wire format: Values align with the proto State enum (0-5) for direct mapping when sending RemoteStateRequest over the wire.

Example:

state, err := client.RemoteState(ctx, host, port, "my-actor", remote.ActorStateRunning)
if err != nil {
    return err
}
if state {
    // actor is running
}
const (
	// ActorStateUnknown is the zero value and indicates an unspecified or unrecognized
	// state. When used in RemoteState, the server typically returns false.
	// Prefer one of the concrete states for explicit checks.
	ActorStateUnknown ActorState = 0

	// ActorStateRunning indicates the actor is active and processing messages.
	// The server returns true if pid.IsRunning(), false otherwise.
	// Use this to verify an actor is alive before sending messages or performing
	// operations that require an active actor.
	ActorStateRunning ActorState = 1

	// ActorStateSuspended indicates the actor has been suspended (e.g., via
	// passivation or explicit suspend). The server returns true if pid.IsSuspended().
	// A suspended actor does not process messages until reinstated.
	ActorStateSuspended ActorState = 2

	// ActorStateStopping indicates the actor is in the process of shutting down.
	// The server returns true if pid.IsStopping(). Use this to detect actors
	// that are being terminated and may not accept new work.
	ActorStateStopping ActorState = 3

	// ActorStateRelocatable indicates the actor is configured for relocation.
	// The server returns true if pid.IsRelocatable(). Relocatable actors can
	// be moved to another node on failure or for load balancing.
	ActorStateRelocatable ActorState = 4

	// ActorStateSingleton indicates the actor is a cluster singleton.
	// The server returns true if pid.IsSingleton(). Only one instance of a
	// singleton actor exists across the cluster at any time.
	ActorStateSingleton ActorState = 5
)

func (ActorState) String

func (s ActorState) String() string

String returns a human-readable representation of the state for logging and debugging.

type CBORSerializer

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

CBORSerializer is a Serializer implementation that encodes and decodes arbitrary Go values using the Concise Binary Object Representation (CBOR) in a length-prefixed, self-describing frame format. The frame embeds the message's type name (from the global types registry) so the receiver can reconstruct the correct concrete Go type at runtime.

Frame layout

All integers are big-endian uint32. The layout matches ProtoSerializer for consistency; the type name is the lowercased, trimmed reflect.Type string used by the global registry.

┌──────────┬──────────┬────────────┬──────────────┐ │ totalLen │ nameLen │ type name │ CBOR bytes │ │ 4 bytes │ 4 bytes │ N bytes │ M bytes │ │ uint32BE │ uint32BE │ lowTrim │ raw CBOR │ └──────────┴──────────┴────────────┴──────────────┘

totalLen = 4 + 4 + N + M   (covers the entire frame including itself)

Usage

CBORSerializer is stateless and safe for concurrent use. A single instance can be shared across goroutines without synchronization.

Register concrete types when configuring the remoting layer; the type is automatically added to the global registry:

cfg := remote.NewConfig("0.0.0.0", 9000,
    remote.WithSerializers(new(MyMessage), remote.NewCBORSerializer()),
)

For types that are only received (never sent from this node), register them the same way — the type is auto-registered for deserialization:

remote.WithSerializers(new(MyMessage), remote.NewCBORSerializer())

Constraints

Both [Serialize] and [Deserialize] require that the message type is registered in the global types registry. Registration happens automatically when using WithSerializers or [WithClientSerializers] with a concrete type.

func DefaultCBORSerializer

func DefaultCBORSerializer() *CBORSerializer

DefaultCBORSerializer returns a shared CBORSerializer instance. CBORSerializer is stateless and safe for concurrent use; reusing it avoids per-option allocations when using WithSerializables or [WithClientSerializables].

func NewCBORSerializer

func NewCBORSerializer() *CBORSerializer

NewCBORSerializer returns a ready-to-use CBORSerializer that uses the package-level global type registry. Register types via WithSerializers or [WithClientSerializers]; they are added to the registry automatically.

func (*CBORSerializer) Deserialize

func (s *CBORSerializer) Deserialize(data []byte) (any, error)

Deserialize implements Serializer. It decodes a frame produced by [Serialize], extracts the type name from the frame header, resolves the concrete Go type from the global registry, and unmarshals the CBOR payload into a fresh instance of that type. The returned value is a pointer to the decoded value wrapped as any; callers can recover the original concrete type with a type assertion.

The type-name extraction uses an unsafe []byte→string conversion to avoid a heap allocation; the resulting string is used only for the registry lookup.

Returns ErrCBORInvalidFrame for truncated or malformed frames. Returns an error wrapping the type name when the type is not registered. Returns ErrCBORDeserializeFailed wrapping the CBOR error on unmarshal failure.

func (*CBORSerializer) RegistryRequired

func (*CBORSerializer) RegistryRequired()

RegistryRequired implements types.UsesRegistry so CBORSerializer is recognized by types.RegisterSerializerType.

func (*CBORSerializer) Serialize

func (s *CBORSerializer) Serialize(message any) ([]byte, error)

Serialize implements Serializer. It derives the message's type name from the global registry, encodes the value with CBOR, and produces a self-describing binary frame so [Deserialize] can resolve the concrete type without out-of-band coordination.

The encoding uses a single allocation for the output frame, writes both uint32 header fields from a stack-allocated array, and appends the type name and CBOR bytes in place — no intermediate buffers.

Returns ErrCBORNilMessage if message is nil. Returns an error for unregistered types (register via WithSerializers or [WithClientSerializers]). Returns ErrCBORSerializeFailed wrapping the CBOR error on marshal failure.

type Compression

type Compression int

Compression represents the compression algorithm applied to data sent and received over TCP connections between remote actor systems. Both the client (Remoting) and the server (remote.Config) must agree on the algorithm; a mismatch will produce unreadable frames.

The default for both NewRemoting and NewConfig / DefaultConfig is NoCompression. This matches the convention adopted by gRPC, Akka, Erlang distribution, Kafka, Orleans, and most service meshes: transport-level compression is left off because typical actor payloads are small structured protobuf for which compression yields little bandwidth and meaningful CPU and allocation overhead, especially on intra-cluster LAN links.

Enable ZstdCompression (or GzipCompression / BrotliCompression) explicitly via WithCompression for deployments where bandwidth is the binding constraint — most commonly cross-region / WAN links, or when sending large or highly repetitive payloads.

const (
	// NoCompression disables compression entirely. Data is transmitted as raw
	// protobuf-encoded frames with no additional processing. This is the
	// default for both NewRemoting and NewConfig / DefaultConfig.
	//
	// Pros:
	//   - Zero CPU overhead for compression/decompression.
	//   - Lowest possible per-message latency.
	//   - No per-connection encoder/decoder state — lighter on the allocator
	//     under high fan-out topologies.
	//   - Simplifies debugging because payloads are not transformed on the wire.
	//
	// Cons:
	//   - No bandwidth savings; large or repetitive messages consume more
	//     network I/O than they would with a compressed transport.
	//   - For bandwidth-constrained links (cross-region, WAN, metered
	//     networks) prefer ZstdCompression.
	NoCompression Compression = iota

	// GzipCompression uses the gzip (RFC 1952 / DEFLATE) algorithm.
	//
	// Pros:
	//   - Universally supported; well-understood and battle-tested.
	//   - Good compression ratio for most payloads.
	//
	// Cons:
	//   - Higher CPU cost than Zstd at comparable compression levels.
	//   - Slower compression and decompression speeds, which can become a
	//     bottleneck under high message throughput.
	//   - No built-in dictionary support for small-message optimization.
	GzipCompression

	// ZstdCompression uses the Zstandard (RFC 8878) algorithm. Recommended
	// when bandwidth is the binding constraint (cross-region / WAN links,
	// large or highly repetitive payloads). Must be set explicitly via
	// WithCompression; it is not enabled by default.
	//
	// Pros:
	//   - Excellent compression ratio (typically 50-70% bandwidth reduction
	//     on protobuf payloads) with very low CPU overhead.
	//   - Significantly faster compression and decompression than gzip.
	//   - Supports trained dictionaries for small messages (used internally).
	//   - Scales well under high concurrency and message rates.
	//
	// Cons:
	//   - Slightly larger compressed output than Brotli at maximum settings.
	//   - Requires the zstd C library or a pure-Go port, adding a build
	//     dependency.
	ZstdCompression

	// BrotliCompression uses the Brotli (RFC 7932) algorithm.
	//
	// Pros:
	//   - Best compression ratio among the supported algorithms, especially
	//     for text-heavy or repetitive payloads.
	//   - Built-in static dictionary improves ratio on small messages.
	//
	// Cons:
	//   - Compression is notably slower than Zstd, particularly at higher
	//     quality levels; may add measurable latency per message.
	//   - Decompression speed is comparable to gzip but slower than Zstd.
	//   - Higher memory usage during compression.
	//   - Best suited for scenarios where bandwidth is scarce and latency
	//     requirements are relaxed.
	BrotliCompression
)

type Config

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

Config defines the remote config.

BindAddr must be provided as a physical IP address rather than a DNS name so GoAkt can bind to a deterministic network interface without relying on external name resolution. When BindAddr is set to 0.0.0.0 the runtime will attempt to discover an appropriate private IP address to publish to other nodes, only falling back to a public IP when no private candidate exists. The design favors predictable intra-cluster connectivity in multi-homed or containerized deployments where DNS entries may be unavailable or resolve to unintended interfaces.

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns the default remote config

func NewConfig

func NewConfig(bindAddr string, bindPort int, opts ...Option) *Config

NewConfig returns a Config initialized with the supplied bind address, port, and any functional options. The bind address must be a concrete IP (not a hostname); if it is 0.0.0.0, GoAkt will resolve it to a suitable private IP and fall back to a public address only when necessary. Callers can further tailor transport behaviour through Option values such as frame size and timeout tuning.

func (*Config) BindAddr

func (x *Config) BindAddr() string

BindAddr returns the bind addr

func (*Config) BindPort

func (x *Config) BindPort() int

BindPort returns the bind port

func (*Config) Compression

func (x *Config) Compression() Compression

Compression returns the compression algorithm to use

func (*Config) ContextPropagator

func (x *Config) ContextPropagator() ContextPropagator

ContextPropagator returns the context propagator

func (*Config) DialTimeout

func (x *Config) DialTimeout() time.Duration

DialTimeout returns the dial timeout

func (*Config) IdleTimeout

func (x *Config) IdleTimeout() time.Duration

IdleTimeout specifies how long until idle clients should be closed with a GOAWAY frame. PING frames are not considered activity for the purposes of IdleTimeout. If zero or negative, there is no timeout.

func (*Config) KeepAlive

func (x *Config) KeepAlive() time.Duration

KeepAlive returns the keep alive

func (*Config) MaxFrameSize

func (x *Config) MaxFrameSize() uint32

MaxFrameSize specifies the largest frame this server is willing to read. A valid value is between 16k and 16M, inclusive. If zero or otherwise invalid, an error will be thrown.

func (*Config) MaxIdleConns

func (x *Config) MaxIdleConns() int

MaxIdleConns returns the max idle connections

func (*Config) ReadIdleTimeout

func (x *Config) ReadIdleTimeout() time.Duration

ReadIdleTimeout is the timeout after which a health check using a ping frame will be carried out if no frame is received on the connection. If zero, no health check is performed.

func (*Config) Sanitize

func (x *Config) Sanitize() error

Sanitize the configuration

func (*Config) Serializer

func (x *Config) Serializer(msg any) Serializer

Serializer returns the Serializer registered for the given message, using the same dispatch order as [Remoting.Serializer]:

  1. Exact concrete type — the entry registered with the message's dynamic type.
  2. Interface match — the first registered interface the message implements.

Returns nil when message is nil or no entry matches.

func (*Config) Serializers

func (x *Config) Serializers() map[reflect.Type]Serializer

Serializers returns a copy of the registered serializer map keyed by reflect.Type. The map contains all entries added via WithSerializers, including the default proto.Message entry registered by NewConfig and DefaultConfig.

The returned map is a defensive copy; callers may iterate or read it freely without affecting the Config.

func (*Config) Validate

func (x *Config) Validate() error

func (*Config) WriteTimeout

func (x *Config) WriteTimeout() time.Duration

WriteTimeout is the timeout after which a connection will be closed if no data can be written to it. The timeout begins when data is available to write, and is extended whenever any bytes are written. If zero or negative, there is no timeout.

type ContextPropagator

type ContextPropagator interface {
	// Inject writes context values into the metadata carrier for an outgoing request.
	// Implementations should not mutate ctx and must be safe for concurrent use.
	Inject(ctx context.Context, headers nethttp.Header) error

	// Extract reads metadata from an incoming request and returns a new context
	// containing any propagated values. The returned context should derive from
	// the provided ctx to preserve cancellations and deadlines.
	Extract(ctx context.Context, headers nethttp.Header) (context.Context, error)
}

ContextPropagator defines how Go context values travel across remoting and cluster boundaries by injecting them into outbound metadata and extracting them on the receiving side (trace IDs, auth tokens, correlation IDs, and similar metadata).

The carrier type is net/http.Header, used as a convenient string-keyed, multi-valued map — the underlying transport is TCP, not HTTP.

Implementations should be stateless, safe for concurrent use, favor stable keys, and avoid leaking sensitive data unless explicitly required. Validate inputs to guard against injection or oversized metadata sets. Go-Akt relies on a ContextPropagator so that context-derived metadata survives hops to remote actors or cluster peers and can be read safely during message handling via ReceiveContext.Context() or GrainContext.Context().

Error handling:

  • Inject should fail only when metadata cannot be written.
  • Extract should return a derived context and report parse issues via the error, letting callers choose log-and-continue vs fail-fast policies.

type GrainRequest

type GrainRequest struct {
	// Name is the logical, unique identity of the Grain instance to activate.
	Name string

	// Kind is the fully-qualified type name of the Grain to activate (typically derived via reflection).
	Kind string

	// Dependencies is the set of values to inject into the Grain during initialization.
	// This enables supplying services/clients/configuration needed by the Grain.
	Dependencies []extension.Dependency

	// ActivationTimeout is the maximum time allowed for the activation to complete.
	// If exceeded, activation is treated as failed by the caller/activation layer.
	ActivationTimeout time.Duration

	// ActivationRetries is the number of additional activation attempts to perform after a failure.
	// A value of 0 means "do not retry".
	ActivationRetries int

	// MailboxCapacity is the capacity of the Grain mailbox (<=0 means unbounded).
	MailboxCapacity int64
}

GrainRequest describes the parameters used to activate (spawn) a Grain on a remote node.

A request identifies the Grain to activate (Name + Kind), optionally provides dependency injection values, and controls activation resiliency via timeout and retry settings.

Field expectations:

  • Name and Kind SHOULD be provided. Name is the logical/unique identity of the Grain instance. Kind is the fully-qualified Grain type name (typically derived via reflection).
  • Dependencies is optional and is passed to the Grain at initialization time.
  • ActivationTimeout and ActivationRetries are optional controls; their effective behavior (e.g., defaults, backoff strategy, what counts as a retryable failure) is defined by the activation/transport layer that consumes this request.

Note: The given Grain kind must be registered on the remote node actor system where activation is requested using the RegisterGrainKind function.

func (*GrainRequest) Sanitize

func (s *GrainRequest) Sanitize()

Sanitize sanitizes the request

func (*GrainRequest) Validate

func (s *GrainRequest) Validate() error

Validate checks that the GrainRequest has valid fields.

type Option

type Option interface {
	// Apply sets the Option value of a config.
	Apply(*Config)
}

Option is the interface that applies a configuration option.

func WithCompression

func WithCompression(c Compression) Option

WithCompression sets the compression algorithm to use when sending or receiving data.

func WithContextPropagator

func WithContextPropagator(propagator ContextPropagator) Option

WithContextPropagator sets the ContextPropagator used to inject and extract cross-cutting metadata (e.g., custom headers, correlation IDs, auth tokens) for remote calls.

Passing a non-nil propagator enables propagation across process boundaries, ensuring values from a context are serialized into headers on outgoing calls and restored into the context on incoming calls. If propagator is nil, this option is ignored and the default/no-op propagator remains in effect.

Typical use:

  • Integrate distributed tracing (e.g., OpenTelemetry) by providing a propagator implementation that injects/extracts trace context.
  • Forward request-scoped metadata like user/session IDs or feature flags.

Note: Only non-nil propagators are applied. Multiple calls will overwrite the previous propagator with the last non-nil value.

func WithMaxFrameSize

func WithMaxFrameSize(size uint32) Option

WithMaxFrameSize specifies the largest frame this server is willing to read. A valid value is between 16k and 16M, inclusive. If zero or otherwise invalid, an error will be thrown.

func WithReadIdleTimeout

func WithReadIdleTimeout(timeout time.Duration) Option

WithReadIdleTimeout sets the read timeout ReadIdleTimeout is the timeout after which a health check using a ping frame will be carried out if no frame is received on the connection. If zero, no health check is performed.

func WithSerializables

func WithSerializables(msgs ...any) Option

WithSerializables registers the CBOR serializer for each of the given concrete or interface types. It is a convenience for registering multiple types with CBORSerializer without repeating the serializer instance.

Concrete type registration

Pass any value of the target type to bind the CBOR serializer to that exact type:

WithSerializables(new(MyMessage), new(OtherMessage))

Each concrete type is automatically registered in the global type registry used for CBOR serialization. No separate registration step is required.

Interface registration

Pass a typed nil pointer to an interface to bind the CBOR serializer to every message that implements that interface:

WithSerializables((*MyInterface)(nil))

Dispatch order

When Config.Serializer resolves a serializer for a message it checks, in order:

  1. Exact concrete type — the entry registered with the message's dynamic type.
  2. Interface match — the first registered interface the message implements.

Nil entries in the types slice are silently ignored.

func WithSerializers

func WithSerializers(msg any, serializer Serializer) Option

WithSerializers registers a Serializer for a specific message type or for all messages that satisfy a given interface.

Concrete type registration

Pass any value of the target type to bind a serializer to that exact type:

WithSerializers(new(MyMessage), mySerializer)

When the serializer is CBORSerializer and the type is not a proto.Message, the type is automatically registered in the global type registry used for CBOR serialization. No separate registration step is required.

Interface registration

Pass a typed nil pointer to an interface to bind a serializer to every message that implements that interface:

WithSerializers((*proto.Message)(nil), remote.NewProtoSerializer())

Dispatch order

When Config.Serializer resolves a serializer for a message it checks, in order:

  1. Exact concrete type — the entry registered with the message's dynamic type.
  2. Interface match — the first registered interface the message implements.

Registration order within each category determines priority. If serializer is nil the option is silently ignored.

The default configuration registers ProtoSerializer for all proto.Message implementations. Calling this option with a typed nil pointer to proto.Message overrides that default for proto messages.

func WithWriteTimeout

func WithWriteTimeout(timeout time.Duration) Option

WithWriteTimeout sets the write timeout

type OptionFunc

type OptionFunc func(config *Config)

OptionFunc implements the Option interface.

func (OptionFunc) Apply

func (f OptionFunc) Apply(c *Config)

type Peer

type Peer struct {
	// Host is the DNS name or IP address of the node that other peers can reach.
	Host string

	// DiscoveryPort is the network port on which the node participates in
	// service discovery (for advertising itself and discovering other nodes).
	// Valid range is 1–65535.
	DiscoveryPort int

	// PeersPort is the network port used for peer-to-peer coordination traffic
	// between nodes (such as membership, gossip, or health checks).
	// Valid range is 1–65535.
	PeersPort int

	// RemotingPort is the network port used for application remoting, such as
	// RPC or actor message delivery between nodes.
	// Valid range is 1–65535.
	RemotingPort int

	// Roles lists the logical roles or capabilities this node advertises
	// (for example: "frontend", "backend", "shard"). Roles can be used for
	// routing or placement decisions. An empty list means no special roles.
	Roles []string

	// CreatedAt is a Unix timestamp (in nanoseconds) indicating when this
	// peer was first discovered or registered.
	CreatedAt int64
}

Peer describes a remote node discovered in the cluster and the network endpoints it exposes for discovery, inter-peer coordination, and remoting.

func (Peer) PeersAddress

func (peer Peer) PeersAddress() string

PeersAddress returns address the node's peers will use to connect To

func (Peer) RemotingAddress

func (peer Peer) RemotingAddress() string

RemotingAddress returns address the node's remoting clients will use to connect To

type ProtoSerializer

type ProtoSerializer struct{}

ProtoSerializer is the built-in Serializer implementation provided by GoAkt. It encodes and decodes proto.Message values using a length-prefixed, self-describing binary frame format that embeds the message's fully-qualified type name so the receiver can reconstruct the correct concrete Go type at runtime via protoregistry.GlobalTypes.

Frame layout

All integers are big-endian uint32:

┌──────────┬──────────┬────────────┬──────────────┐
│ totalLen │ nameLen  │ type name  │ proto bytes  │
│ 4 bytes  │ 4 bytes  │ N bytes    │ M bytes      │
└──────────┴──────────┴────────────┴──────────────┘

totalLen = 4 + 4 + N + M   (covers the entire frame including itself)

Usage

ProtoSerializer is stateless and safe for concurrent use. A single instance can be shared across goroutines without synchronization.

Pass it to the remoting layer when configuring the actor system:

remoteCfg := remote.NewConfig("0.0.0.0", 9000,
    remote.WithSerializer(remote.NewProtoSerializer()),
)

Constraints

Both [Serialize] and [Deserialize] require that the message type is registered in the global protobuf registry (protoregistry.GlobalTypes). Types generated by protoc-gen-go are registered automatically via their init functions; custom or dynamically constructed types must be registered explicitly.

func NewProtoSerializer

func NewProtoSerializer() *ProtoSerializer

NewProtoSerializer returns a ready-to-use ProtoSerializer.

func (*ProtoSerializer) Deserialize

func (x *ProtoSerializer) Deserialize(data []byte) (any, error)

Deserialize implements Serializer. It decodes a frame produced by [Serialize], extracts the fully-qualified type name, resolves the concrete proto.Message type from protoregistry.GlobalTypes, and unmarshals the proto payload into a fresh instance of that type. The returned value is the decoded proto.Message wrapped as any; callers can recover the original concrete type with a type assertion.

The type-name extraction uses an unsafe []byte→string conversion to avoid a heap allocation; the resulting string is used only for the registry lookup.

Returns ErrInvalidFrame for truncated or malformed frames. Returns ErrUnknownMessageType if the type name is not in the global registry. Returns ErrDeserializeFailed wrapping the proto error on unmarshal failure.

func (*ProtoSerializer) Serialize

func (x *ProtoSerializer) Serialize(message any) ([]byte, error)

Serialize implements Serializer. It type-asserts message to proto.Message, then encodes it into a self-describing binary frame. The frame embeds the message's fully-qualified type name so [Deserialize] can resolve the concrete Go type without any out-of-band coordination.

The encoding uses a single allocation by pre-computing the exact frame size via proto.Size, writing both uint32 header fields from a stack-allocated array, and appending the proto wire bytes in-place — no intermediate buffers.

Returns ErrNotProtoMessage if message does not implement proto.Message. Returns ErrUnknownMessageType if the message has no registered type name. Returns ErrSerializeFailed wrapping the proto error on marshal failure.

type Serializer

type Serializer interface {
	// Serialize encodes message into a byte slice suitable for transmission
	// over the network. The encoding must be self-describing so that
	// [Serializer.Deserialize] can reconstruct the original concrete type on
	// the remote node without additional context.
	//
	// message is guaranteed to be non-nil by the remoting layer, but
	// implementations should guard against unexpected nil values and return
	// an error rather than panic.
	//
	// Returns the encoded bytes and a nil error on success.
	// Returns a nil slice and a descriptive non-nil error on failure.
	Serialize(message any) ([]byte, error)

	// Deserialize decodes data produced by [Serializer.Serialize] and returns
	// the original Go value with its concrete type restored. The caller will
	// use a type assertion on the returned value, so the dynamic type must
	// match what was passed to Serialize.
	//
	// data is guaranteed to be non-empty by the remoting layer, but
	// implementations should validate the payload and return an error for
	// truncated, corrupted, or unrecognized input rather than panicking.
	//
	// Returns the decoded value and a nil error on success.
	// Returns nil and a descriptive non-nil error on failure.
	Deserialize(data []byte) (any, error)
}

Serializer is the extension point for plugging a custom wire format into GoAkt's remoting layer. Implement this interface when you need to send messages between actors running on different nodes using an encoding other than the built-in protobuf framing.

Responsibilities

A Serializer implementation is responsible for two complementary operations:

  • [Serializer.Serialize]: encode an arbitrary Go value into a self-describing byte slice that can be safely transmitted over the network.
  • [Serializer.Deserialize]: decode that byte slice back into a Go value with the exact concrete type it had before serialization. The caller uses a type assertion on the returned value, so the round-trip must preserve the original dynamic type.

Self-description requirement

Because GoAkt's remoting layer must reconstruct the correct Go type on the receiving end without out-of-band coordination, the encoded bytes must be self-describing. That is, the serialized payload must embed enough information (e.g. a fully-qualified type name or a registered numeric ID) for [Serializer.Deserialize] to determine which concrete type to instantiate.

Concurrency

A single Serializer instance may be called from multiple goroutines concurrently. Implementations must be safe for concurrent use without external synchronization.

Error handling

Both methods must return a non-nil error when encoding or decoding fails. Errors should be descriptive enough for operators to diagnose malformed payloads or unrecognized type identifiers. Returning a nil error alongside a nil or zero value is incorrect and may cause silent data loss.

Example implementation

type JSONSerializer struct{}

type envelope struct {
    Type    string          `json:"type"`
    Payload json.RawMessage `json:"payload"`
}

func (s *JSONSerializer) Serialize(message any) ([]byte, error) {
    payload, err := json.Marshal(message)
    if err != nil {
        return nil, err
    }
    env := envelope{
        Type:    fmt.Sprintf("%T", message),
        Payload: payload,
    }
    return json.Marshal(env)
}

func (s *JSONSerializer) Deserialize(data []byte) (any, error) {
    var env envelope
    if err := json.Unmarshal(data, &env); err != nil {
        return nil, err
    }
    // resolve the concrete type by name and unmarshal into it …
}

type SingletonSpec

type SingletonSpec struct {
	// SpawnTimeout is the maximum time to wait for the singleton actor to become
	// available after a spawn request is issued.
	//
	// This should account for network latency, leader/placement decisions, and the
	// time required to start the actor on the chosen node.
	//
	// A zero value usually means "use the system default" (if supported by the caller).
	SpawnTimeout time.Duration

	// WaitInterval is the delay between successive checks (polls) for the singleton’s
	// spawn/availability status.
	//
	// Smaller values detect readiness sooner but increase control-plane traffic;
	// larger values reduce traffic but may add latency before the caller observes that
	// the singleton is ready.
	//
	// A zero value usually means "use the system default" (if supported by the caller).
	WaitInterval time.Duration

	// MaxRetries is the maximum number of status-check attempts performed while waiting
	// for the singleton to become available.
	//
	// Implementations typically stop waiting when either:
	//   - the singleton becomes available, or
	//   - MaxRetries is reached, or
	//   - SpawnTimeout elapses (whichever happens first).
	//
	// A value <= 0 usually means "use the system default" or "do not retry" depending on
	// the caller semantics.
	MaxRetries int32
}

SingletonSpec defines configuration options for *cluster singletons*.

A cluster singleton is an actor for which only one active instance is allowed across the whole cluster at any moment. SingletonSpec controls how long the caller should wait and how aggressively it should poll when the singleton is being created (or relocated) on some node.

This spec does not change *where* the singleton is placed; it only governs the client-side waiting/retry behavior while ensuring the singleton becomes available.

All durations are expressed as integers to match the wire format used by remote spawn requests (typically representing a time.Duration in nanoseconds or milliseconds depending on the surrounding API). Use the constructor/helpers provided by the package (if any) to avoid unit mistakes.

type SpawnChildRequest

type SpawnChildRequest struct {
	// Name represents the unique name of the actor.
	// This name is used to identify and reference the actor across different nodes.
	Name string

	// Kind represents the type of the actor.
	// It typically corresponds to the actor’s implementation within the system
	Kind string

	// Relocatable indicates whether the actor can be automatically relocated to another node
	// if its current host node unexpectedly shuts down.
	// By default, actors are relocatable to ensure system resilience and high availability.
	// Setting this to false ensures that the actor will not be redeployed after a node failure,
	// which may be necessary for actors with node-specific dependencies or state.
	Relocatable bool

	// PassivationStrategy sets the passivation strategy after which an actor
	// will be passivated. Passivation allows the actor system to free up
	// resources by stopping actors that have been inactive for the specified
	// duration. If the actor receives a message before this timeout,
	// the passivation timer is reset.
	PassivationStrategy passivation.Strategy

	// Supervisor defines the supervision strategy for the actor being spawned.
	//
	// When set, this supervisor configuration is attached to the actor’s parent/manager (depending on the
	// spawn path) and governs how failures are handled at runtime. It typically controls:
	//   - Restart behavior (e.g., whether the actor is restarted on panic/error)
	//   - Backoff/retry characteristics (if supported by the chosen supervisor)
	//   - Escalation semantics (how failures propagate within a supervision tree)
	//
	// If nil, the system default supervision configuration is used.
	//
	// Notes:
	//   - Supervision affects *failure handling*, not *placement*. Use Role/Singleton/Relocatable to
	//     influence where and how the actor is (re)deployed across the cluster.
	//   - In a relocatable/singleton scenario, the supervisor still applies after the actor is started
	//     on the target node.
	Supervisor *supervisor.Supervisor

	// Dependencies define the list of dependencies that injects the given dependencies into
	// the actor during its initialization.
	//
	// This allows you to configure an actor with one or more dependencies,
	// such as services, clients, or configuration objects it needs to function.
	// These dependencies will be made available to the actor when it is spawned,
	// enabling better modularity and testability.
	Dependencies []extension.Dependency

	// EnableStashing enables stashing and sets the stash buffer for the actor, allowing it to temporarily store
	// incoming messages that cannot be immediately processed. This is particularly useful
	// in scenarios where the actor must delay handling certain messages—for example,
	// during initialization, while awaiting external resources, or transitioning between states.
	//
	// By stashing messages, the actor can defer processing until it enters a stable or ready state,
	// at which point the buffered messages can be retrieved and handled in a controlled sequence.
	// This helps maintain a clean and predictable message flow without dropping or prematurely
	// processing input.
	//
	// Use WithStashing when spawning the actor to activate this capability. By default, the stash
	// buffer is disabled.
	//
	// ⚠️ Note: The stash buffer is *not* a substitute for robust message handling or proper
	// supervision strategies. Misuse may lead to unbounded memory growth if messages are
	// stashed but never unstashed. Always ensure the actor eventually processes or discards
	// stashed messages to avoid leaks or state inconsistencies.
	//
	// When used correctly, the stash buffer is a powerful tool for managing transient states
	// and preserving actor responsiveness while maintaining orderly message handling.
	EnableStashing bool

	// Reentrancy defines async request behavior for the spawned actor.
	// When nil, async requests are disabled (default behavior).
	Reentrancy *reentrancy.Reentrancy

	// Parent specifies the parent actor name.
	Parent string
}

func (*SpawnChildRequest) Sanitize

func (s *SpawnChildRequest) Sanitize()

Sanitize sanitizes the request

func (*SpawnChildRequest) Validate

func (s *SpawnChildRequest) Validate() error

type SpawnRequest

type SpawnRequest struct {
	// Name represents the unique name of the actor.
	// This name is used to identify and reference the actor across different nodes.
	Name string

	// Kind represents the type of the actor.
	// It typically corresponds to the actor’s implementation within the system
	Kind string

	// Singleton specifies whether the actor is a singleton, meaning only one instance of the actor
	// can exist across the entire cluster at any given time.
	// This option is useful for actors responsible for global coordination or shared state.
	// When Singleton is set to true it means that the given actor is automatically relocatable
	Singleton *SingletonSpec

	// Relocatable indicates whether the actor can be automatically relocated to another node
	// if its current host node unexpectedly shuts down.
	// By default, actors are relocatable to ensure system resilience and high availability.
	// Setting this to false ensures that the actor will not be redeployed after a node failure,
	// which may be necessary for actors with node-specific dependencies or state.
	Relocatable bool

	// PassivationStrategy sets the passivation strategy after which an actor
	// will be passivated. Passivation allows the actor system to free up
	// resources by stopping actors that have been inactive for the specified
	// duration. If the actor receives a message before this timeout,
	// the passivation timer is reset.
	PassivationStrategy passivation.Strategy

	// Supervisor defines the supervision strategy for the actor being spawned.
	//
	// When set, this supervisor configuration is attached to the actor’s parent/manager (depending on the
	// spawn path) and governs how failures are handled at runtime. It typically controls:
	//   - Restart behavior (e.g., whether the actor is restarted on panic/error)
	//   - Backoff/retry characteristics (if supported by the chosen supervisor)
	//   - Escalation semantics (how failures propagate within a supervision tree)
	//
	// If nil, the system default supervision configuration is used.
	//
	// Notes:
	//   - Supervision affects *failure handling*, not *placement*. Use Role/Singleton/Relocatable to
	//     influence where and how the actor is (re)deployed across the cluster.
	//   - In a relocatable/singleton scenario, the supervisor still applies after the actor is started
	//     on the target node.
	Supervisor *supervisor.Supervisor

	// Dependencies define the list of dependencies that injects the given dependencies into
	// the actor during its initialization.
	//
	// This allows you to configure an actor with one or more dependencies,
	// such as services, clients, or configuration objects it needs to function.
	// These dependencies will be made available to the actor when it is spawned,
	// enabling better modularity and testability.
	Dependencies []extension.Dependency

	// EnableStashing enables stashing and sets the stash buffer for the actor, allowing it to temporarily store
	// incoming messages that cannot be immediately processed. This is particularly useful
	// in scenarios where the actor must delay handling certain messages—for example,
	// during initialization, while awaiting external resources, or transitioning between states.
	//
	// By stashing messages, the actor can defer processing until it enters a stable or ready state,
	// at which point the buffered messages can be retrieved and handled in a controlled sequence.
	// This helps maintain a clean and predictable message flow without dropping or prematurely
	// processing input.
	//
	// Use WithStashing when spawning the actor to activate this capability. By default, the stash
	// buffer is disabled.
	//
	// ⚠️ Note: The stash buffer is *not* a substitute for robust message handling or proper
	// supervision strategies. Misuse may lead to unbounded memory growth if messages are
	// stashed but never unstashed. Always ensure the actor eventually processes or discards
	// stashed messages to avoid leaks or state inconsistencies.
	//
	// When used correctly, the stash buffer is a powerful tool for managing transient states
	// and preserving actor responsiveness while maintaining orderly message handling.
	EnableStashing bool

	// Role narrows placement to cluster members that advertise the given role.
	//
	// In a clustered deployment, GoAkt uses placement roles to constrain where actors may be
	// started or relocated. When `Role` is non-nil the actor will only be considered for nodes
	// that list the same role; clearing the field makes the actor eligible on any node.
	//
	// ⚠️ Note: This setting has effect only for `SpawnOn` and `SpawnSingleton` requests. Local-only
	// spawns ignore it.
	Role *string

	// Reentrancy defines async request behavior for the spawned actor.
	// When nil, async requests are disabled (default behavior).
	Reentrancy *reentrancy.Reentrancy
}

SpawnRequest defines configuration options for spawning an actor on a remote node. These options control the actor’s identity, behavior, and lifecycle, especially in scenarios involving node failures or load balancing.

func (*SpawnRequest) Sanitize

func (s *SpawnRequest) Sanitize()

Sanitize sanitizes the request

func (*SpawnRequest) Validate

func (s *SpawnRequest) Validate() error

Validate validates the SpawnRequest

Jump to

Keyboard shortcuts

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