Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Easy FSM Add-on: Makes making Finite State Machines with go-statemachine easier #4

Merged
merged 20 commits into from
Feb 26, 2020

Conversation

hannahhoward
Copy link
Contributor

@hannahhoward hannahhoward commented Feb 24, 2020

Goals

This builds somewhat off the work in go-storage-miner, somewhat from looking at other go fsm libraries https://github.com/looplab/fsm . The goal is as follows: provide a simple, mostly type safe, readable way to build finite state machines that work with go-statemachine.

Implementation

There are a few innovations here but:

  1. Provide a simple syntax for defining events -- the state transitions they implement plus any additional state modifications they make. The state transitions, found in eventbuilder.go can be defined as:
  • FromMany - To
  • FromAny - To
  • From - To
  • any of the above - ToNoChange (just trigger the state handler again
  • you can do multiple per event (i.e. From-To + From-To if different states trigger different destinations)
  1. A state is any object, but it must have specified field that acts as a "key" to represent its uniquely identifiable state, and which is managed by the FSM according to events dispatched.

  2. Provide a tool for easily defining handlers for each state - all handlers are defined interms of

  • Context -- for dispatching events & accessing go context
  • Environment - a shared item for all statemachines that gives access to any external dependencies
  • State - current value of the state being tracked
  1. Provide an optional notification channel that is called any time an event is successfully applied along with the state connected to it

  2. There is also a lower level event machine class (see eventmachine.go) for defining and type checking the events a state machine has, which also handles actually applying events to manage state.

  3. There is a utility TestContext class that can be paired with an event machine to test statehandlers and the state transitions they generate atomically

For Discussion

There is as you can see, some fairly extensive use of reflect here. I am ultimately not SO worried, cause while errors in the construction of the statemachine will be caught at runtime, they'll likely be caught at the very beginning of executing the program, and early in development -- they're almost a compile error. However, one thing I'm not sure about is the event callbacks themselves. I thought of making these an interface so the parameters could be properties on a struct, and they could be compile time checked. I didn't feel like I'd gotten it exactly right which is why I haven't yet moved forward with this but, it's an option. We could potentially do away with naming events entirely and have the name be the struct, but that gets a bit weird in terms of notification.

add module for constructing statehandlers as simple finite state machines, to support readable
definitions, type checking, and testing
Add the ability ot dispatch a synchronous event to the state machine and wait for a result
Use a full transition map for events to make them more flexible
support a transition that applies to any source state
Add ability to get identifier for this instance of the state machine
Allow each instance of fsm to get a unique instance of outside dependencies
remove universal events, add notifications, add params struct, make event name an interface
remove world by indentifier for simplicity, rename world to environment
add more data about why state handlers fail
add begin function from base group to fsm group
simplify builders for state, extract state machine from event machine
adds a utility class for testing state machines by allowing contexts to actually be replayed with a
given event machine
fsm/types.go Outdated
// - event X does not exist
//
// - arguments don't match expected transition
Event(event EventName, args ...interface{}) error

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In reviewing the related go-filecoin PR I found this naming confusing. Can there be some naming distinction between a fsm.Context Event and a statemachine Event? If Event initiates a state transition maybe it could be called (Start|Post|Try|Initiate|Queue)Event, or (Start|etc)Transition or something like that. Then in the go-filecoin call it would read better.

Copy link
Contributor

@ingar ingar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, seems nice to use this to build FSM.

One comment, I am more used to the pattern of callbacks on state transitions, not attached to events themselves. I know both are patterns in FSMs. Seems like it could be useful to have StateEntryFunc (and maybe StateExitFunc) callbacks, for things like logging errors or cleanup or whatever.

func (t transitionToBuilder) To(to StateKey) EventBuilder {
transitions := t.transitionsSoFar
for _, from := range t.nextFrom {
transitions[from] = to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, what do you think about emitting a warning or error even if we are clobbering previously-set callbacks or transitions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hate to say it thought I'd rather not mess up the fluidity of the interface I might store the error and fail when you try to construct the event machine which is delayed but preserves the DSL.

fsm/types.go Outdated
// func applyTransition<StateType, T extends any[]>(s stateType, args ...T)
// and then an event can be dispatched on context or group
// with the form .Event(Name, args ...T)
type ApplyTransitionFunc interface{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with this naming convention... this would have been be clearer to if it was named something like StateMutatorFunc or EventCallbackFunc and WithMutator(), etc. But if it's a well-known naming convention I'm cool with it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no I just made it up will change :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaning towards "actions" cause that is the actual FSM terminolgy for things that happen along with an event

@hannahhoward
Copy link
Contributor Author

@ingar I would consider the StateHandlers to be StateEntryFunc's, effectively

Rename applyTransition to action to match actual FSM concept it represents
rename event -> trigger with context
rename eventMachine -> eventProcessor
rename eventProcessor.Event -> eventProcessor.Generate
rename stateHandler -> stateEntryFunc
track various DSL errors in event builder so it fails when used
@hannahhoward hannahhoward merged commit e5b11df into master Feb 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants