genserver

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Apr 5, 2026 License: Apache-2.0 Imports: 1 Imported by: 0

Documentation

Overview

Package genserver provides a generic server process inspired by Elixir's OTP GenServer. State is owned by a single goroutine; Call and Cast messages are serialized through a single channel to preserve ordering.

Call is synchronous: the caller blocks until HandleCall returns a response. Cast is asynchronous: the caller returns immediately after sending.

Without tail-call optimization in Go, the server loop is an explicit channel select rather than a recursive function, but the semantics are equivalent.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type GenServer

type GenServer[S, Req, Resp any] struct {
	// contains filtered or unexported fields
}

GenServer is a running server process. All operations are goroutine-safe. Methods must not be called after Stop.

func Start

func Start[S, Req, Resp any](srv Server[S, Req, Resp]) *GenServer[S, Req, Resp]

Start initialises the server by calling srv.Init() and launches the message loop in a new goroutine.

func (*GenServer[S, Req, Resp]) Call

func (g *GenServer[S, Req, Resp]) Call(req Req) Resp

Call sends a synchronous request and blocks until the server returns a response.

Example
package main

import (
	"fmt"

	"github.com/mikehelmick/go-functional/genserver"
)

type counterCmd int

const (
	cmdGet counterCmd = iota
	cmdIncrement
	cmdAdd
	cmdReset
)

type counterReq struct {
	cmd counterCmd
	val int
}

// counterServer is a simple integer counter implementing genserver.Server.
type counterServer struct{}

func (counterServer) Init() int { return 0 }

func (counterServer) HandleCall(req counterReq, state int) (int, int) {
	switch req.cmd {
	case cmdGet:
		return state, state
	case cmdIncrement:
		return state + 1, state + 1
	case cmdAdd, cmdReset:
		return state, state
	}
	return state, state
}

func (counterServer) HandleCast(req counterReq, state int) int {
	switch req.cmd {
	case cmdAdd:
		return state + req.val
	case cmdReset:
		return 0
	case cmdGet, cmdIncrement:
		return state
	}
	return state
}

func main() {
	gs := genserver.Start[int, counterReq, int](counterServer{})
	defer gs.Stop()

	gs.Cast(counterReq{cmd: cmdAdd, val: 10})
	gs.Call(counterReq{cmd: cmdIncrement})
	fmt.Println(gs.Call(counterReq{cmd: cmdGet}))
}
Output:
11

func (*GenServer[S, Req, Resp]) Cast

func (g *GenServer[S, Req, Resp]) Cast(req Req)

Cast sends an asynchronous message and returns immediately without waiting for the server to process it.

func (*GenServer[S, Req, Resp]) Stop

func (g *GenServer[S, Req, Resp]) Stop()

Stop shuts down the server and blocks until the loop goroutine exits. Calling Stop more than once is safe; subsequent calls are no-ops.

type Server

type Server[S, Req, Resp any] interface {
	// Init returns the initial state of the server.
	Init() S
	// HandleCall processes a synchronous request. It returns a response
	// sent back to the caller and the updated state.
	HandleCall(req Req, state S) (Resp, S)
	// HandleCast processes an asynchronous message. It returns the
	// updated state. No response is sent to the caller.
	HandleCast(msg Req, state S) S
}

Server defines the callbacks for a GenServer process. S is the state type, Req is the request/message type, and Resp is the response type returned by synchronous calls.

Jump to

Keyboard shortcuts

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