-
Notifications
You must be signed in to change notification settings - Fork 15
Wire3 decoder pipeline causes 'too much recursion' on Firefox #83
Description
Bug
Lamdera apps with large FrontendModel records crash on Firefox with:
Uncaught internal error: too much recursion
The app works fine on Chrome and works locally (lamdera live) but fails in production (lamdera deploy).
Root Cause
In extra/Lamdera/Wire3/Decoder.hs (line 459), decodeRecord generates a decoder pipeline using foldlPairs (|>):
succeedDecode (\a b c ... -> { field1 = a, field2 = b, ... })
|> andMapDecode decoder1
|> andMapDecode decoder2
...
|> andMapDecode decoderNEach |> compiles to a nested function call in JS. For apps with deeply nested records (e.g. a FrontendModel containing OwnerData containing forms, lists, etc.), the total chain can reach 380+ levels of nesting.
In the compiled production JS, this becomes:
i(X, i(Y, i(Z, ... i(W, u(fn)) ...)))
// 380+ nested callsFirefox's call stack limit is ~5000 frames. With ~12-15 frames per decoder step, 380 steps × ~13 frames ≈ ~5000 frames → stack overflow on Firefox.
Chrome has a higher stack limit (~8800+) so it works there.
How to Reproduce
- Create a Lamdera app with a
FrontendModelthat has many fields (including nested records with many fields) - Deploy to production (
lamdera deploy) - Open the deployed app in Firefox → "too much recursion" error
- Same app works in Chrome and locally with
lamdera live(non-optimized build)
Evidence
Analysis of the compiled production JS (frontend.*.js):
- Global max parenthesis nesting depth: 218 at the decoder chain
- 380 consecutive
i()calls (the compiledandMapDecodepipeline) - The
ifunction isfunction(r,t){ return A4(function(n){ return L$(n,r) }, t) }— each call adds to the stack
Suggested Fix
In Decoder.hs, chunk the foldlPairs into groups to limit nesting depth:
Instead of:
foldlPairs (|>) -- creates a single chain of N stepsSplit into batches of ~50 fields, decode each batch separately, then compose the results. This would cap the nesting depth regardless of record size.
Alternatively, the encoder (Encoder.hs line 237-254) already avoids this issue by using a flat list:
encodeSequenceWithoutLength $ list fieldEncodersA similar flat approach for the decoder would eliminate the problem entirely.
Environment
- Firefox (any version — all have stricter stack limits than Chrome)
- Lamdera production builds (optimized + minified JS)
- App with ~100+ total fields across nested FrontendModel records