Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: xstate@5 beta It's not possible to rehydrate a nested state correctly #4141

Closed
RainerAtSpirit opened this issue Jul 12, 2023 · 6 comments

Comments

@RainerAtSpirit
Copy link

Description

I added some tests to the example that shows working with persisting states. This example is not working as expected atm.
#4102

examples/deep-persistence-with-restore/rehydrationIssue.test.ts

There's one failing test that uses the correct persisted state, but it runs into a timesout.

 it('when using with the correct persistedState actorRef should start with failing {Up: "Step2"} state and stop in final {Up: "Done"}', async () => {
    const workingActors = await createActors(runServiceMachine, 0);

    //const persistedState = JSON.parse(history[2])
    console.log(history);

    // currentPersistedState has the correct value for "src":"fromPromise2". This breaks the test with a timeout.
    // by replacing  "src":"fromPromise2" with "src":"Whatever" it will start working as expected
    const currentPersistedState = JSON.parse(
      '{"value":{"Up":"Step2"},"done":false,"context":{},"historyValue":{},"_internalQueue":[],"children":{"step2":{"state":{"value":"running","done":false,"context":{"errorCount":0,"threshold":3},"historyValue":{},"_internalQueue":[],"children":{"upDownService":{"state":{"status":"active"},"src":"upDownService"}}},"src":"fromPromise2"}}}'
    );
    console.log(currentPersistedState);
    expect(currentPersistedState.value).toStrictEqual({ Up: 'Step2' });

    const actorRef = interpret(
      parentMachine.provide({
        actors: workingActors
      }),
      { state: currentPersistedState }
    );

    actorRef.subscribe({
      next: (state) => {
        console.log('---next', state.value);
      },
      complete: () => {
        console.log('---complete');
      },
      error: (error) => {
        expect(error.message).toBe(
          'Cannot stop child actor step2 of x:0 because it is not a child'
        );
      }
    });

    actorRef.start();

    const stateStep2 = waitFor(actorRef, (s) => s.matches({ Up: 'Step2' }));
    expect((await stateStep2).value).toStrictEqual({ Up: 'Step2' });

    const stateDone = waitFor(actorRef, (s) => s.matches({ Up: 'Done' }));
    return expect((await stateDone).value).toStrictEqual({ Up: 'Done' });
    // actorRef.stop()
  });

What was unexpected that if you change the valid src in the test with something invalid, the test will work as expected.

Looks like something in the rehydration process is broken.

Expected result

rehydration should work with nested state

Actual result

rehydration does not work with nested state

Reproduction

examples/deep-persistence-with-restore/rehydrationIssue.test.ts

Additional context

No response

@murrowblue22
Copy link

murrowblue22 commented Jul 14, 2023

do you think this would be the same as not being able to restore a machine using xstate/react library. pam_service from below does not go to a running state nor does it restart already spawned actors running on the parent

const [state, send, pam_service] = useActor(parentAppMachine, {
        state: restoredState
 })

the documentation says this functionality is available or it it theoretical or still in progress
"In XState v5 beta, actors are now deeply (recursively) persisted. Invoked/spawned actors will be persisted, as well as actors invoked/spawned from those actors, and so on.

In the following example, the state of the mainActor will be persisted, as well as the state of the invoked someCounter actor. When the restoredActor is started, it will start at the persisted state of mainActor, which includes the persisted state of someCounter:"

@Andarist
Copy link
Member

I believe that this particular issue has been solved by #4330 . If you could confirm that the whole thing matches your expectations right now - it would be greatly appreciated

@RainerAtSpirit
Copy link
Author

@Andarist Sorry for not coming back earlier. The tests have now two failing tests, which is better than before, because I didn't expect the second test to pass. But it still runs into a timeout.

However, when running main.ts it now returns an Error: An inline child actor cannot be persisted.. I'm just not sure if I should change the code somewhat or if there's an issue with the way the machine, submachine, actor gets persisted.

13:34 $ yarn start
yarn run v1.22.15
$ vite-node --script ./main.ts
Expected to fail, second service is not available
        Building it up
---next Init
---next { Up: 'Step1 system1' }
No history states found.
---rs:  done up-step1-system1
---next { Up: 'Step2 system1' }
---rs: error up-step2-system1
---rs: error up-step2-system1
---rs: error up-step2-system1
---rs: error up-step2-system1
---rs:  notDone up-step2-system1
---next NotDone
---complete
Event "xstate.done.actor.up-step2-system1" was sent to stopped actor "x:0 (x:0)". This actor has already reached its final state, and will not transition.
Event: {"type":"xstate.done.actor.up-step2-system1"}
a little later, back to normal. Pick up where we left
{
  value: { Up: 'Step2 system1' },
  status: 'active',
  historyValue: {},
  _internalQueue: [],
  context: {},
  children: { 'up-step2-system1': { state: [Object], src: 'up-step2-system1' } }
}
        Resuming failed step
