Finite State Machine for Go

Build Status Coverage Status GoDoc Go Report Card

FSM for Go

FSM is a finite state machine for Go.

It is heavily based on two FSM implementations:

For API docs and examples see http://godoc.org/github.com/looplab/fsm

Basic Example

From examples/simple.go:

package main

import (
    "fmt"
    "github.com/looplab/fsm"
)

func main() {
    fsm := fsm.NewFSM(
        "closed",
        fsm.Events{
            {Name: "open", Src: []string{"closed"}, Dst: "open"},
            {Name: "close", Src: []string{"open"}, Dst: "closed"},
        },
        fsm.Callbacks{},
    )

    fmt.Println(fsm.Current())

    err := fsm.Event("open")
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(fsm.Current())

    err = fsm.Event("close")
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(fsm.Current())
}

Usage as a struct field

From examples/struct.go:

package main

import (
    "fmt"
    "github.com/looplab/fsm"
)

type Door struct {
    To  string
    FSM *fsm.FSM
}

func NewDoor(to string) *Door {
    d := &Door{
        To: to,
    }

    d.FSM = fsm.NewFSM(
        "closed",
        fsm.Events{
            {Name: "open", Src: []string{"closed"}, Dst: "open"},
            {Name: "close", Src: []string{"open"}, Dst: "closed"},
        },
        fsm.Callbacks{
            "enter_state": func(e *fsm.Event) { d.enterState(e) },
        },
    )

    return d
}

func (d *Door) enterState(e *fsm.Event) {
    fmt.Printf("The door to %s is %s\n", d.To, e.Dst)
}

func main() {
    door := NewDoor("heaven")

    err := door.FSM.Event("open")
    if err != nil {
        fmt.Println(err)
    }

    err = door.FSM.Event("close")
    if err != nil {
        fmt.Println(err)
    }
}

License

FSM is licensed under Apache License 2.0

http://www.apache.org/licenses/LICENSE-2.0

