protofsm: allow multiple internal events to be emitted

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.
This commit is contained in:
Olaoluwa Osuntokun 2024-03-04 23:49:11 -06:00
parent 8816b357ef
commit 06c691e753
3 changed files with 34 additions and 23 deletions

View File

@ -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

View File

@ -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.
@ -339,9 +339,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)
}),
@ -465,7 +465,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.
@ -535,12 +539,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
@ -598,18 +596,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

View File

@ -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
@ -246,6 +248,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.
//
@ -254,6 +259,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{})