Skip to content

Commit

Permalink
Unregister systemIds eagerly (#4405)
Browse files Browse the repository at this point in the history
* Unregister `systemId`s eagerly

* fixed test title
  • Loading branch information
Andarist committed Oct 26, 2023
1 parent bd22b14 commit a01169e
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/green-fishes-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': patch
---

Fixed crash on a `systemId` synchronous re-registration attempt that could happen, for example, when dealing with reentering transitions.
6 changes: 6 additions & 0 deletions packages/core/src/actions/stop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ function executeStop(
if (!actorRef) {
return;
}

// we need to eagerly unregister it here so a new actor with the same systemId can be registered immediately
// since we defer actual stopping of the actor but we don't defer actor creations (and we can't do that)
// this could throw on `systemId` collision, for example, when dealing with reentering transitions
actorContext.system._unregister(actorRef);

// this allows us to prevent an actor from being started if it gets stopped within the same macrostep
// this can happen, for example, when the invoking state is being exited immediately by an always transition
if (actorRef.status !== ActorStatus.Running) {
Expand Down
45 changes: 45 additions & 0 deletions packages/core/test/system.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,4 +424,49 @@ describe('system', () => {

expect(actor.system.get('test')).toBeDefined();
});

it('should gracefully handle re-registration of a `systemId` during a reentering transition', () => {
const spy = jest.fn();

let counter = 0;

const machine = createMachine({
initial: 'listening',
states: {
listening: {
invoke: {
systemId: 'listener',
src: fromCallback(({ receive }) => {
const localId = counter++;

receive((event) => {
spy(localId, event);
});

return () => {};
})
}
}
},
on: {
RESTART: {
target: '.listening'
}
}
});

const actorRef = createActor(machine).start();

actorRef.send({ type: 'RESTART' });
actorRef.system.get('listener')!.send({ type: 'a' });

expect(spy.mock.calls).toEqual([
[
1,
{
type: 'a'
}
]
]);
});
});

0 comments on commit a01169e

Please sign in to comment.