xstate
xstate copied to clipboard
Performance issue with xstate inspector + rxjs Subject + fast-safe-stringify
Description
I've somehow managed to create a state that takes 11s to send to the interpreter. In looking into the issue, I've discovered that the fast-safe-stringify stringify method's runtime is somewhat linear to the complexity of a state chart under certain circumstances.
The offender appears to be when you invoke an rxjs Subject. The resulting call to stringify calls decirc a large amount of times. The amount of times appears to be directly correlated to the complexity of the state chart in its entirety.
The following is a simple reproduction case.
import { inspect } from "@xstate/inspect";
import { useService } from "@xstate/react";
import { default as React } from 'react';
import { Subject } from "rxjs";
import { map } from "rxjs/operators";
import { createMachine, interpret } from "xstate";
import './App.css';
inspect({
iframe: false
});
const child = createMachine({
id: "child",
initial: "init",
states: {
init: {
invoke: {
src: ({subject}:any) => subject.pipe(map((data) => ({ type: "RECV", data })))
}
}
}
}
);
const parent = createMachine(
{
id: "machine",
initial: "ready",
context: { subject: new Subject() },
states: {
ready: {
invoke: {
src: child,
data: {
subject: ({subject}: any) => subject,
}
}
},
}
}
);
const service = interpret(parent, { devTools: true })
service.start()
function App() {
const [current, send] = useService(service);
return <React.Fragment>:(</React.Fragment>;
}
export default App;
This produces a somewhat mild 3.99ms stringify call.

If we add 20 arbitrary sibling states, we see the stringify call nearly double to 7.68ms.

Now if we look at the states for my somewhat more complex application which includes services, actions, guards etc. we can see it's blown out to over 11,000ms.

I haven't investigated this any deeper to determine whether this is affected more by having many services, actions etc. or whether it's simply linear with the number of states.
My temporary solution is to hack the Subject with a toJSON method.
subject.toJSON = () => "[Rxjs Subject]
I'm not actually sure if this is a "problem" per se, but I figured it's worth bringing it up as it's caused no small amount of headaches.
Expected Result
Not to wait 11s for the state to load.
Actual Result
The state takes 11s to load.
Reproduction
See above example.
Additional context
I was able to reproduce this but found the issue isn't related to the Subject but having a complex object stored in context. I believe the inspector is trying to stringify the whole object to be able to show it in the state panel.
This seems like something that https://github.com/statelyai/xstate/pull/2640 will fix, once we determine a proper solution.
Had this issue while implementing a websocket solution built on top of the phoenix-js library. We store the Socket and Channel classes in context and they take around 4 seconds each to stringify.
It would be great if we could opt out of stringifying large objects. Maybe an ignore-list for context names in the inspector options or better yet, a regex ignore-list
You can avoid serializing massive objects like this:
inspect({
serialize: (_key, value) => {
if (isSocket(value)) {
return '[redacted Socket]';
}
if (isChannel(value)) {
return '[redacted Channel]';
}
return value;
}
})
Closing as stale – if there are issues with the new inspector, please file them here: https://github.com/statelyai/inspect/issues/new