Owner
Looplab AB
Cloud Architecture
Looplab AB
Comments
  • Add context to Event method, callback handlers and canceling an event

    Add context to Event method, callback handlers and canceling an event

    This PR adds a context to the main Event method. It's also in response to https://github.com/looplab/fsm/pull/37#issuecomment-992477957. Finally, it's the first step towards implementing #43 and #38.

    Adding the context is a breaking change and while the library isn't at v1.0.0 yet it's still an inconvenience. I distinctly didn't add a CallbacksContext field because of the following reasons:

    • while an inconvenience, it's easy to add the context to all callback functions – even the way I did in the test suite using _ context.Context
    • it'd make the internal logic fragile
    • it's now best practice to have a context available, especially given the fact that the library supports asynchronous transitions and be able to cancel work that isn't needed anymore

    Of course, if the maintainer(s)/contributors/the community at large wants this then it's still totally possible.

    To see the whole thing in action, potential users can use the full implementation: https://github.com/alfaview/fsm/pull/1 (this PR only contains the first part).

  • Allow transitions in callbacks

    Allow transitions in callbacks

    This allows state transitions to happen in enter state and after event callbacks by adding the possibility of "starting" a state machine and have it execute multiple state transitions in succession, given that no errors occur.

    The equivalent code without this change:

    var errTransition error
    
    for errTransition == nil {
    	transitions := request.FSM.AvailableTransitions()
    	if len(transitions) == 0 {
    		break
    	}
    	if len(transitions) > 1 {
    		errTransition = errors.New("only 1 transition should be available")
    	}
    	errTransition = request.FSM.Event(transitions[0])
    }
    
    if errTransition != nil {
    	fmt.Println(errTransition)
    }
    

    Arguably, that’s bad because of several reasons:

    1. The state machine is used like a puppet.
    2. The state transitions that make up the "happy path" are encoded outside the state machine.
    3. The code really isn’t good.
    4. There’s no way to intervene or make different decisions on which state to transition to next (reinforces bullet point 2).
    5. There’s no way to add proper error handling.

    It is possible to fix a certain number of those problems but not all of them, especially 2 and 4 but also 1.

    The added test is green and uses both an enter state and an after event callback. No other test case was touched in any way (besides enhancing the context one that was added in the previous commit).

    The relevant test is here: https://github.com/alfaview/fsm/blob/94fb353ef273319b14928aa3a04ab4e01a58d6da/fsm_test.go#L735-L774

    This fixes #36. It also fixes #38. And it fixes #43. Finally, it fixes #61.

  • Self Transitions

    Self Transitions

    Self transitions don't seem to be supported. The following code snippet from fsm.go seems to consider self transitions as errors.

    if f.current == dst {
    	f.afterEventCallbacks(e)
    	return &NoTransitionError{e.Err}
    }
    

    Perhaps I'm not using the fsm appropriately. any thoughts?

  • Support for metadata

    Support for metadata

    • Added a feature such that data can be stored at the fsm level that can be used across the callbacks
    • Refer to data.go in examples for demonstration of this feature
  • Fix deadlock when event fired in callback

    Fix deadlock when event fired in callback

    Fixes #36

    Changes:

    • Do check if there is a transition going on also before acquiring event lock
    • Add test case to verify no deadlock is happening when event is fired in callback

    I'm not sure if that is safe to remove second check (after acquiring stateMu lock) so I just added one more before locks.

  • Scheduling internal transitions within callbacks

    Scheduling internal transitions within callbacks

    Hi, I have a particular need and want to get your views and the best way to deal with this. Here are my transitions -

    [1]initial=disconnected [2]disconnected: on "connect"->connected [3]connected: on "disconnect"->disconnected [4]connected: on "echo" -> "connected" [5]connected: on "echolimit_met"->"up"

    Please note that on line [4], the transition is from connected->connected which results in NoTransitionError and that is ok.

    Now, any number of echo events might happen in connected state. But, after 3rd echo I need a transition to "up"

    I intend to do this transition (using a echolimit_met event) after the current event has been processed. There is currently no way to do this within the callbacks and I'm doing this right now by starting a new goroutine within the callback

    go func() {
    		fsm.Event(e)
    	}()
    

    Do this approach seem right to you?

    Perhaps we could have a fsm.ScheduleLater(e) which the fsm executes towards the end of current event processing.

  • Proper way to call Events

    Proper way to call Events

    Sorry if this is a stupid question. Related To #37

    Why can't we fire events in Callback? where are we supposed to call Event Transition?

    I'm trying to write a protocol lib, that reacts based on the server response. So from the callback I'm sending messages to the server, and based on the response I fire a specific event.

    Thanks for your help

  • Full unit test coverage on package fsm.

    Full unit test coverage on package fsm.

    I use your FSM package. The branch I am requesting to be merged in to your master branch brings the package to full unit test coverage. The key changes were:

    1. Mock out the FSM's Transition function into an interface, so that I could provide an implementation that would throw an internal error.
    2. Add a unit test file for errors.go. This mostly just checks return strings, but it is nice to have coverage at 100%.
  • Multiple events with same name

    Multiple events with same name

    And another one I'm afraid ... really liking FSM though! Following the example #2, I want to have do following:

    {Name: "purchase", Src: []string{"quoted"}, Dst: "open"}, {Name: "purchase", Src: []string{"quoted"}, Dst: "onhold"},

    I issue an purchase event, if it succeed the new state should be open. If it fails, or is cancelled, I would like FSM to try the next. Fysom seems to do something like that? https://github.com/oxplot/fysom/blob/master/fysom.py#L65

    But this https://github.com/looplab/fsm/blob/master/fsm.go#L179 pretty much prevents me from doing that.

    Any suggestions, am I doing it wrong?

  • feat: add flowChart style to mermaid visualization

    feat: add flowChart style to mermaid visualization

    We needed a new way of displaying the state machine in mermaid style in order to highlight the current state. To my knowledge this cannot be easily done with the previously implemented StateDiagram type, which is why we implemented the far more flexible style FlowChart (LR). The code is backward-compatible . I would suggest to move the different visualizers (graphviz/mermaid (maybe more in the future)) into separate files, but did not want to do this before general acceptance of this feature.

  • Allow validating transitions without triggering event

    Allow validating transitions without triggering event

    Hi,

    Wanted the check for valid transitions to include the before callbacks - I think similar to suggestion in #10. My changes break the existing interface for Can/Cannot so maybe not something that you would want to merge as it is, but any thoughts on whether you think this functionality would be useful and if so, what changes you would need to the PR before accepting?

    Thanks

    Alastair

  • Why fsm code not update to the latest version

    Why fsm code not update to the latest version

    Hi, I see you example :

    fsm

    When run the demo , it errors:

    image

    So I look the code , found the callback :

    type Callback func(*Event)

    But the latest version, the callback :

    type Callback func(context.Context, *Event)

    So I try to update the code to the latest version:

    image

    But it not work

  • fsm.Event(ctx, ...) not work properly with context.WithTimeout()

    fsm.Event(ctx, ...) not work properly with context.WithTimeout()

    When I use context.WithTimeout(ctx, time.Duration) with fsm.Event(ctx, ...) like:

    Callbacks{
    			"enter_end": func(ctx context.Context, e *Event) {
    				go func() {
    					<-ctx.Done()
    					enterEndAsyncWorkDone = true
    				}()
    			},
    		},
    

    the internal block exec immediately, not after timeout.

  • add NewFSMFromTemplate

    add NewFSMFromTemplate

    suppor create from template

    template := `
    1=New
    2=Ready
    3=Waiting
    4=Running
    5=Terminated
    Admitted:1->2
    Dispatch:2 -> 4
    Interrupt:4->2
    InputOutputoreventwait:4->3
    InputOutputoreventcompletion:3->2
    Exit:4->5
    `
    

    format as follow:

    //for example:
    action1:state1 -> state2
    action2:state12-> state3
    action3:state13-> state2
    
  • when I use SetState in a callback in a `before_<event>`  => deadlock

    when I use SetState in a callback in a `before_` => deadlock

    I am writing an fsm that needs the follow:

    an Event named creating can goes to 3 different states (ok, unreachable, creating) depending on some checks. In order to realize this, I wrote a callback function like this one:

    func (p *ClusterState) beforeCreating(e *fsm.Event) {
    	p.Logger.Infof("Triggered event %v, trying to reach status %s", e.Event, e.Dst)
    
    	if p.Unreachable {
    		timeLimit := p.CreatedAt.Add(time.Minute * 30)
    		if timeLimit.Before(p.Clock.Now()) {
    			e.FSM.SetState("CREATING") // it hangs here
    		}
    	}
    }
    

    Probably there is a better way to do that, or i am missing something. Can anyone help me with that? Thanks in advance!

  • Generics

    Generics

    Hi Max,

    This is just a POC to see what it might look like, not so convinced if its worth the effort. ~~Callback handling is hard to transform because it depends on string prefixes of events or states~~. Callback handling is now generic as well.

  • add before enter state callback

    add before enter state callback

    This PR adds a new callback called "before enter state".

    It is different from enter state callback, when this callback return an error, the state remains the origin state.

