pytransitions / transitions

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

HierarchicalMachine on_enter call to a state transition results in infinite loop. #548

Closed wes-public-apps closed 2 years ago

wes-public-apps commented 2 years ago

Thank you for taking the time to report a bug! Your support is essential for the maintenance of this project. Please fill out the following fields to ease bug hunting and resolving this issue as soon as possible:

Describe the bug First off I appreciate the work you all have done with this project. I found the documentation and usage overall very reasonable.

Now the issue: (this could be misusage of the tool on my part) When using a queued Hierarchical State Machine, I added an on_enter hook to conditionally transition into a default state. So basically the on_enter callback calls a transition method. This results in an infinite loop. My understanding is that the point of the queued state machine is that transitions will only occur after a previous one exited alleviating any concerns for an infinite loop.

In this part of the documentation the following line suggests transitions from inside on_enter are supported. machine.on_enter_B(go_to_C)

My issue is similar to the following: https://stackoverflow.com/questions/69043203/python-transitions-library-calling-transition-from-inside-callbacks

Conditional transitions could be made to work but I have many events that can result in the state in question being entered and that state has many possible default sub states that look at multiple state values. This results in an absurd amount of transitions. When using the on_enter hooks the code is very compact, readable, and easier to maintain in the future.

Minimal working example

from transitions.extensions.nesting import HierarchicalMachine

default_ca = True

class MachineModel:

    def enter_c(self):
        if default_ca:
            self.to_b_c_ca()
            return
        else:
            self.to_b_c_cb()
            return

STATE_MACHINE = MachineModel()

states = [
    'a',
    {'name': 'b', 'children': [
        {'name': 'c', 'on_enter': 'enter_c', 'children': ['ca', 'cb']},
        'd'
    ]},
]

_sm = HierarchicalMachine(model=STATE_MACHINE, states=states, queued=True, initial='a')
STATE_MACHINE.to_b()
STATE_MACHINE.to_b_c()  # line that causes infinite loop
print(STATE_MACHINE.state)

Expected behavior I expected the current state to become b_c_ca.

Additional context The main goal is to enter a parent state, look at system signals for information to then conditionally transition into the correct default sub state in the sub state machine. I accept that I am possibly misusing the library.

aleneum commented 2 years ago

hello @wes-public-apps,

thank you for the bug report with that great MRE. I can confirm this is a bug. b_c should not be exited in this example. d1c11f9 should fix it (don't mind the cross, coverage isn't reported correctly). Could you try to install transitions from master and check whether this solves your issue.

wes-public-apps commented 2 years ago

Awesome! The changes on master absolutely fixed my issues.

Can you speak to how long before I can see this fix rolled out to the pypi repository? I do not know much about that process.

aleneum commented 2 years ago

Can you speak to how long before I can see this fix rolled out to the pypi repository

I will prepare a release on Friday or Monday.

aleneum commented 2 years ago

0.8.10 has been published and can be downloaded from PyPI. I will close this issue. Feel free to comment if your issue hasnt been solved. I will reopen the issue if necessary.