---next { Up: 'Step2 system1' }
---next { Up: 'Step2 system1' }
/home/rainer/github/xstate/examples/deep-persistence-with-restore/node_modules/.pnpm/[email protected]/node_modules/xstate/dist/interpreter-5c4e6634.development.esm.js:145
    throw err;
    ^

Error: An inline child actor cannot be persisted.
    at Module.getPersistedState (/home/rainer/github/xstate/examples/deep-persistence-with-restore/node_modules/.pnpm/[email protected]/node_modules/xstate/dist/raise-cdcdf834.development.esm.js:1426:13)
    at StateMachine.getPersistedState (/home/rainer/github/xstate/examples/deep-persistence-with-restore/node_modules/.pnpm/[email protected]/node_modules/xstate/dist/xstate.development.esm.js:593:12)
    at Actor.getPersistedState (/home/rainer/github/xstate/examples/deep-persistence-with-restore/node_modules/.pnpm/[email protected]/node_modules/xstate/dist/interpreter-5c4e6634.development.esm.js:764:42)
    at Object.next (/home/rainer/github/xstate/examples/deep-persistence-with-restore/main.ts:41:37)
    at Actor.update (/home/rainer/github/xstate/examples/deep-persistence-with-restore/node_modules/.pnpm/[email protected]/node_modules/xstate/dist/interpreter-5c4e6634.development.esm.js:494:24)
    at Actor.start (/home/rainer/github/xstate/examples/deep-persistence-with-restore/node_modules/.pnpm/[email protected]/node_modules/xstate/dist/interpreter-5c4e6634.development.esm.js:586:10)
    at builtIt (/home/rainer/github/xstate/examples/deep-persistence-with-restore/main.ts:62:21)
    at /home/rainer/github/xstate/examples/deep-persistence-with-restore/main.ts:81:7
    at ViteNodeRunner.directRequest (file:///home/rainer/github/xstate/examples/deep-persistence-with-restore/node_modules/.pnpm/[email protected]/node_modules/vite-node/dist/client.mjs:328:5)
    at ViteNodeRunner.cachedRequest (file:///home/rainer/github/xstate/examples/deep-persistence-with-restore/node_modules/.pnpm/[email protected]/node_modules/vite-node/dist/client.mjs:186:14)

Node.js v18.16.0

@RainerAtSpirit
Copy link
Author

@Andarist After upgrading to xstate@5, this example is now working as expected.

13:06 $ npm run start

> [email protected] start
> vite-node --script ./main.ts

Expected to fail, second service is not available
        Building it up
---next Init
---next { Up: 'Step1 system1' }
No history states found.
---rs:  done up-step1-system1
---next { Up: 'Step2 system1' }
---rs: error up-step2-system1
---rs: error up-step2-system1
---rs: error up-step2-system1
---rs: error up-step2-system1
---rs:  notDone up-step2-system1
---next NotDone
---complete
Event "xstate.done.actor.up-step2-system1" was sent to stopped actor "x:0 (x:0)". This actor has already reached its final state, and will not transition.
Event: {"type":"xstate.done.actor.up-step2-system1"}
a little later, back to normal. Pick up where we left
{
  status: 'active',
  value: { Up: 'Step2 system1' },
  historyValue: {},
  context: {},
  children: {
    'up-step2-system1': {
      snapshot: [Object],
      src: 'up-step2-system1',
      syncSnapshot: false
    }
  }
}
        Resuming failed step
---next { Up: 'Step2 system1' }
---next { Up: 'Step2 system1' }
---rs:  done up-step2-system1
---next { Up: 'Step3 system2' }
---rs:  done up-step3-system2
---next { Up: 'Step4 System1' }
---rs:  done up-step4-system1
---next { Up: 'Step5 system3' }
---rs:  done up-step5-system3
---next { Up: 'Done' }

@Andarist
Copy link
Member

Andarist commented Dec 4, 2023

@RainerAtSpirit oh, that's really good to know! Thanks for re-checking. I meant to get to your report sooner but we were quite busy with the whole v5 release and it just slipped through the cracks. It would probably still be good to recheck this example here and convert it to a test case if we don't have one like it yet.

@RainerAtSpirit
Copy link
Author

@Andarist: No worries. Glad that this is working with v5 #4102, so that I can start refactoring it for production . Keep up the good work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants