{{ message }}
perf(interpreter): near-linear variable resolver (fix hang on large stacks) - #212
Open
swirle13 wants to merge 1 commit into
Open
perf(interpreter): near-linear variable resolver (fix hang on large stacks)#212swirle13 wants to merge 1 commit into
swirle13 wants to merge 1 commit into
Conversation
…rge stacks) find_replace_values re-resolves the entire attribute string once per nested reference (via replace_module_vars), which is O(refs**depth) and can hang for minutes / exhaust memory on large stacks whose attributes reference several module outputs (e.g. a jsonencode() secret built from other modules' endpoints). This adds, within a single handle_metadata_vars pass: - Memoization of (value, module) -> resolved result. - A re-entrancy guard that returns the string unchanged when the same (value, module) is already being resolved on the stack (that recursion cannot make progress and is the source of the exponential fan-out). - A per-attribute bounded-cost guard (call budget + working-string length) so a value the resolver could never fully resolve anyway (it would otherwise reach the existing recursion_depth>=50 'UNKNOWN' path) bails out in bounded time instead of after a hang. Normal attributes resolve in a handful of small calls and never hit the caps, so their resolved output is unchanged.
swirle13
force-pushed
the
perf/near-linear-variable-resolver
branch
from
September 4, 2026 23:25
a808873 to
cd5c859
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
modules.interpreter.find_replace_valuescan hang for many minutes (and grow tomultiple GB of RSS) on larger stacks. This PR makes the variable resolver
near-linear so those stacks render in seconds, while leaving the resolved
output of normal stacks unchanged.
This is unrelated to #209 (that fixed an interactive
terraformstdin prompthang). This one is an algorithmic blow-up inside the resolver itself.
Root cause
find_replace_valuesresolves a resource attribute string by regex-scanning itfor
var./local./data./module.references and substituting each.The problem is in
replace_module_vars: when a referenced module output'svalue itself contains further unresolved references, it recurses by calling
find_replace_valueson the entire attribute string again (not just on thesub-value), once per reference:
For an attribute with R references this fans out roughly O(R^depth) (capped
only by
recursion_depth >= 50), and the working string can grow on each pass asoutputs expand. The pathological shape is common: e.g. a
jsonencode({...})secret assembled from several other modules' endpoints/ARNs, where those values
are themselves references.
The fix (within one
handle_metadata_varspass)(value, module) -> resolved result.(value, module)is already beingresolved on the call stack, return it unchanged. That recursion cannot make
further progress on that exact string and is the source of the fan-out.
_FRV_MAX_CALLS) and aworking-string length cap (
_FRV_MAX_LEN). This only fires on values theresolver could never fully resolve anyway (they would otherwise reach the
existing
recursion_depth >= 50→"UNKNOWN"path) — so it fails in boundedtime instead of after a long hang.
Caches are module-level and cleared at the start of each
handle_metadata_varspass, so nothing leaks across CLI invocations.
Before / after (real-world)
Measured on real AWS Terraform stacks (planfile mode, so only the enrichment
pipeline is timed):
jsonencode()secret referencing ~8 module outputsProfiling the 15 enrichment steps on the ~115-resource stack showed
resolve_all_variableswas the sole hotspot (every other step < 0.1 s);after the patch it is ~27 s and the whole run is ~33 s.
Correctness
caps, so their resolved values are unchanged.
resulting diagram is structurally identical — same node set and same edges;
the only diff is TerraVision's own random per-run node UUIDs.
Notes
_FRV_MAX_CALLS/_FRV_MAX_LENare module constants and easy to tune; happyto make them configurable (env var / flag) or to split this into a
memoization-only change plus a separate discussion on the cap if you'd prefer.
replace_module_varsto resolve thereferenced output value in isolation and substitute it, rather than
re-resolving the whole string — but that is a larger, behavior-sensitive change
and I kept this PR minimal.