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

Fixed an issue with exit actions being called in random order when stopping a machine #2903

Merged
merged 5 commits into from
Jan 11, 2022

Conversation

Andarist
Copy link
Member

No description provided.

@changeset-bot
Copy link

changeset-bot bot commented Dec 29, 2021

🦋 Changeset detected

Latest commit: 3352231

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
xstate Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@@ -2,4 +2,4 @@
'xstate': patch
---

Fixed an issue with some exit handlers being executed more than once when stopping a machine.
Fixed an issue with some exit actions being executed more than once when stopping a machine.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just tweaked this as usually we don't call those 'handlers" in the docs so this could be confusing for people

@ghost
Copy link

ghost commented Dec 29, 2021

CodeSee Review Map:

Review these changes using an interactive CodeSee Map

Review in an interactive map

View more CodeSee Maps

Legend

CodeSee Map Legend

}
});
[...this.state.configuration]
.sort((a, b) => a.order - b.order)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've considered sorting this when we create States:

this.configuration = config.configuration;

but this has resulted in some test failures. I think that all of them were either snapshot tests (so we could just update them) or were related to rehydration. When dealing with the latter it turns out that config.configuration can be undefined. Well, our tests say that this can be undefined but our types assume that this is a required property:
configuration: Array<StateNode<TContext, any, TEvent>>;

I could just default this to an empty array but this would also potentially cover up another problem. The problem is that state.configuration might be invalid/incomplete. I think this is "patched" for most cases within our resolveState:

public resolveState(
state: State<TContext, TEvent, any, any>
): State<TContext, TEvent, TStateSchema, TTypestate> {
const configuration = Array.from(
getConfiguration([], this.getStateNodes(state.value))
);
return new State({
...state,
value: this.resolve(state.value),
configuration,
done: isInFinalState(configuration, this),
tags: getTagsFromConfiguration(configuration)
});
}

I've added test cases for this as part of this PR here:
c4781eb

I've decided not to patch it and sort it in the constructor of the State for now though as that would incur the sorting cost for all created States (and we create them after each transition). By only sorting this here we make this operation "lazy" in a sense that it's only performed when it is actually needed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually... I've made a huge mistake at first and I've sorted those exit actions in the document order while this should be done in the reversed document order (just how it's done when sorting & calling exit actions based on the exitSet during regular transitions).

The exitOrder is defined here as follows:

exitOrder // Descendants precede ancestors, with reverse document order being used to break ties
(Note: since descendants follow ancestors, this is equivalent to reverse document order.)

So sorting this in the State's constructor wouldn't be good anyway because most likely a document order would be better suited there while we need the reversed document order here. But, of course, if that would be sorted in the documented order then we could just iterate in reverse rather than sorting it but the "lazy" argument still stands.

@@ -1354,96 +1475,6 @@ describe('choose', () => {

expect(service.state.context).toEqual({ answer: 42 });
});

// https://github.com/davidkpiano/xstate/issues/1109
it('exit actions should be called when invoked machine reaches final state', (done) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved those to the new describe blocks as those were nested in the "choose" block which didn't quite make sense for those tests.

expect(actual).toEqual(['root', 'a', 'a1']);
});

it('should call exit handlers in document order when the service gets stopped', () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only test added in this file. It's sort of basic and acts as a regression test here. Its implementation depends on the fact that transitions "mess up" the state.configuration order and this is an implementation detail - I think I'm going to add in a sec another, similar, test for parallel states, just in case (I know it will pass right now)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added those tests for parallel regions here:
6b5a57c
I've created 3 very similar tests but each one of them is "messing" up the state.configuration in a different order so I've decided to include all of those cases

@Andarist Andarist merged commit 56842cb into main Jan 11, 2022
@Andarist Andarist deleted the andarist/fix-exit-handlers-order branch January 11, 2022 11:25
@github-actions github-actions bot mentioned this pull request Jan 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants