Skip to content

Make composition iterative so max_depth stops needing a ceiling #37

Description

@elioseverojunior

Follow-up to #33, which stopped deep input from aborting but did not make it parse.

Where things stand

Composition is recursive — compose_nodecompose_node_from_eventcompose_sequence/compose_mappingcompose_node — one stack frame per nesting level. #33 capped the reachable depth at MAX_SAFE_DEPTH (192) so the overflow became a clean DepthLimitExceeded instead of an uncatchable abort.

That is the safety half. The capability half is unaddressed: glaucus cannot parse a document nested deeper than 192, whatever max_depth says.

Why the ceiling is so low

The measurement has a 24x spread, because stack frame size scales with optimisation level:

build stack overflows at
opt-level = 1 8 MiB (main thread) ~7,300
opt-level = 1 2 MiB (spawned thread) ~1,850
opt-level = 0 2 MiB (spawned thread) ~300

192 is sized against the last row — a consumer building glaucus in their own debug profile on a runtime whose workers get Rust's default 2 MiB stack. Any ceiling that keeps the recursion has to assume that case, so no amount of tuning gets this much higher. Only removing the recursion does.

What the rewrite involves

An explicit frame stack, the way the parser already works (Vec<State>, deliberately non-recursive). The delicate part is compose_mapping, which holds ~12 locals live across the recursive call:

  • entries, and the strict flag
  • seen_keys — a HashSet lazily promoted past DUP_LINEAR_THRESHOLD
  • merges — merge-key sources, with explicit-wins-then-earlier-source precedence
  • a pending key awaiting its value

All of that has to move into a frame type. The subtle risks are duplicate-key error spans, merge precedence, node-count ordering, and anchor-registration order — none of which the type system protects.

Acceptance criteria

  • Composition uses an explicit work-list; no recursive call remains in the node path
  • A 50,000-deep document composes successfully at opt-level = 0 on a 2 MiB stack
  • MAX_SAFE_DEPTH removed, or raised to a value justified by measurement rather than by stack budget
  • 735/735 conformance unchanged — this is the real oracle
  • Duplicate-key spans, merge-key precedence and anchor registration order all unchanged, with tests naming each
  • Node::clone recurses and overflows around 15,000 levels #36 (Node::clone recursion) resolved too, or the ceiling stays needed for a different reason

Split from #33 so the safety fix could ship without waiting on a rewrite of the composer's control flow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions