xstate: Bug: Memory leak appeared between xstate 4.32.1 and xstate 4.33.4

Description

We’ve recently had a memory leak in a Home Assistant addon which uses xstate under the hood (as a transitive dependency). It took a while to narrow this down to xstate (or using it), since there are two similar addons, using the exact same library with the exact same version, and only one of those exhibited a quite drastic memory leak (in the order of 100MB/h), while the other one is perfectly stable: https://github.com/home-assistant/core/issues/77767

It turns out that the difference between those installations was how the dependencies got installed. The non-leaking one uses yarn install --immutable, resulting in an install of xstate@4.32.1 from the lockfile - while the leaking one uses npm install, which installed the latest version at the time. The leak appeared starting with xstate@4.33.4 and persists with 4.33.6 - unfortunately I don’t know which versions between those two are also affected. After pinning the xstate version in the leaking addon, the leak went away.

Sadly, I cannot provide a reproduction, since only a handful of users are affected and I haven’t been able to reproduce myself. I haven’t seen anything in the changelog that would suggest a change in behavior - like actor references not getting cleaned up or anything like that. I would be grateful for some help with tracking this down though, since I’m afraid this is going to prevent us from upgrading xstate indefinitely.

Expected result

No memory leak

Actual result

Memory leak

Reproduction

Not possible unfortunately, see description

Additional context

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 1
  • Comments: 20 (9 by maintainers)

Most upvoted comments

@Andarist I was able to get a minimal reproduction setup for this issue: https://github.com/beejunk/xstate-memory-leak

Details are in the README.md file; let me know if you need any additional info. If debugging over a call is helpful, I could probably make that work but not until next weekend.

Hope this helps!

Ran into this issue this week. Deployed a Node app that uses XState, and as soon as it faced production traffic we started seeing signs of a memory leak. This deploy included an upgrade of XState from 4.31.0 to 4.35.0. Took us a while to get an idea of what was going on, and seeing this issue confirmed our theory. We downgraded XState to 4.31.0, redeployed and the issue was no longer there.

While we were investigating the issue, we analyzed a core dump from the app and discovered that a massive number of State and StateNode objects were in memory. Following this, I was able to run the app locally, hit it with bit of traffic using K6, and analyze the memory using the Chrome dev-tools heap profiler. For v. 4.35.0 of XState, this also revealed State and StateNode objects that weren’t being grabage-collected. When analyzing with XState v. 4.31.0, these objects did not appear on the profile at all (so I am assuming they were effectively GC’d).

The machine in question does use parallel child machines. Additionally, the version of the app that exhibited the memory leak issue was using the predictableActionArguments option set to true for each machine.

If I have time this weekend, I’ll see if I can get together a minimal reproduction.

@beejunk thank you! this was very helpful. Thanks to the repro I located the problem really quickly - the PR with a fix is up here: https://github.com/statelyai/xstate/pull/3801

The library often creates child machines, maybe something changed in their handling, so a reference is kept around under specific circumstances now?

That’s a good place for us to look. It’s either that or some code introduced with the predictableActionArguments flag, I’d think.