I have a fairly complex application involving a GUI front-end and several other classes, several of which are state machines based on the excellent pytransitions library. The application hangs at various times, and being multi-threaded it is difficult to debug, but I have reproduced a minimal example here:
from transitions import Machine
import time
States = ["OFF", "ON"]
Transitions = [{"trigger": "PowerUp", "source": "OFF", "dest": "ON"}]
class GUI:
def __init__(self):
self.machine1 = Machine_1(self)
self.machine2 = Machine_2(self)
def on_button_pressed(self):
self.machine2.trigger("PowerUp")
self.machine1.trigger("PowerUp")
class Machine_1:
def __init__(self, parent):
self.parent = parent
self.fsm = Machine(model=self, states=States, transitions=Transitions, initial="OFF")
def on_enter_ON(self):
print("machine1 is on!")
self.parent.machine2.signal = True
class Machine_2:
def __init__(self, parent):
self.parent = parent
self.fsm = Machine(model=self, states=States, transitions=Transitions, initial="OFF")
self.signal = False
def on_enter_ON(self):
print("machine2 waits for machine1 ...")
while self.signal is False:
time.sleep(0.1)
print("machine2 on!")
i.e. When machine1 is "turned on", it sends a signal to machine2. machine2 must wait for this signal before executing its logic for the "on" state.
Using the Python REPL as the main loop:
>>> import test
>>> gui = test.GUI()
>>> gui.machine1.state
'off'
>>> gui.machine2.state
'off'
>>> gui.on_button_pressed()
machine2 waits for machine1 ...
(never returns)
machine2 is stuck in its while loop, waiting for the signal from machine1, which never arrives because machine1 is never triggered.
If I re-order the sequence of triggers in gui.on_button_pressed()
:
self.machine1.trigger("PowerUp")
self.machine2.trigger("PowerUp")
... everything works:
>>> gui.on_button_pressed()
machine1 is on!
machine2 waits for machine1 ...
machine2 is on!
What is going on here? Is the trigger()
method supposed to block? Is it documented somewhere, when does it return? Or am I using the on_enter_state
callbacks in an unsupported way? Is there a recommended pattern I should be using?