pytransitions / transitions

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

Method remove_model doesn't work properly when using the HierarchicalAsyncGraphMachine extension. #526

Closed Plazas87 closed 3 years ago

Plazas87 commented 3 years ago

When I removed a model from a machine, using the standard Machine form transitions, everything goes just well.

from transitions import Machine

class Matter():
    pass

lump1 = Matter()
lump2 = Matter()

# The states
states=['solid', 'liquid', 'gas', 'plasma']

# And some transitions between states. We're lazy, so we'll leave out
# the inverse phase transitions (freezing, condensation, etc.).
transitions = [
    { 'trigger': 'melt', 'source': 'solid', 'dest': 'liquid' },
    { 'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas' },
    { 'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas' },
    { 'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma' }
]

# setting 'model' to None or passing an empty list will initialize the machine without a model
machine = Machine(model=None, states=states, transitions=transitions, initial='solid')

machine.add_model(lump1)
machine.add_model(lump2, initial='liquid')

machine.remove_model([lump1, lump2])
del lump1  # lump1 is garbage collected
del lump2  # lump2 is garbage collected
>>> machine.models
# [] 

But, when trying to remove a model from a Machine, using a HierarchicalAsyncGraphMachine, the method .remove_model([lump1, lump2]) does not work properly.

from transitions.extensions import HierarchicalAsyncGraphMachine as Machine

class Matter():
    pass

lump1 = Matter()
lump2 = Matter()

# The states
states=['solid', 'liquid', 'gas', 'plasma']

# And some transitions between states. We're lazy, so we'll leave out
# the inverse phase transitions (freezing, condensation, etc.).
transitions = [
    { 'trigger': 'melt', 'source': 'solid', 'dest': 'liquid' },
    { 'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas' },
    { 'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas' },
    { 'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma' }
]

# setting 'model' to None or passing an empty list will initialize the machine without a model
machine = Machine(model=None, states=states, transitions=transitions, initial='solid')

machine.add_model(lump1)
machine.add_model(lump2, initial='liquid')

machine.remove_model([lump1, lump2])
del lump1  # lump1 is garbage collected
del lump2  # lump2 is garbage collected

This is the error, the models remain attached to the machine after executing the remove_model method.

>>> machine.models
# [<__main__.Matter object at 0x00000223148B38E0>, <__main__.Matter object at 0x00000223147D52B0>]

It seems to be related to the HierarchicalAsyncGraphMachine extension.

I'm going to try to find a solution for my implementation but, If you can give me some guidance I really appreciated it.

aleneum commented 3 years ago

Hello @Plazas87,

thank you for the report. During #492, the AsyncMachine remove model had been (falsely) overridden. Unfortunately, none of the already 2300+ tests covered that :/. Well, now it's one more.

Plazas87 commented 3 years ago

Hello, @aleneum

Great!! Thank you so much for your help!