statemachine

package module
v0.0.0-...-0bb2e03 Latest Latest
Warning

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

Go to latest
Published: Feb 4, 2015 License: MIT Imports: 2 Imported by: 1

README

go-statemachine

Build Status

Transform your Go structs into tiny state machines.

About

In Go you quite often need to make your struct thread-safe (basically serialize all the calls to any of its methods) and also allow only particular sequences of calls, e.g. when Close is called, no other method can be called ever again since it does not make sense to call it. And this is exactly what go-statemachine is handling for your.

No mutexes are being used, just channels. It might not be as fast as mutexes, but it's nice and robust.

State of the Project

I am still developing this, so things may and will change if I find it more appropriate for my use cases.

Example

Check ExampleStateMachine in statemachine_test.go to see an example.

Documentation

We are writing Go, so GoDoc, what were you expecting?

License

MIT

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// Returned from Emit if there is no mapping for the current state and the
	// event that is being emitted.
	ErrIllegalEvent = errors.New("Illegal event received")

	// Returned from a method if the state machine is already terminated.
	ErrTerminated = errors.New("State machine terminated")
)

Functions

This section is empty.

Types

type Event

type Event struct {
	Type EventType
	Data EventData
}

Events are the basic units that can be processed by a state machine.

type EventData

type EventData interface{}

type EventHandler

type EventHandler func(s State, e *Event) (next State)

Various EventHandlers can be registered to process events in particular states. By registering event handlers we build up a mapping of state x event -> handler and the handler is invoked exactly in the defined state when the defined event is emitted.

Once a handler is invoked, its role is to take the StateMachine into the next state, doing some useful work on the way.

If an event is emitted in a state where no handler is defined, ErrIllegalEvent is returned.

type EventType

type EventType int

type State

type State int

type StateMachine

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

StateMachine is the only struct this package exports. Once an event is emitted on a StateMachine, the relevant handler is fetched and invoked. StateMachine takes care of all the synchronization, it is thread-safe. It does not use any locking, just channels. While that may be a bit more overhead, it is more robust and clear.

It uses an unbuffered channel for passing events to the internal goroutine, so all the methods block until their requests read from that channel.

Example
// Allocate space for 3 states, 3 commands and 10 requests in the channel.
sm := New(stateStopped, 3, 3)

// Allocate a new Context which is going to keep our data between
// the handler calls.
ctx := new(Context)
fmt.Printf("Context struct: %#v\n", ctx)

// RUN
sm.On(cmdRun, []State{
	stateStopped,
}, ctx.handleRun)

// STOP
sm.On(cmdStop, []State{
	stateRunning,
}, ctx.handleStop)

// CLOSE
sm.On(cmdClose, []State{
	stateStopped,
	stateRunning,
}, ctx.handleClose)

var (
	run  = &Event{cmdRun, nil}
	stop = &Event{cmdStop, nil}
	cls  = &Event{cmdClose, nil}
)

sm.Emit(run)
sm.Emit(stop)
sm.Emit(run)
sm.Emit(stop)

// Show how to write an event producer.
exit := make(chan struct{})

go func() {
	eventCh := make(chan *Event)
	go func() {
		eventCh <- run
		eventCh <- stop
		eventCh <- run
		eventCh <- cls
		close(exit)
	}()

	for {
		select {
		case event := <-eventCh:
			sm.Emit(event)
		case <-sm.TerminatedChannel():
			return
		}
	}
}()

// Wait for the inner goroutine to close the exit channel.
<-exit

// Close our state machine.
sm.Terminate()

// Wait for the state machine to terminate.
<-sm.TerminatedChannel()

fmt.Println("Goroutine exited")
Output:

Context struct: &statemachine.Context{seq:0}
Event number 1 received
STOPPED -> RUNNING by RUN
Event number 2 received
RUNNING -> STOPPED by STOP
Event number 3 received
STOPPED -> RUNNING by RUN
Event number 4 received
RUNNING -> STOPPED by STOP
Event number 5 received
STOPPED -> RUNNING by RUN
Event number 6 received
RUNNING -> STOPPED by STOP
Event number 7 received
STOPPED -> RUNNING by RUN
Event number 8 received
RUNNING -> CLOSED by CLOSE
Goroutine exited

func New

func New(initState State, stateCount, eventCount uint) *StateMachine

Create new StateMachine. Allocate internal memory for particular number of states and events.

func (*StateMachine) Emit

func (sm *StateMachine) Emit(event *Event) error

Emit an event.

func (*StateMachine) GetState

func (sm *StateMachine) GetState() (st State, err error)

GetState returns the internal state machine state.

func (*StateMachine) IsHandlerAssigned

func (sm *StateMachine) IsHandlerAssigned(t EventType, s State) (defined bool, err error)

Check if a handler is defined for this state and event.

func (*StateMachine) Off

func (sm *StateMachine) Off(t EventType, s State) error

Drop the handler assigned to the requested state and event.

func (*StateMachine) On

func (sm *StateMachine) On(t EventType, ss []State, h EventHandler) error

Register an event handler.

func (*StateMachine) OnChain

func (sm *StateMachine) OnChain(t EventType, ss []State, hs []EventHandler) error

Register the chain of event handlers.

func (*StateMachine) SetState

func (sm *StateMachine) SetState(state State) error

SetState changes the internal state machine state.

func (*StateMachine) Terminate

func (sm *StateMachine) Terminate() error

Terminate the internal event loop and close all internal channels. Particularly the termination channel is closed to signal all producers that they can no longer emit any events and shall exit.

func (*StateMachine) TerminatedChannel

func (sm *StateMachine) TerminatedChannel() (isTerminatedCh chan struct{})

TerminateChannel can be used to obtain a channel that is closed once the state machine is terminated and is no longer willing to accept any events. This is useful if you want to start multiple goroutines to asynchronously post events. You can just start them, pass them this termination channel and leave them be. The only requirement is that those producer goroutines should exit or simply stop posting any events as soon as the channel is closed.

Jump to

Keyboard shortcuts

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