ssm

package module
v0.0.0-...-80a12ae Latest Latest
Warning

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

Go to latest
Published: Feb 6, 2024 License: MIT Imports: 5 Imported by: 1

README

Simple State Machine

Build Status

This is a very basic API for creating State Machines.

A state is represented by any function which accepts a context as a parameter and returns another state function.

The states are run iteratively until the End state is reached, when the state machine stops.

Error handling is provided through two Error states:

  • The simple Error state which stops the execution
  • The RestartError state which restarts the execution from the first received state.

type Fn func(ctx context.Context) Fn

To start the state machine you can pass a state to one of the Run or RunParallel functions. The later runs the received states in parallel and the former does so sequentially.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsEnd

func IsEnd(f Fn) bool

func IsError

func IsError(f Fn) bool

IsError ver grubby API to check if a state Fn is an error state

func Run

func Run(ctx context.Context, states ...Fn)
Example
package main

import (
	"context"
	"fmt"

	sm "git.sr.ht/~mariusor/ssm"
)

type maxKey string

const _max maxKey = "__max"

func start(ctx context.Context) sm.Fn {
	fmt.Print("start ")
	i := iter(0)
	return i.next
}

type iter int

func (i *iter) next(ctx context.Context) sm.Fn {
	fmt.Printf("%d ", *i)
	if m, ok := ctx.Value(_max).(int); ok {
		if int(*i) == m {
			fmt.Print("end")
			return sm.End
		}
	}
	*i = *i + 1
	return i.next
}

func main() {
	ctx := context.WithValue(context.Background(), _max, 10)

	sm.Run(ctx, start)

}
Output:

start 0 1 2 3 4 5 6 7 8 9 10 end

func RunParallel

func RunParallel(ctx context.Context, states ...Fn)

Types

type ErrorFn

type ErrorFn Fn

func (ErrorFn) Error

func (f ErrorFn) Error() string

type Fn

type Fn func(context.Context) Fn
var End Fn = nil

func After

func After(d time.Duration, state Fn) Fn

After runs the received state after d time.Duration has elapsed. This function blocks until the timer elapses, when it returns the next resolved state.

func At

func At(t time.Time, state Fn) Fn

At runs the received state at t time.Time. This function blocks until the time is reached, when it returns the next resolved state.

func BackOff

func BackOff(dur StrategyFn, fn Fn) Fn

BackOff returns an aggregator function which can be used to execute the received state with increasing delays. The function for determining the delay is passed in the StrategyFn "dur" parameter.

There is no end condition, so take care to limit the execution through some external method.

func Batch

func Batch(states ...Fn) Fn

Batch executes the received states sequentially, and accumulates the next states. The resulting next state is returned as a sequential batch of all the non End states resolved.

func ErrorEnd

func ErrorEnd(err error) Fn

ErrorEnd represents an error state which returns an End state.

func ErrorRestart

func ErrorRestart(err error) Fn

ErrorRestart represents an error state which returns the first iteration passed. This iteration is loaded from the context, and is saved there by the Run and RunParallel functions.

func NonBlocking

func NonBlocking(states ...Fn) Fn

NonBlocking executes states in a goroutine and until it resolves it returns a wait state

func Parallel

func Parallel(states ...Fn) Fn

Parallel executes the received states in parallel goroutines, and accumulates the next states. The resulting next state is returned as a parallel batch of all the non End states resolved.

func Retry

func Retry(retries int, fn Fn) Fn

Retry is a way to construct a state machine out of repeating the execution of received state "fn", until the number of "retries" has been reached, or "fn" returns the End state.

The "fn" parameter can be one of the functions accepting a StrategyFn parameters, which wrap the original state Fn, and which provide a way to delay the execution between retries.

Example
package main

import (
	"context"
	"fmt"
	"time"

	sm "git.sr.ht/~mariusor/ssm"
)

func main() {
	st := time.Now()
	cnt := 0

	fmt.Printf("Retries: ")
	start := sm.Retry(10, func(_ context.Context) sm.Fn {
		cnt++
		run := time.Now()
		fmt.Printf("%d:%s ", cnt, run.Sub(st).Truncate(time.Millisecond))
		st = run
		return sm.ErrorEnd(fmt.Errorf("retrying"))
	})

	sm.Run(context.Background(), start)

}
Output:

Retries: 1:0s 2:0s 3:0s 4:0s 5:0s 6:0s 7:0s 8:0s 9:0s 10:0s

func StartState

func StartState(ctx context.Context) Fn

StartState retrieves the initial state from ctx context.Context. If nothing is found it returns the End state.

type StrategyFn

type StrategyFn func() time.Duration

StrategyFn is the type that returns the desired time.Duration for the BackOff function.

func Constant

func Constant(d time.Duration) StrategyFn

Constant returns a constant time.Duration for every call.

Example
package main

import (
	"context"
	"fmt"
	"time"

	sm "git.sr.ht/~mariusor/ssm"
)

const delay = 10 * time.Millisecond

func main() {
	st := time.Now()
	cnt := 0

	fmt.Printf("Retries: ")
	start := sm.Retry(8, sm.BackOff(sm.Constant(delay), func(_ context.Context) sm.Fn {
		run := time.Now()
		cnt++
		fmt.Printf("%d:%s ", cnt, run.Sub(st).Truncate(10*time.Millisecond))
		st = run
		return sm.ErrorEnd(fmt.Errorf("keep going"))
	}))

	sm.Run(context.Background(), start)

}
Output:

Retries: 1:10ms 2:10ms 3:10ms 4:10ms 5:10ms 6:10ms 7:10ms 8:10ms

func Jitter

func Jitter(max time.Duration, fn StrategyFn) StrategyFn

Jitter adds random jitter of "max" time.Duration for the fn StrategyFn

Example
package main

import (
	"context"
	"fmt"
	"time"

	sm "git.sr.ht/~mariusor/ssm"
)

const delay = 10 * time.Millisecond

func main() {
	st := time.Now()
	cnt := 0

	fmt.Printf("Retries: ")
	start := sm.Retry(2, sm.BackOff(sm.Jitter(time.Millisecond, sm.Constant(delay)), func(_ context.Context) sm.Fn {
		run := time.Now()
		cnt++
		// NOTE(marius): The jitter adds a maximum of one ms, so with truncation of 2 ms the output will be correct
		// This is not a very good example test.
		fmt.Printf("%d:%s ", cnt, run.Sub(st).Truncate(2*time.Millisecond))
		st = run
		return sm.ErrorEnd(fmt.Errorf("never right"))
	}))

	sm.Run(context.Background(), start)

}
Output:

Retries: 1:10ms 2:10ms

func Linear

func Linear(d time.Duration, m float64) StrategyFn

Linear returns the linear function of the time.Duration multiplied by mul for every call.

Example
package main

import (
	"context"
	"fmt"
	"time"

	sm "git.sr.ht/~mariusor/ssm"
)

const delay = 10 * time.Millisecond

func main() {
	st := time.Now()
	cnt := 0

	fmt.Printf("Retries: ")
	start := sm.Retry(5, sm.BackOff(sm.Linear(delay, 2), func(_ context.Context) sm.Fn {
		run := time.Now()
		cnt++
		fmt.Printf("%d:%s ", cnt, run.Sub(st).Truncate(10*time.Millisecond))
		st = run
		return sm.ErrorEnd(fmt.Errorf("don't stop"))
	}))

	sm.Run(context.Background(), start)

}
Output:

Retries: 1:10ms 2:20ms 3:40ms 4:80ms 5:160ms

Directories

Path Synopsis
cmd module

Jump to

Keyboard shortcuts

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