pytransitions / transitions

A lightweight, object-oriented finite state machine implementation in Python with many extensions
MIT License
5.49k stars 524 forks source link

Add to documentation: when does a trigger() method return? #529

Closed banjaxedben closed 3 years ago

banjaxedben commented 3 years ago

I am using the trigger() method to execute transitions by name. I have found that the trigger() method does not return until the triggered on_enter_<state> callback completes. Referring to the callback execution order list here, where exactly does the trigger() method return?

aleneum commented 3 years ago

Hello @banjaxedben,

calling model.trigger("event") is equivalent to executing model.event(). Both methods will only return after all callbacks have returned. If you have defined a 'machine.finalize_event', this will be executed before model.trigger returns. I hope that the explanations added in 8186df0 improve the comprehension of this. I will close this issue for now as I consider this resolved. Feel free to comment anyway. I will reopen the issue if further actions need to be conducted.

banjaxedben commented 3 years ago

Thanks for the clarification. I think the fundamental problem I have is that it is not clear how best to integrate my business logic with transitions. See my StackOverflow question here.

The transitions library gives the impression of being very simple, but there are traps for the unwary! I thought I could simply put my business logic in the on_enter callbacks, but I eventually learned that this was a bad idea: I must not trigger a state change from within a callback, and the code that calls a trigger method must be prepared to wait. Browsing old issues like #18, #30, #36, I get the sense that others have had a similar learning process as me. Could you distill the collective learning from these issues into something in the README? I am talking from the point of view of an engineer with no formal software training, so examples involving thread locks, asyncio, or coroutines tend to go over my head.

aleneum commented 3 years ago

Hello @banjaxedben,

See my StackOverflow question here.

I just posted an answer. I hope this gives you some ideas about how to tackle that issue.

I must not trigger a state change from within a callback, and the code that calls a trigger method must be prepared to wait.

well, you can trigger a state change from within a callback. But callbacks need to return.

Could you distill the collective learning from these issues into something in the README?

I can give it a try but if you boil it down, you have a classic deadlock situation where pytransitions is not part of the actual problem.

This:

def Model1:

    success = False    

    def on_enter_FOO(self):
        success = True  

def Model2:

    def on_enter_FOO(self):
        while Model1.success is False:
            time.sleep(1)        

model1 = Model1()
model2 = Model2()
machine = Machine(model=[model1, model2], states=[''FOO'])
model2.to_FOO()
model1.to_FOO()

is a fancy way of writing:


success = False

def func1():
    success = True

def func2():
    while not success:
        time.sleep(1)

func2()
func1()

I am talking from the point of view of an engineer with no formal software training, so examples involving thread locks, asyncio, or coroutines tend to go over my head.

Don't worry, this is tough for everyone.

aleneum commented 3 years ago

I extended the example section of the FAQ (You have to scroll down to the bottom. Page anchors do not work on GitHub). Let me know if this is helpful for you.