Advice on debugging stack size problems? (Concur React related)

Hi all,

I’m building a frontend application using Purescript Concur React and I’ve run into a stack overflow, apparently somewhere deep in the guts of the Concur/React layers. The stack trace (below) from the browser is fairly opaque (to me). Are there tools or techniques people have used in the past to diagnose what is causing these issues?

To provide a bit more color, my code looks okay, but I am creating many nodes, some fairly deeply nested. In particular, I am rendering a widget for each item in a list, and this error only occurs when the list gets above a certain length.

Uncaught RangeError: Maximum call stack size exceeded
    at Object.createElement (react.development.js:727)
    at Object.createElementWithValidation [as createElement] (react.development.js:1792)
    at foreign.js:140
    at DOM.purs:21
    at DOM.purs:21
    at __do (Core.purs:47)
    at foreign.js:12
    at Left.value0 (foreign.js:12)
    at __do (Types.purs:84)
    at foreign.js:12

I don’t have a solution to this issue, but @ajnsit might be able to help out.

@fros1y, if you share a minimal example that reproduces this issue, we’ll have an easier time figuring out how to fix it.

Thanks @milesfrain! This has been a really frustrating stumble, so I appreciate your help!

I can’t share the code, but worked up an example that seems to be minimal: https://gist.github.com/fros1y/09bf2efabe49d880f0a9c022f25d2706

Playing with the x and y variables, I can pretty quickly figure out the limits on Chrome and Firefox (higher) and cause the stack overflow to occur. I tested it on TryPurescript as well to make sure it wasn’t something in my local versions/build – same result. To the extent it is relevant, I’m using a Linux build of Chrome, Version 84.0.4147.135 (Official Build) (64-bit)

1 Like

Actually, I can boil it down to something even smaller that triggers the error for n > 9000:

testCase :: forall a. Int -> Widget HTML a
testCase n = D.div' $ (\x -> D.text $ (show x) <> "\t") <$> (A.range 0 n) 

Full script including this testCase is in the gist above.

Great reproducible example!
The magic number for me in TryPurescript with Firefox + Linux is 7130.
Unfortunately, I’m not enough of an expert in Concur nor React to identify what’s going wrong.

Thanks. Glad(?) you saw similar behavior. I opened a ticket in the repo. I just noticed, though, that the official SlowButton example (https://github.com/purescript-concur/purescript-concur-react/blob/master/examples/src/Test/SlowButtonList.purs) has the same behavior for large numbers. It’s not so much SlowButton as ExplodeButton…

Unfortunately, the “solution” of mkLeafNode proposed in that example kind of eats the advantage of Concur. So I hope this isn’t some fundamental limitation.

Disclaimer: I am not familiar with Concur.
As far as I can tell A.range and Array’s map don’t have any stack-related issues like foldr. Their implementations use FFI to create one new Array with the changes in it.

Is there an issue with D.div'? I doubt D.text would be it.

1 Like

If the implementation is using an argument spread for React children (fn.apply(...) in JS), then it’s probably too many arguments to fit on the stack. I had this problem recently at work.

4 Likes

I think this might be it; it looks like concur-react delegates to purescript-react, whose div implementation is based on this createElement:

1 Like

That definitely seems to be the culprit, where @hdgarood found it in purescript-react.

This is getting a bit more afield, but can I ask how you replaced that call to apply?

I’m not a React expert, at all, and it looks like the alternative call to createElement directly would run into the same problem with the […children] parameter.

I’m not certain but I think React.createElement(class_, props, children) should have the same effect, although it might generate more warnings about elements not having keys.

In purescript-react, you would want to use the Dynamic DOM modules. These pass through children as a single array, but as @hdgarrood mentioned, you will get warnings from React if they are not keyed.

2 Likes