Skip to content

Wire3 decoder pipeline causes 'too much recursion' on Firefox #83

@CharlonTank

Description

@CharlonTank

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 decoderN

Each |> 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 calls

Firefox'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

  1. Create a Lamdera app with a FrontendModel that has many fields (including nested records with many fields)
  2. Deploy to production (lamdera deploy)
  3. Open the deployed app in Firefox → "too much recursion" error
  4. 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 compiled andMapDecode pipeline)
  • The i function is function(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 steps

Split 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 fieldEncoders

A 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions