Fix timeout implementation and address review feedback

- Kill git process on timeout: use child_process.spawn directly for
  timeout-eligible operations so we have a ChildProcess handle to send
  SIGTERM (then SIGKILL after 5s). On Windows, SIGTERM is a forced kill
  so the SIGKILL fallback is effectively a no-op there.

- Fix timeout:0 not working: replace falsy || coalescion with explicit
  empty-string check so that '0' is not replaced by the default '300'.

- Refactor execGit to use an options object instead of 5 positional
  parameters, eliminating error-prone filler args (false, false, {}).

- Pass allowAllExitCodes through to execGitWithTimeout so both code
  paths have consistent behavior for non-zero exit codes.

- Add settled guard to prevent double-reject when both close and error
  events fire on the spawned process.

- Handle null exit code (process killed by signal) as an error rather
  than silently treating it as success.

- Capture stderr in error messages for the timeout path, matching the
  information level of the non-timeout exec path.

- Log SIGKILL failures at debug level instead of empty catch block.

- Warn on customListeners being ignored in the timeout path.

- Emit core.warning() when invalid input values are silently replaced
  with defaults, so users know their configuration was rejected.

- Add input validation in setTimeout (reject negative values).

- Clarify retry-max-attempts semantics: total attempts including the
  initial attempt (3 = 1 initial + 2 retries).

- Remove Kubernetes probe references from descriptions.

- Use non-exhaustive list (e.g.) for network operations in docs to
  avoid staleness if new operations are added.

- Add tests for timeout/retry input parsing (defaults, timeout:0,
  custom values, invalid input with warnings, backoff clamping) and
  command manager configuration (setTimeout, setRetryConfig, fetch).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Anatoly Rabkin 2026-03-18 19:04:59 +02:00
parent 5df58a66d1
commit 3ff67abc5a
8 changed files with 556 additions and 121 deletions

View file

@ -161,38 +161,57 @@ export async function getInputs(): Promise<IGitSourceSettings> {
result.githubServerUrl = core.getInput('github-server-url')
core.debug(`GitHub Host URL = ${result.githubServerUrl}`)
// Timeout (per-attempt, like k8s timeoutSeconds)
result.timeout = Math.floor(Number(core.getInput('timeout') || '300'))
// Timeout per network operation attempt
const timeoutInput = core.getInput('timeout')
result.timeout = Math.floor(Number(timeoutInput !== '' ? timeoutInput : '300'))
if (isNaN(result.timeout) || result.timeout < 0) {
core.warning(
`Invalid value '${timeoutInput}' for 'timeout' input. Using default: 300 seconds.`
)
result.timeout = 300
}
core.debug(`timeout = ${result.timeout}`)
// Retry max attempts (like k8s failureThreshold)
// Retry max attempts (total attempts including initial)
const retryMaxAttemptsInput = core.getInput('retry-max-attempts')
result.retryMaxAttempts = Math.floor(
Number(core.getInput('retry-max-attempts') || '3')
Number(retryMaxAttemptsInput !== '' ? retryMaxAttemptsInput : '3')
)
if (isNaN(result.retryMaxAttempts) || result.retryMaxAttempts < 1) {
core.warning(
`Invalid value '${retryMaxAttemptsInput}' for 'retry-max-attempts' input. Using default: 3.`
)
result.retryMaxAttempts = 3
}
core.debug(`retry max attempts = ${result.retryMaxAttempts}`)
// Retry backoff (like k8s periodSeconds, but as a min/max range)
// Retry backoff range
const retryMinBackoffInput = core.getInput('retry-min-backoff')
result.retryMinBackoff = Math.floor(
Number(core.getInput('retry-min-backoff') || '10')
Number(retryMinBackoffInput !== '' ? retryMinBackoffInput : '10')
)
if (isNaN(result.retryMinBackoff) || result.retryMinBackoff < 0) {
core.warning(
`Invalid value '${retryMinBackoffInput}' for 'retry-min-backoff' input. Using default: 10 seconds.`
)
result.retryMinBackoff = 10
}
core.debug(`retry min backoff = ${result.retryMinBackoff}`)
const retryMaxBackoffInput = core.getInput('retry-max-backoff')
result.retryMaxBackoff = Math.floor(
Number(core.getInput('retry-max-backoff') || '20')
Number(retryMaxBackoffInput !== '' ? retryMaxBackoffInput : '20')
)
if (isNaN(result.retryMaxBackoff) || result.retryMaxBackoff < 0) {
core.warning(
`Invalid value '${retryMaxBackoffInput}' for 'retry-max-backoff' input. Using default: 20 seconds.`
)
result.retryMaxBackoff = 20
}
if (result.retryMaxBackoff < result.retryMinBackoff) {
core.warning(
`'retry-max-backoff' (${result.retryMaxBackoff}) is less than 'retry-min-backoff' (${result.retryMinBackoff}). Using retry-min-backoff value for both.`
)
result.retryMaxBackoff = result.retryMinBackoff
}
core.debug(`retry max backoff = ${result.retryMaxBackoff}`)