fgmacedo / python-statemachine

Python Finite State Machines made easy.
MIT License
854 stars 84 forks source link

Error when invoking send in sequence within on_enter_state synchronously #449

Closed robsonpiere closed 2 months ago

robsonpiere commented 2 months ago

Description

We have a scenario with automatic transitions, which are invoked within the on_enter_state method. This worked synchronously until version 2.2.

In version 2.3, with this type of transition, only the first one succeeds, and the second one fails. If we add an await, it works, but synchronously, the second transition is not completed.

What I Did

I'm adding a simple example to simulate the behavior, In this example we have two transitions occurring within the

stateDiagram-v2
    accTitle: Bug problem
    accDescr: Show fail in second transition
    direction LR

    classDef transitionFail fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow

    [*] --> initial

    state automatic {
        direction LR
        second--> third
        third --> fourth
    }

    initial --> automatic
    automatic-->final
    final --> [*]

    Class fourth transitionFail
from statemachine import StateMachine, State

class ExampleStateMachine(StateMachine):
    initial = State(initial=True)
    second = State()
    third = State()
    fourth = State()
    final = State(final=True)

    initial_to_second = initial.to(second)
    second_to_third = second.to(third)
    third_to_fourth = third.to(fourth)
    completion = fourth.to(final)

    def on_enter_state(self, event, ):
        print(f"Entering state {event}")
        if event == "initial_to_second":
            self.send("second_to_third")
        if event == "second_to_third":
            self.send("third_to_fourth") #incomplete transition in version 2.3
        if event == "third_to_fourth":
            print("third_to_fourth on on_enter_state worked")

if __name__ == "__main__":
    example = ExampleStateMachine()
    print(example.current_state)
    example.send("initial_to_second") # this will call second_to_third and third_to_fourth
    print("My current state is", example.current_state)

output in version 2.2:

Entering state __initial__
Initial
Entering state initial_to_second
Entering state second_to_third
Entering state third_to_fourth
third_to_fourth on on_enter_state worked
My current state is Fourth

Error in version 2.3:

Entering state __initial__
Initial
Entering state initial_to_second
Entering state second_to_third
My current state is Third
Task was destroyed but it is pending!
task: <Task pending name='Task-4' coro=<StateMachine.async_send() running at *venv/lib/python3.9/site-packages/statemachine/statemachine.py:375>>
3.9.18/lib/python3.9/asyncio/base_events.py:672: RuntimeWarning: coroutine 'StateMachine.async_send' was never awaited

Applying this change works on 2.3

    async def on_enter_state(self, event, ):
        print(f"Entering state {event}")
        if event == "initial_to_second":
            await self.send("second_to_third")
        if event == "second_to_third":
            await self.send("third_to_fourth")
        if event == "third_to_fourth":
            print("third_to_fourth on on_enter_state worked")