Parse hcl in a reusable worker process - #448
Open
benlangfeld wants to merge 1 commit into
Open
benlangfeld wants to merge 1 commit into
benlangfeld wants to merge 1 commit into
Conversation
load() started a fresh Python interpreter for every file it was asked to parse, so that a parse could be killed if it hung, and then parsed the file again in the calling process. The interpreter start and the lark import it pulls in cost about 113ms, which load_module() pays once per file in the root module. On a 245 file module that is 33s of process spawning to do 0.6s of parsing, and load_module() is called twice per plan and four times per apply, so it is around 90s of a plan and 170s of an apply. Run the parse in a worker process that is reused across calls instead. Killing a hung parse still works, because the worker is a separate process that can be terminated, but a module now pays for one worker rather than one per file: load_module() over those 245 files goes from 41.2s to 0.6s, with identical output. The worker returns the reason a parse failed rather than raising, since lark's exceptions can't be pickled back to the caller and the reason is wanted for the debug log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
benlangfeld
marked this pull request as ready for review
September 24, 2026 16:38
This branch has not been deployed
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.
Fixes #447.
terraform.hcl.load()started a fresh Python interpreter for every file it was asked to parse, to find out whether the file could be parsed, and then parsed the file again in the calling process. The interpreter start and theimport hcl2/import larkit pulls in cost ~113ms, whichload_module()pays once per.tffile in the root module.The subprocess was added in f87a90c to mitigate hangs from unterminated strings, and that requirement still stands — a hang inside a
rematch can't be interrupted by an in-process timeout, so the parse does have to happen somewhere killable. It doesn't need a new interpreter per file though.This runs the parse in a worker process that is reused across calls, and is only replaced when one actually has to be killed. A module pays for one worker instead of one per file.
Effect
On a module with 245 root
.tffiles (14k lines, 1.1MB), python-hcl2 7.3.1:load_module()Output is identical (860 top-level blocks both ways).
load_module()is called fromterraform-backendandgithub_pr_comment, so on that module this is worth ~90s perterraform-planrun and ~170s perterraform-apply.Notes
cannot pickle 'module' object. The debug log keeps the same content it has today.load()'s contract is unchanged: a timeout raisesValueError(soload_module()skips the file, as now), anything else falls back tofallback_parser.loads()still raisesValueErroreither way.is_loadable()and the__main__block existed only to support the subprocess and are gone. Nothing else referenced them.forkis preferred for the worker so it inherits the already-imported parser; without it the worker starts an interpreter, but still only once.tests/test_hcl.pycovering the timeout kill and worker reuse.pytest testspasses (213 tests, excluding the ones needing network access to releases.hashicorp.com), as doruff --config=.config/ruff.toml checkandmypy.🤖 Generated with Claude Code