fix: bound the systemctl retry budget in cse_helpers.sh - #9310
Open
Ganeshkumar Ashokavardhanan (ganeshkumarashok) wants to merge 1 commit into
Open
fix: bound the systemctl retry budget in cse_helpers.sh#9310Ganeshkumar Ashokavardhanan (ganeshkumarashok) wants to merge 1 commit into
Ganeshkumar Ashokavardhanan (ganeshkumarashok) wants to merge 1 commit into
Conversation
systemctlEnableAndStart/NoBlock hardcoded `systemctl_restart 100 5 $timeout`.
The retry count is not a time bound, and the `30`/`300` a caller passes is only
the per-systemctl-call timeout, not a budget. A unit that fails fast burns
100 * 5s sleeping before giving up; a unit that hangs burns retries * timeout,
which can exceed the 900s DefaultCSETimeout on its own.
Measured with a fast-failing unit: 498s to give up on a single service.
That time is taken out of the CSE window, so downstream provisioning steps
never run and their specific error codes get replaced by a generic CSE
timeout. cse_config.sh::configureLocalDNSExporterSocket already carries a
per-caller workaround for exactly this ("systemctlEnableAndStartNoBlock would
hit its retry loop (~100 retries x 5s) for a missing unit").
Changes:
- _systemctl_retry_svc_operation takes an optional maxBudget (7th arg,
default 0 = off), matching the convention already used by
_retry_file_curl_internal. It caps the per-attempt timeout to the
remaining budget and returns 2 when the budget is exhausted.
- Add the global check_cse_timeout guard that every other retry helper in
this file already has, so we fail with a specific error code instead of
running the CSE window down.
- Bound the per-failure journal dump (`--no-pager -n 50`). It ran on every
failed attempt with no limit, so log volume grew quadratically with retries.
- systemctlEnableAndStart/NoBlock take an optional retry count and apply a
120s budget: 498s -> 122s for a fast-failing unit.
- Pass a small retry count for units whose failure is already explicitly
non-fatal (mig-partition, node-exporter, dcgm, localdns-exporter.socket,
aks-log-collector.timer, ...): 498s -> 10s each. There are enough of these
that a full budget each would exceed the CSE window on its own. Note
ensureMigPartition is documented as "expected to fail and work only on next
reboot", yet retried for ~500s.
- systemctl enable retries 120 -> 3. It only writes symlinks on disk, and it
runs right after a successful restart, so a 600s tail cannot help.
Every new parameter is optional and defaults to today's values, so old and
new copies of these scripts interoperate. systemctl_stop/systemctl_disable
are unchanged (budget defaults to 0).
Adds shellspec coverage for the retry loop, which previously had none - all
existing callers mocked the helper.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ganeshkumar Ashokavardhanan (ganeshkumarashok)
requested review from
Chou Hu (AbelHu),
Devinwong,
Sri Harsha (SriHarsha001),
Abigail Liang (abigailliang-aks-sig-node),
Abdul Asfari (aboodasfari),
Nishchay (awesomenix),
Ben Brady (benjamin-brady),
Calvin S. (calvin197),
Cameron Meissner (cameronmeissner),
Sylvain Boily (djsly),
fcher,
janenotjung-hue,
Karen Chen (karenychen),
lilypan26,
Mark Ibrahim (mxj220),
Peter Damianov (pdamianov-dev),
Patrick W. Healy (phealy),
r2k1,
Runzhen (runzhen),
Shun Lyu (sinmentis),
sulixu,
Tim Wright (timmy-wright),
Thibault Cohen (titilambert),
Xu Xue (xuexu6666) and
Zachary (zachary-bailey)
as code owners
August 26, 2026 02:01
Copilot started reviewing on behalf of
Ganeshkumar Ashokavardhanan (ganeshkumarashok)
August 26, 2026 02:02
View session
Contributor
Windows Unit Test Results 3 files 13 suites 50s ⏱️ Results for commit 3b9a7d6. |
Contributor
There was a problem hiding this comment.
Pull request overview
Bounds systemctl retries during Linux VHD builds and node provisioning.
Changes:
- Adds operation budgets and bounded journal output.
- Reduces retries for optional services.
- Adds ShellSpec coverage for retry behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
cse_helpers.sh |
Adds retry budgets and tuning constants. |
cse_config.sh |
Applies reduced retries to optional services. |
cse_main.sh |
Limits log collector timer retries. |
cse_retry_helpers_spec.sh |
Tests budgets, passthrough, and journal limits. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+631
to
+632
| timeout $effectiveTimeout systemctl daemon-reload | ||
| timeout $effectiveTimeout systemctl $operation $svcname && return 0 |
Comment on lines
+607
to
+608
| retries=$1; wait_sleep=$2; timeout=$3 operation=$4 svcname=$5 shouldLogRetryInfo=${6:-false} maxBudget=${7:-0} | ||
| opStartTime=$(date +%s) |
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.
What
systemctlEnableAndStart/systemctlEnableAndStartNoBlockhardcodesystemctl_restart 100 5 $timeout $service.The retry count is not a time bound, and the
30/300a caller passes is only the per-systemctl-call timeout, not a retry budget.systemctlEnableAndStart localdns 30reads like "give up after 30s"; it can actually run for over an hour.I measured this with a fast-failing unit:
DefaultCSETimeoutis 900s (pkg/agent/datamodel/const.go). A single bad unit eating 498s of that means downstream provisioning steps never run, and their specific error codes get replaced by a generic CSE timeout — which is strictly worse for diagnosis than failing fast.This isn't hypothetical.
cse_config.sh::configureLocalDNSExporterSocketalready carries a per-caller workaround for exactly this problem:# systemctlEnableAndStartNoBlock would hit its retry loop (~100 retries × 5s) for a missing unit.The
100 5values date to #7339 and were never deliberately tuned (#8105 only moved the status logging).Changes
_systemctl_retry_svc_operationmaxBudget(7th arg, default0= disabled) — the same convention_retry_file_curl_internalalready uses in this file. Caps the per-attempt timeout to the remaining budget and returns2when exhausted.check_cse_timeoutguard that every other retry helper here already has, so we exit with a specific code instead of running the CSE window to zero.journalctl -u $svc --no-pager -n 50. It previously ran unbounded on every failed attempt, so log volume grew quadratically with the retry count.systemctlEnableAndStart/NoBlockretriesparam; applies a 120s budget by default.systemctl enableretries120 → 3. It only writes symlinks on disk and runs immediately after a successful restart, so a 600s tail cannot help.Callers — a small retry count for units whose failure is already explicitly non-fatal:
mig-partition,node-exporter(+.path),nvidia-dcgm(+-exporter),localdns-exporter.socket,aks-localdns-hosts-setup.timer,aks-log-collector.timer,measure-tls-bootstrapping-latency. There are enough of these that a full 120s budget each would exceed the CSE window on its own.Worth calling out:
ensureMigPartitionis documented as "this is expected to fail and work only on next reboot" — and is currently retried for ~500s.containerd,kubelet,localdns, andnvidia-griddkeep the default budget.Compatibility
Every new parameter is optional and defaults to today's values, so old and new copies of these scripts interoperate in both directions.
systemctl_stop/systemctl_disableare unchanged (budget defaults to0).cse_helpers.shandcse_config.shship in the same CustomData blob, so the shared constants are versioned together.Testing
make shellspec(docker): 939 examples, 0 failuresmake validate-shell: shellcheck findings byte-identical tomain(the gate has pre-existing failures in unrelated files).go test ./pkg/agent/...: okNo testdata regeneration needed —
cse_helpers.shis not embedded in the parser snapshots.