qmuntal / stateless

Go library for creating finite state machines
BSD 2-Clause "Simplified" License
942 stars 49 forks source link

Provide API to return Is state machine has queued event #29

Closed okhowang closed 3 years ago

okhowang commented 3 years ago

I write a wrapper for stateless.StateMachine. for run some function after trigger succeed.

func (sm *State Machine) FireCtx(ctx context.Context, trigger stateless.Trigger, args ...interface{}) error {
    if v, ok := ctx.Value(deferRunnerKey{}).(*deferRunner); !ok || v == nil {
        ctx = context.WithValue(ctx, deferRunnerKey{}, &deferRunner{})
    }
    err := sm.StateMachine.FireCtx(ctx, trigger, args...)
    if err == nil { // one more condition if trigger queued, defer function shouldn't be call
        defers := ctx.Value(deferRunnerKey{}).(*deferRunner) // nolint:errcheck
        for _, task := range defers.tasks {
            task(ctx)
        }
    }
    return err
}

but there is a bug when call Fire in StateMachine

func TestDeferTwice(t *testing.T) {
    counter := 0
    sm := &StateMachine{stateless.NewStateMachine("A")}
    sm.Configure("A").Permit("toB", "B").
        OnExit(func(ctx context.Context, i ...interface{}) error {
            addDeferRunner(ctx, func(ctx context.Context) {
                counter++
            })
            return nil
        })
    sm.Configure("B").Permit("toC", "C").
        OnEntry(func(ctx context.Context, i ...interface{}) error {
            return sm.FireCtx(ctx, "toC")
        }).
        OnExit(func(ctx context.Context, i ...interface{}) error {
            addDeferRunner(ctx, func(ctx context.Context) {
                counter++
            })
            return nil
        })
    assert.Equal(t, 0, counter)
    assert.NoError(t, sm.Fire("toB"))
    assert.Equal(t, "C", sm.MustState())
    assert.Equal(t, 2, counter) // error here 3 vs 2
}

but there is no api to get that is there any event pendding.

Can we provide a Firing() bool for StateMachine to indicate is StateMachine firing now?