statelyai / xstate

Actor-based state management & orchestration for complex app logic.
https://stately.ai/docs
MIT License
26.51k stars 1.22k forks source link

Bug: reenter/always transitions do not get executed when state is restored #4627

Open florian-lefebvre opened 6 months ago

florian-lefebvre commented 6 months ago

XState version

XState version 5

Description

If a machine has a state with reenter + always, after restarting the machine with the persisted state, the transition will not be re-executed.

Expected result

I expect the transition to keep looping with some restored state.

Actual result

The transition is never taken again.

Reproduction

https://stately.ai/registry/editor/d7e28966-d655-4b64-9312-a2b5aea2d0c1?machineId=06480795-85c4-42fc-967b-443fa349f204

Additional context

No response

Andarist commented 6 months ago

This is expected - a state machine can only change its state as a result of the transition. Restoring a state is not a transition. Think of it as "pause"+"resume". We resume with whatever you have restored and we wait for new transitions to happen. Otherwise, you'd risk re-executing the same actions (some could have already been executed before you paused). On top of that, actions might depend on event and when you resume no event is received so we don't have any event value to call your actions with.

florian-lefebvre commented 6 months ago

So what would be the plan here then? Event-sourcing? https://stately.ai/docs/persistence#event-sourcing

Andarist commented 6 months ago

Could you tell me what problem are you facing?

davidkpiano commented 6 months ago

So what would be the plan here then? Event-sourcing? https://stately.ai/docs/persistence#event-sourcing

Yes, the solution would be replaying the events to get to the state if it is necessary to re-execute the actions.

florian-lefebvre commented 6 months ago

Sorry @davidkpiano, I was on vacation so I couldn't answer! I don't agree with the resolution of the issue (probably an understanding issue on my end but still). So let me reexplain what issue I'm facing and why I think it should not happen.

How the machine is designed

My machine has some logic (it does not matter what) and in a specific state (let's call it playing), it has an always reenter self-transition with a delay of 1s. It basically acts as a timer in this specific state.

So when the machine is in the playing state, the timer context variable is incremented by 1 every second.

What happens with persistence

On subscribe, I persist the machine using getPersistedSnapshot. When I restore the state using snapshot, the machine is in the right state (playing) but the always reenter self-transition is not taken.

What I expected

I'd expect this transition to be taken straight away because I don't think it should rely on another event to be triggered. Being in the playing state should be enough.

Alternatives

There's event-sourcing as you said and I may take this route anyway, but I think something can be fixed here.

davidkpiano commented 6 months ago

Ah okay @florian-lefebvre - this would be related to persisting and restoring timer, which we are still working on solving.

florian-lefebvre commented 6 months ago

Just to be clear, the context is persisted well, just not updated through the transition. Thanks for re-opening so quickly!

abizek commented 3 months ago

I faced the same issue for a timer state machine I wanted to persist and restore. I tried event sourcing but the context depended on Date.now(). I don't know if there is a way around it and I didn't want to go in that direction.

I settled on a workaround with an event with a guard to the state where the reentering self-transition is set up to "jumpstart" the machine whenever it is restored. This is a very hacky way to fix it but maybe ¯\_(ツ)_/¯ this can help you @florian-lefebvre.

State machine link: https://stately.ai/registry/editor/350b30e8-7251-4b58-bd6c-670bfee8af31?mode=Design&machineId=04d2084b-bb49-4e65-b68e-3db3ac7c40ae

image