Skip to content

Commit

Permalink
protofsm: allow multiple internal events to be emitted
Browse files Browse the repository at this point in the history
In this commit, we update the execution logic to allow multiple internal
events to be emitted. This is useful to handle potential out of order
state transitions, as they can be cached, then emitted once the relevant
pre-conditions have been met.
  • Loading branch information
Roasbeef committed Mar 6, 2024
1 parent 5b510f0 commit fa12732
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 23 deletions.
2 changes: 1 addition & 1 deletion protofsm/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var log btclog.Logger

// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("PRCL", nil))
UseLogger(build.NewSubLogger("PFSM", nil))
}

// DisableLog disables all library log output. Logging output is disabled
Expand Down
43 changes: 22 additions & 21 deletions protofsm/state_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type EmittedEvent[Event any] struct {
// InternalEvent is an optional internal event that is to be routed
// back to the target state. This enables state to trigger one or many
// state transitions without a new external event.
InternalEvent fn.Option[Event]
InternalEvent fn.Option[[]Event]

// ExternalEvent is an optional external event that is to be sent to
// the daemon for dispatch. Usually, this is some form of I/O.
Expand Down Expand Up @@ -342,9 +342,9 @@ func (s *StateMachine[Event, Env]) executeDaemonEvent( //nolint:funlen
// any preconditions as well as post-send events.
case *SendMsgEvent[Event]:
sendAndCleanUp := func() error {
log.Debugf("FSM(%v): sending message to target(%v): "+
log.Debugf("FSM(%v): sending message to target(%x): "+
"%v", s.cfg.Env.Name(),
daemonEvent.TargetPeer,
daemonEvent.TargetPeer.SerializeCompressed(),
newLogClosure(func() string {
return spew.Sdump(daemonEvent.Msgs)
}),
Expand Down Expand Up @@ -481,7 +481,11 @@ func (s *StateMachine[Event, Env]) executeDaemonEvent( //nolint:funlen
defer s.wg.Done()
for {
select {
case spend := <-spendEvent.Spend:
case spend, ok := <-spendEvent.Spend:
if !ok {
return
}

// If there's a post-send event, then
// we'll send that into the current
// state now.
Expand Down Expand Up @@ -551,12 +555,6 @@ func (s *StateMachine[Event, Env]) executeDaemonEvent( //nolint:funlen
func (s *StateMachine[Event, Env]) applyEvents(currentState State[Event, Env],
newEvent Event) (State[Event, Env], error) {

log.Debugf("FSM(%v): applying new event: %v", s.cfg.Env.Name(),
newLogClosure(func() string {
return spew.Sdump(newEvent)
}),
)

eventQueue := fn.NewQueue(newEvent)

// Given the next event to handle, we'll process the event, then add
Expand Down Expand Up @@ -614,18 +612,21 @@ func (s *StateMachine[Event, Env]) applyEvents(currentState State[Event, Env],
// our event queue.
//
//nolint:lll
events.InternalEvent.WhenSome(func(inEvent Event) {
log.Debugf("FSM(%v): adding new "+
"internal event to queue: %v",
s.cfg.Env.Name(),
newLogClosure(func() string {
return spew.Sdump(
inEvent,
)
}),
)
events.InternalEvent.WhenSome(func(es []Event) {
for _, inEvent := range es {
log.Debugf("FSM(%v): adding "+
"new internal event "+
"to queue: %v",
s.cfg.Env.Name(),
newLogClosure(func() string { //nolint:lll
return spew.Sdump(
inEvent,
)
}),
)

eventQueue.Enqueue(inEvent)
eventQueue.Enqueue(inEvent)
}
})

return nil
Expand Down
12 changes: 11 additions & 1 deletion protofsm/state_machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ func (d *dummyStateStart) ProcessEvent(event dummyEvents, env *dummyEnv,
return &StateTransition[dummyEvents, *dummyEnv]{
NextState: &dummyStateStart{},
NewEvents: fn.Some(EmittedEvent[dummyEvents]{
InternalEvent: fn.Some(dummyEvents(&goToFin{})),
InternalEvent: fn.Some(
[]dummyEvents{&goToFin{}},
),
}),
}, nil

Expand Down Expand Up @@ -252,6 +254,9 @@ func TestStateMachineTerminateCleanup(t *testing.T) {
stateMachine.Start()
defer stateMachine.Stop()

stateSub := stateMachine.RegisterStateEvents()
defer stateMachine.RemoveStateSub(stateSub)

// Now we'll send in a new dummy event to trigger out state machine.
// We'll have the ProcessEvent method take is to our terminal state.
//
Expand All @@ -260,6 +265,11 @@ func TestStateMachineTerminateCleanup(t *testing.T) {
env.On("CleanUp").Return(nil)
stateMachine.SendEvent(&goToFin{})

expectedStates := []State[dummyEvents, *dummyEnv]{
&dummyStateStart{}, &dummyStateFin{},
}
assertStateTransitions(t, stateSub, expectedStates)

// The state machine should now also be on the final terminal state.
assertState[dummyEvents, *dummyEnv](t, &stateMachine, &dummyStateFin{})

Expand Down

0 comments on commit fa12732

Please sign in to comment.