:pushpin: State of the art point location and neighbour finding algorithms for region quadtrees, in Go
:pushpin: State of the art point location and neighbour finding algorithms for region quadtrees, in Go

Region quadtrees in Go Region quadtrees and efficient neighbour finding techniques in Go Go-rquad proposes various implementations of region quadtrees

Dec 13, 2022
A generic Go library for implementations of tries (radix trees), state commitments and proofs of inclusion

trie.go Go library for implementations of tries (radix trees), state commitments and proof of inclusion for large data sets. It implements a generic 2

Aug 3, 2022
DataFrames for Go: For statistics, machine-learning, and data manipulation/exploration
DataFrames for Go: For statistics, machine-learning, and data manipulation/exploration

Dataframes are used for statistics, machine-learning, and data manipulation/exploration. You can think of a Dataframe as an excel spreadsheet. This pa

Jan 3, 2023
Finite State Machine for Go
Finite State Machine for Go

FSM for Go Finite State Machine for Go It is heavily inspired from looplab/fsm library but with more abstractions and optimizations License FSM is lic

Nov 30, 2021
Finite-state machine with processors

FSM Finite-state machine with processors. Builder Register state processors type state1Processor struct { // clients } func NewState1Processor(..

Jan 7, 2022
State observer - StateObserver used to synchronize the local(cached) state of the remote object with the real state

state observer StateObserver used to synchronize the local(cached) state of the

Jan 19, 2022
A Go library implementing an FST (finite state transducer)
A Go library implementing an FST (finite state transducer)

vellum A Go library implementing an FST (finite state transducer) capable of: mapping between keys ([]byte) and a value (uint64) enumerating keys in l

Jan 8, 2023
Package mafsa implements Minimal Acyclic Finite State Automata in Go, essentially a high-speed, memory-efficient, Unicode-friendly set of strings.

MA-FSA for Go Package mafsa implements Minimal Acyclic Finite State Automata (MA-FSA) with Minimal Perfect Hashing (MPH). Basically, it's a set of str

Oct 27, 2022
Package fsm allows you to add finite-state machines to your Go code.

fsm Package fsm allows you to add finite-state machines to your Go code. States and Events are defined as int consts: const ( StateFoo fsm.State =

Dec 9, 2022
cluster-api-state-metrics (CASM) is a service that listens to the Kubernetes API server and generates metrics about the state of custom resource objects related of Kubernetes Cluster API.

Overview cluster-api-state-metrics (CASM) is a service that listens to the Kubernetes API server and generates metrics about the state of custom resou

Oct 27, 2022
Us-api: a simple service that returns the US state code based on the state

us-api us-api is a simple service that returns the US state code based on the state. It does not support creating, updating nor deleting data. Local D

Dec 13, 2021
Plinko - a Fluent State Machine for Go
 Plinko - a Fluent State Machine for Go

Plinko - a Fluent State Machine for Go Build Status Create state machines and lightweight state machine-based workflows directly in golang code The pr

Jan 3, 2023
Raft library Raft is a protocol with which a cluster of nodes can maintain a replicated state machine.

Raft library Raft is a protocol with which a cluster of nodes can maintain a replicated state machine. The state machine is kept in sync through the u

Oct 15, 2021
Tendermint Core is a Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine
Tendermint Core is a Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine

Tendermint Core is a Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine - written in any programming language - and securely replicates it on many machines.

Sep 8, 2022
An ease to use finit state machine golang implementation.Turn any struct to a fsm with graphviz visualization supported.

go-fsm An ease to use finit state machine golang implementation.Turn any struct to a fsm with graphviz visualization supported. usage import github.co

Dec 26, 2021
A deadly simple state machine for Golang

go-litefsm A deadly simple state machine for Golang, within 100 LOC. Example // Create accepted transitions transitions := NewTransitions() transition

Jul 13, 2022
Tendermint Core - A Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine
Tendermint Core - A Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine

Tendermint Core - A Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine

Jan 25, 2022
Using finite projective planes to make card (maybe video) games

pairwise What it is Using finite projective plane to generate card (maybe video) games. Running Run with go run . Right now uses Go 1.17 but 1.18 just

Jan 24, 2022
:pushpin: State of the art point location and neighbour finding algorithms for region quadtrees, in Go
:pushpin: State of the art point location and neighbour finding algorithms for region quadtrees, in Go

Region quadtrees in Go Region quadtrees and efficient neighbour finding techniques in Go Go-rquad proposes various implementations of region quadtrees

Dec 13, 2022
Go library for creating state machines
Go library for creating state machines

Stateless Create state machines and lightweight state machine-based workflows directly in Go code: phoneCall := stateless.NewStateMachine(stateOffHook

Jan 6, 2023