fix(conformance): one regex made the merge gate take 28 minutes instead of 2
ha-control-plane-as-code/check.sh had: re.search(r"--server https://.*?\n(?:.*\n){0,10}?\s*when:...'secondary'", K3S_TEXT, re.S) With re.S every `.` matches newlines, so `.*?` and the nested `(?:.*\n){0,10}?` can each consume the whole document -- nested unbounded repetition, which backtracks exponentially on a failed or late match. MEASURED in CI via `docker top` on the live job container: python3 at 19:54 of CPU time, which is essentially the entire ~28 minute gate. Localised to the exact line by extracting the inline python and running it under faulthandler.dump_traceback_later, which named re/__init__.py:177 in search from this call -- not inferred from reading. Replaced `.` with `[^\n]*` and dropped re.S, so the {0,10} is a real ten-line window with nothing to backtrack into. On the real input (22,179 chars): 0.2 ms, match still found. Whole check: 0.199s, rc 0, 115 assertions, identical success message. WHY IT SURFACED NOW, AND IT WAS MINE: K3S_TEXT is every *.yml in roles/k3s_server concatenated. The regex was latent for as long as it existed; PR #71 added ~100 lines to that role (kubelet graceful-shutdown config), growing the input past the point where exponential backtracking stopped being survivable. WHY ONLY infra/** WAS SLOW: touched-requirements.sh maps infra/* to R-ENV-01, so the L0 run becomes --include-scope whole-repo --requirements R-ENV-01, which selects this whole-repo check. Doc/platform changes never did -- #69 1m55s and #72 1m53s stayed fast while #71 27m44s, the #73 merge 27m46s and #75 all ran ~28m. The pattern is unique in the repo (grep over all l0-static check.sh files). Not re-timed in CI post-fix; this PR's own run is the first datapoint. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
1 parent
ee196c7bcd
commit
793c3940a8
2 files changed
+91
-2
No files matched your search
@@ -445,8 +445,21 @@ join = re.search(r"--server https://\{\{\s*k3s_api_host", K3S_TEXT)
|
||||
check(join is not None,
|
||||
"roles/k3s_server does not join secondaries with `--server https://{{ k3s_api_host }}` "
|
||||
"— without a join target the servers are separate single-node clusters")
|
||||
join_when = re.search(r"--server https://.*?\n(?:.*\n){0,10}?\s*when:\s*k3s_server_mode\s*==\s*'secondary'",
|
||||
K3S_TEXT, re.S)
|
||||
# LINE-BOUNDED ON PURPOSE -- DO NOT "SIMPLIFY" BACK TO `.*` + re.S.
|
||||
# This was `--server https://.*?\n(?:.*\n){0,10}?\s*when:...` with re.S, which made every `.`
|
||||
# match newlines too. That is nested unbounded repetition (a `{0,10}` of a quantifier that can each
|
||||
# swallow the whole document), so a FAILED or late match backtracks exponentially. It cost
|
||||
# **19m54s of CPU in CI** (measured 2026-10-18 via `docker top` on the live job container) and made
|
||||
# the whole merge gate take ~28 minutes instead of ~2 on any change touching `infra/**` -- because
|
||||
# `ci/touched-requirements.sh` maps `infra/*` to R-ENV-01, which pulls in this whole-repo check.
|
||||
# It was latent for as long as the regex existed and only crossed the threshold when
|
||||
# roles/k3s_server grew by ~100 lines (the kubelet graceful-shutdown config, PR #71): K3S_TEXT is
|
||||
# every *.yml in that role concatenated, so the role growing is the input growing.
|
||||
# `[^\n]*` cannot cross a line, so the `{0,10}` is a genuine ten-line window with nothing to
|
||||
# backtrack into. Measured on the same input: 0.2 ms, same verdict (match found).
|
||||
join_when = re.search(
|
||||
r"--server https://[^\n]*\n(?:[^\n]*\n){0,10}?[ \t]*when:[ \t]*k3s_server_mode[ \t]*==[ \t]*'secondary'",
|
||||
K3S_TEXT)
|
||||
check(join_when is not None,
|
||||
"the join install task is not gated on k3s_server_mode == 'secondary' — the founder "
|
||||
"would also try to join itself")
|
||||
|
||||
Reference in new issue
Block a user